diff --git a/ChangeLog/7.1.0-RC-dev.txt b/ChangeLog/7.1.0-RC-dev.txt index 5abb980437..b250b7e6bb 100644 --- a/ChangeLog/7.1.0-RC-dev.txt +++ b/ChangeLog/7.1.0-RC-dev.txt @@ -1,2 +1,3 @@ +[main] Add support for new .NET 6 DateOnly and TimeOnly types [main] Updated BitFaster.Caching package reference to v2.0.0 [sqlserver] Updated Microsoft.Data.SqlClient package reference to v5.0.0 \ No newline at end of file diff --git a/Extensions/TestCommon/TestCommon.csproj b/Extensions/TestCommon/TestCommon.csproj index f19fa46dd6..e23f0c4aef 100644 --- a/Extensions/TestCommon/TestCommon.csproj +++ b/Extensions/TestCommon/TestCommon.csproj @@ -12,7 +12,7 @@ - + diff --git a/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj b/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj index ab1e779974..34ee5d125c 100644 --- a/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj +++ b/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj @@ -16,7 +16,7 @@ - + diff --git a/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj b/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj index 686f6bf4ac..8643bbdc08 100644 --- a/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj +++ b/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj @@ -17,7 +17,7 @@ - + diff --git a/Extensions/Xtensive.Orm.Tracking/Xtensive.Orm.Tracking.csproj b/Extensions/Xtensive.Orm.Tracking/Xtensive.Orm.Tracking.csproj index b69fff4349..3c18a0e846 100644 --- a/Extensions/Xtensive.Orm.Tracking/Xtensive.Orm.Tracking.csproj +++ b/Extensions/Xtensive.Orm.Tracking/Xtensive.Orm.Tracking.csproj @@ -16,7 +16,7 @@ - + diff --git a/Extensions/Xtensive.Orm.Web/Xtensive.Orm.Web.csproj b/Extensions/Xtensive.Orm.Web/Xtensive.Orm.Web.csproj index d4d5c4299e..a919cdb476 100644 --- a/Extensions/Xtensive.Orm.Web/Xtensive.Orm.Web.csproj +++ b/Extensions/Xtensive.Orm.Web/Xtensive.Orm.Web.csproj @@ -19,7 +19,7 @@ - + diff --git a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/Constants.cs b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/Constants.cs index d0c47590a5..700a2187fa 100644 --- a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/Constants.cs +++ b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/Constants.cs @@ -14,5 +14,9 @@ internal static class Constants // cannot use "FFF" cause it may lead to empty string for fractions part. public const string DateTimeFormatString = @"''\'yyyy\.MM\.dd HH\:mm\:ss\.fff\'''"; +#if NET6_0_OR_GREATER + public const string DateFormatString = @"''\'yyyy\.MM\.dd\'''"; + public const string TimeFormatString = @"''\'HH\:mm\:ss\.ffff\'''"; +#endif } } \ No newline at end of file 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 82a5c02c26..1c00fdfaea 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 @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2022 Xtensive LLC. +// Copyright (C) 2011-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: Csaba Beer @@ -101,6 +101,31 @@ public override void Visit(SqlExtract node) Visit(CastToLong(node.Operand)); return; } +#if NET6_0_OR_GREATER + if (((node.IsDatePart && node.DatePart == SqlDatePart.DayOfYear) + || (node.IsDateTimePart && node.DateTimePart == SqlDateTimePart.DayOfYear))) { + if (!case_SqlDateTimePart_DayOfYear) { + case_SqlDateTimePart_DayOfYear = true; + Visit(SqlDml.Add(node, SqlDml.Literal(1))); + case_SqlDateTimePart_DayOfYear = false; + } + else { + base.Visit(node); + } + return; + } + else if (node.IsSecondExtraction) { + if (!case_SqlDateTimePart_Second) { + case_SqlDateTimePart_Second = true; + Visit(SqlDml.Truncate(node)); + case_SqlDateTimePart_Second = false; + } + else { + base.Visit(node); + } + return; + } +#else switch (node.DateTimePart) { case SqlDateTimePart.DayOfYear: if (!case_SqlDateTimePart_DayOfYear) { @@ -123,6 +148,8 @@ public override void Visit(SqlExtract node) } return; } +#endif + base.Visit(node); } @@ -149,6 +176,14 @@ public override void Visit(SqlBinary node) case SqlNodeType.DateTimeMinusDateTime: DateTimeSubtractDateTime(node.Left, node.Right).AcceptVisitor(this); return; +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: + TimeAddInterval(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.TimeMinusTime: + TimeSubtractTime(node.Left, node.Right).AcceptVisitor(this); + return; +#endif case SqlNodeType.Modulo: Visit(SqlDml.FunctionCall(translator.TranslateToString(SqlNodeType.Modulo), node.Left, node.Right)); return; @@ -170,34 +205,84 @@ public override void Visit(SqlBinary node) /// public override void Visit(SqlFunctionCall node) { + var arguments = node.Arguments; + switch (node.FunctionType) { case SqlFunctionType.Concat: - Visit(SqlDml.Concat(node.Arguments.ToArray(node.Arguments.Count))); + Visit(SqlDml.Concat(arguments.ToArray(node.Arguments.Count))); return; case SqlFunctionType.DateTimeTruncate: - Visit(SqlDml.Cast(node.Arguments[0], new SqlValueType("Date"))); + Visit(SqlDml.Cast(arguments[0], new SqlValueType("Date"))); return; case SqlFunctionType.IntervalToMilliseconds: - Visit(CastToLong(node.Arguments[0]) / NanosecondsPerMillisecond); + Visit(CastToLong(arguments[0]) / NanosecondsPerMillisecond); return; case SqlFunctionType.IntervalConstruct: case SqlFunctionType.IntervalToNanoseconds: - Visit(CastToLong(node.Arguments[0])); + Visit(CastToLong(arguments[0])); return; case SqlFunctionType.DateTimeAddMonths: - Visit(DateAddMonth(node.Arguments[0], node.Arguments[1])); + Visit(DateAddMonth(arguments[0], arguments[1])); return; case SqlFunctionType.DateTimeAddYears: - Visit(DateAddYear(node.Arguments[0], node.Arguments[1])); + 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), - node.Arguments[0] - 2001), - node.Arguments[1] - 1), - node.Arguments[2] - 1)); + arguments[0] - 2001), + arguments[1] - 1), + arguments[2] - 1)); + return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateAddYears: + Visit(DateAddYear(arguments[0], arguments[1])); + return; + case SqlFunctionType.DateAddMonths: + Visit(DateAddMonth(arguments[0], arguments[1])); + return; + case SqlFunctionType.DateAddDays: + 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)); + return; + case SqlFunctionType.TimeAddHours: + Visit(DateAddHour(node.Arguments[0], node.Arguments[1])); + return; + case SqlFunctionType.TimeAddMinutes: + 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])); return; + case SqlFunctionType.DateToString: + Visit(DateToString(arguments[0])); + return; + case SqlFunctionType.TimeToString: + Visit(TimeToString(arguments[0])); + return; + case SqlFunctionType.DateToDateTime: + DateToDateTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToDate: + DateTimeToDate(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToTime: + DateTimeToTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTime: + TimeToDateTime(arguments[0]).AcceptVisitor(this); + return; +#endif case SqlFunctionType.DateTimeToStringIso: - Visit(DateTimeToStringIso(node.Arguments[0])); + Visit(DateTimeToStringIso(arguments[0])); return; } @@ -223,6 +308,15 @@ protected static SqlExpression DateTimeSubtractDateTime(SqlExpression date1, Sql (CastToLong(DateDiffMillisecond(DateAddDay(date2, DateDiffDay(date2, date1)), date1)) * NanosecondsPerMillisecond); } +#if NET6_0_OR_GREATER + + protected static SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) + { + return SqlDml.Modulo( + NanosecondsPerDay + CastToLong(DateDiffMillisecond(time2, time1)) * NanosecondsPerMillisecond, + NanosecondsPerDay); + } +#endif protected static SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpression interval) { @@ -231,6 +325,9 @@ protected static SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpres (interval / NanosecondsPerMillisecond) % (MillisecondsPerDay)); } + protected static SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval) => + DateAddMillisecond(time, (interval / NanosecondsPerMillisecond) % (MillisecondsPerDay)); + protected static SqlCast CastToLong(SqlExpression arg) => SqlDml.Cast(arg, SqlType.Int64); protected static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2) => @@ -271,6 +368,26 @@ protected static SqlUserFunctionCall BitXor(SqlExpression left, SqlExpression ri protected static SqlUserFunctionCall BitNot(SqlExpression operand) => SqlDml.FunctionCall("BIN_NOT", operand); +#if NET6_0_OR_GREATER + + protected static SqlExpression TimeToDateTime(SqlExpression time) => + SqlDml.Cast(time, SqlType.DateTime); + + protected static SqlExpression DateTimeToTime(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Time); + + protected static SqlExpression DateToDateTime(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTime); + + protected static SqlExpression DateTimeToDate(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Date); + + protected static SqlFunctionCall DateToString(SqlExpression date) => + SqlDml.Substring(date, 0, 10); + + protected static SqlConcat TimeToString(SqlExpression time) => + SqlDml.Concat(SqlDml.Substring(time, 0, 12), SqlDml.Literal("0000")); +#endif protected static SqlConcat DateTimeToStringIso(SqlExpression dateTime) { @@ -279,8 +396,8 @@ protected static SqlConcat DateTimeToStringIso(SqlExpression dateTime) return SqlDml.Concat(date, SqlDml.Literal("T"), time); } - - #endregion + +#endregion protected internal Compiler(SqlDriver driver) : base(driver) diff --git a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Extractor.cs b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Extractor.cs index 1ab70927fd..9a6512aba6 100644 --- a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Extractor.cs +++ b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Extractor.cs @@ -490,7 +490,7 @@ private static void ReadForeignKeyColumnData(DbDataReader reader, ref ForeignKey state.ReferencingTable = state.ReferencingSchema.Tables[reader.GetString(1).Trim()]; state.ForeignKey = state.ReferencingTable.CreateForeignKey(reader.GetString(2).Trim()); ReadConstraintProperties(state.ForeignKey, reader, 3, 4); - ReadCascadeAction(state.ForeignKey, reader, 5); + ReadCascadeAction(state.ForeignKey, reader, 5, 12); state.ReferencedTable = state.ReferencedSchema.Tables[reader.GetString(9).Trim()]; state.ForeignKey.ReferencedTable = state.ReferencedTable; } @@ -618,25 +618,25 @@ private static void ReadConstraintProperties(Constraint constraint, constraint.IsInitiallyDeferred = ReadStringOrNull(row, isInitiallyDeferredIndex) == "YES"; } - private static void ReadCascadeAction(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex) + private static void ReadCascadeAction(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex, int updateRuleIndex) { var deleteRule = ReadStringOrNull(row, deleteRuleIndex); - switch (deleteRule) { - case "CASCADE": - foreignKey.OnDelete = ReferentialAction.Cascade; - return; - case "SET NULL": - foreignKey.OnDelete = ReferentialAction.SetNull; - return; - case "NO ACTION": - foreignKey.OnDelete = ReferentialAction.NoAction; - return; - case "RESTRICT": // behaves like NO ACTION - foreignKey.OnDelete = ReferentialAction.NoAction; - return; - case "SET DEFAULT": - foreignKey.OnDelete = ReferentialAction.SetDefault; - return; + foreignKey.OnDelete = GetRefAction(deleteRule); + + var updateRule = ReadStringOrNull(row, updateRuleIndex); + foreignKey.OnUpdate = GetRefAction(updateRule); + + + static ReferentialAction GetRefAction(in string rawActionName) + { + return rawActionName switch { + "CASCADE" => ReferentialAction.Cascade, + "SET NULL" => ReferentialAction.SetNull, + "NO ACTION" => ReferentialAction.NoAction, + "RESTRICT" => ReferentialAction.NoAction, + "SET DEFAULT" => ReferentialAction.SetDefault, + _ => throw new ArgumentOutOfRangeException() + }; } } diff --git a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/ServerInfoProvider.cs b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/ServerInfoProvider.cs index ef04c0ec26..bb32bdb1dd 100644 --- a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/ServerInfoProvider.cs @@ -237,6 +237,11 @@ public override DataTypeCollection GetDataTypesInfo() dtc.DateTime = DataTypeInfo.Range(SqlType.DateTime, commonFeatures, ValueRange.DateTime, "timestamp"); +#if NET6_0_OR_GREATER + + dtc.DateOnly = DataTypeInfo.Range(SqlType.Date, commonFeatures, ValueRange.DateOnly, "date"); + dtc.TimeOnly = DataTypeInfo.Range(SqlType.Time, commonFeatures, ValueRange.TimeOnly, "time"); +#endif dtc.Char = DataTypeInfo.Stream(SqlType.Char, commonFeatures, MaxCharLength, "char"); dtc.VarChar = DataTypeInfo.Stream(SqlType.VarChar, commonFeatures, MaxCharLength, "varchar"); diff --git a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Translator.cs b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Translator.cs index e30a69e275..b60a32f15c 100644 --- a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Translator.cs +++ b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Translator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2022 Xtensive LLC. +// Copyright (C) 2011-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: Csaba Beer @@ -19,15 +19,15 @@ namespace Xtensive.Sql.Drivers.Firebird.v2_5 { internal class Translator : SqlTranslator { - public override string DateTimeFormatString - { - get { return Constants.DateTimeFormatString; } - } + public override string DateTimeFormatString => Constants.DateTimeFormatString; - public override string TimeSpanFormatString - { - get { return string.Empty; } - } +#if NET6_0_OR_GREATER + public override string DateOnlyFormatString => Constants.DateFormatString; + + public override string TimeOnlyFormatString => Constants.TimeFormatString; +#endif + + public override string TimeSpanFormatString => string.Empty; public override SqlHelper.EscapeSetup EscapeSetup => SqlHelper.EscapeSetup.WithQuotes; @@ -123,6 +123,9 @@ public override void Translate(IOutput output, SqlNodeType type) case SqlNodeType.Modulo: _ = output.Append("MOD"); break; case SqlNodeType.DateTimeMinusDateTime: +#if NET6_0_OR_GREATER + case SqlNodeType.TimeMinusTime: +#endif _ = output.Append("-"); break; case SqlNodeType.Except: case SqlNodeType.Intersect: @@ -221,6 +224,18 @@ public override void Translate(IOutput output, SqlDateTimePart dateTimePart) default: base.Translate(output, dateTimePart); break; } } +#if NET6_0_OR_GREATER //DO_DATEONLY + + /// + public override void Translate(IOutput output, SqlDatePart datePart) + { + switch (datePart) { + case SqlDatePart.DayOfYear: _ = output.Append("YEARDAY"); break; + case SqlDatePart.DayOfWeek: _ = output.Append("WEEKDAY"); break; + default: base.Translate(output, datePart); break; + } + } +#endif /// public override void Translate(SqlCompilerContext context, SqlSelect node, SelectSection section) 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 51afafdf9c..ae20d304fa 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 @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2022 Xtensive LLC. +// Copyright (C) 2011-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: Malisa Ncube @@ -94,6 +94,14 @@ public override void Visit(SqlBinary node) case SqlNodeType.DateTimeMinusInterval: DateTimeAddInterval(node.Left, -node.Right).AcceptVisitor(this); return; +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: + TimeAddInterval(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.TimeMinusTime: + TimeSubtractTime(node.Left, node.Right).AcceptVisitor(this); + return; +#endif default: base.Visit(node); return; @@ -137,13 +145,14 @@ public override void Visit(SqlQueryExpression node) /// public override void Visit(SqlFunctionCall node) { + var arguments = node.Arguments; + switch (node.FunctionType) { case SqlFunctionType.Truncate: - var argument = node.Arguments[0]; - SqlDml.FunctionCall("TRUNCATE", argument, SqlDml.Literal(0)).AcceptVisitor(this); + SqlDml.FunctionCall("TRUNCATE", arguments[0], SqlDml.Literal(0)).AcceptVisitor(this); return; case SqlFunctionType.Concat: - Visit(SqlDml.Concat(node.Arguments.ToArray(node.Arguments.Count))); + Visit(SqlDml.Concat(arguments.ToArray(node.Arguments.Count))); return; case SqlFunctionType.CharLength: SqlDml.FunctionCall(translator.TranslateToString(SqlFunctionType.CharLength), node.Arguments[0]).AcceptVisitor(this); @@ -157,34 +166,107 @@ public override void Visit(SqlFunctionCall node) SqlDml.FunctionCall(translator.TranslateToString(SqlFunctionType.Rand)).AcceptVisitor(this); return; case SqlFunctionType.Square: - SqlDml.Power(node.Arguments[0], 2).AcceptVisitor(this); + SqlDml.Power(arguments[0], 2).AcceptVisitor(this); return; case SqlFunctionType.IntervalToMilliseconds: - Visit(CastToLong(node.Arguments[0]) / NanosecondsPerMillisecond); + Visit(CastToLong(arguments[0]) / NanosecondsPerMillisecond); return; case SqlFunctionType.IntervalConstruct: case SqlFunctionType.IntervalToNanoseconds: - Visit(CastToLong(node.Arguments[0])); + Visit(CastToLong(arguments[0])); return; case SqlFunctionType.DateTimeAddMonths: - Visit(DateAddMonth(node.Arguments[0], node.Arguments[1])); + Visit(DateTimeAddMonth(arguments[0], arguments[1])); return; case SqlFunctionType.DateTimeAddYears: - Visit(DateAddYear(node.Arguments[0], node.Arguments[1])); + Visit(DateTimeAddYear(arguments[0], arguments[1])); return; case SqlFunctionType.DateTimeConstruct: - Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)), - node.Arguments[0] - 2001), - node.Arguments[1] - 1), - node.Arguments[2] - 1)); + Visit(DateTimeAddDay(DateTimeAddMonth(DateTimeAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)), + arguments[0] - 2001), + arguments[1] - 1), + arguments[2] - 1)); + return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateAddYears: + Visit(DateAddYear(arguments[0], arguments[1])); + return; + case SqlFunctionType.DateAddMonths: + Visit(DateAddMonth(arguments[0], arguments[1])); + return; + case SqlFunctionType.DateAddDays: + 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)); + return; + case SqlFunctionType.TimeAddHours: + Visit(SqlDml.FunctionCall("TIME", SqlDml.FunctionCall( + "DATE_ADD", + SqlDml.Literal(new DateTime(2001, 1, 1)), + SqlDml.RawConcat( + SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.FunctionCall("TIME_TO_SEC", arguments[0]) + arguments[1] * 3600), + SqlDml.Native("SECOND"))))); + return; + case SqlFunctionType.TimeAddMinutes: + Visit(SqlDml.FunctionCall("TIME", + SqlDml.FunctionCall("DATE_ADD", + SqlDml.Literal(new DateTime(2001, 1, 1)), + SqlDml.RawConcat( + 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]))); + return; + case SqlFunctionType.DateToString: + Visit(DateToString(arguments[0])); + return; + case SqlFunctionType.TimeToString: + Visit(TimeToString(arguments[0])); return; + case SqlFunctionType.DateTimeToDate: + DateTimeToDate(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTime: + DateToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToTime: + DateTimeToTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTime: + TimeToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; +#endif case SqlFunctionType.DateTimeToStringIso: - Visit(DateTimeToStringIso(node.Arguments[0])); + Visit(DateTimeToStringIso(arguments[0])); return; } base.Visit(node); } +#if NET6_0_OR_GREATER + + public override void Visit(SqlPlaceholder node) + { + if (node.Id is Orm.Providers.ParameterBinding qpb + && qpb.TypeMapping?.Type == typeof(TimeOnly)) { + _ = context.Output.Append("TIME("); + base.Visit(node); + _ = context.Output.Append(")"); + } + else { + base.Visit(node); + } + } +#endif /// protected override void VisitSelectLimitOffset(SqlSelect node) @@ -210,6 +292,13 @@ public override void Visit(SqlExtract node) Visit(SqlDml.FunctionCall(node.DateTimePart.ToString(), node.Operand)); return; } +#if NET6_0_OR_GREATER + if (node.DatePart == SqlDatePart.DayOfWeek || node.DatePart == SqlDatePart.DayOfYear) { + Visit(SqlDml.FunctionCall(node.DatePart.ToString(), node.Operand)); + return; + } +#endif + base.Visit(node); } @@ -217,40 +306,113 @@ protected virtual SqlExpression DateTimeSubtractDateTime(SqlExpression date1, Sq { return (CastToDecimal(DateDiffDay(date1, date2), 18, 0) * NanosecondsPerDay) + - (CastToDecimal(DateDiffMicrosecond(DateAddDay(date2, DateDiffDay(date1, date2)), date1), 18, 0) * NanosecondsPerMicrosecond); + (CastToDecimal(DateTimeDiffMicrosecond(DateTimeAddDay(date2, DateDiffDay(date1, date2)), date1), 18, 0) * NanosecondsPerMicrosecond); } protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpression interval) { - return DateAddMicrosecond( - DateAddDay(date, ((interval - (interval % NanosecondsPerDay)) + ((interval % NanosecondsPerDay) > (NanosecondsPerDay / 2) ? 0 : 1)) / NanosecondsPerDay), + return DateTimeAddMicrosecond( + DateTimeAddDay(date, ((interval - (interval % NanosecondsPerDay)) + ((interval % NanosecondsPerDay) > (NanosecondsPerDay / 2) ? 0 : 1)) / NanosecondsPerDay), (interval / NanosecondsPerMillisecond * NanosecondsPerMicrosecond) % (MillisecondsPerDay * NanosecondsPerMicrosecond)); } +#if NET6_0_OR_GREATER + + 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, + NanosecondsPerDay); + + protected virtual SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval) => + SqlDml.FunctionCall("TIME", + SqlDml.FunctionCall( + "DATE_ADD", + SqlDml.Literal(new DateTime(2001, 1, 1)), + SqlDml.RawConcat( + SqlDml.RawConcat(SqlDml.Native("INTERVAL "), + SqlDml.FunctionCall("TIME_TO_SEC", time) + interval / NanosecondsPerSecond), + SqlDml.Native("SECOND")))); +#endif #region Static helpers - private static SqlCast CastToLong(SqlExpression arg) => SqlDml.Cast(arg, SqlType.Int64); + protected static SqlCast CastToLong(SqlExpression arg) => SqlDml.Cast(arg, SqlType.Int64); - private static SqlCast CastToDecimal(SqlExpression arg, short precision, short scale) => + protected static SqlCast CastToDecimal(SqlExpression arg, short precision, short scale) => SqlDml.Cast(arg, SqlType.Decimal, precision, scale); - private static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2) => + protected static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2) => SqlDml.FunctionCall("DATEDIFF", date1, date2); - private static SqlUserFunctionCall DateDiffMicrosecond(SqlExpression date1, SqlExpression date2) => - SqlDml.FunctionCall("TIMESTAMPDIFF", SqlDml.Native("MICROSECOND"), date1, date2); + protected static SqlUserFunctionCall DateTimeDiffMicrosecond(SqlExpression datetime1, SqlExpression datetime2) => + SqlDml.FunctionCall("TIMESTAMPDIFF", SqlDml.Native("MICROSECOND"), datetime1, datetime2); + + protected static SqlUserFunctionCall DateTimeDiffSecond(SqlExpression datetime1, SqlExpression datetime2) => + SqlDml.FunctionCall("TIMESTAMPDIFF", SqlDml.Native("SECOND"), datetime1, datetime2); + + protected static SqlUserFunctionCall DateTimeAddYear(SqlExpression datetime, SqlExpression years) => + SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("YEAR"), years, datetime); + + protected static SqlUserFunctionCall DateTimeAddMonth(SqlExpression datetime, SqlExpression months) => + SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MONTH"), months, datetime); + + protected static SqlUserFunctionCall DateTimeAddDay(SqlExpression datetime, SqlExpression days) => + SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("DAY"), days, datetime); + + protected static SqlUserFunctionCall DateTimeAddHour(SqlExpression datetime, SqlExpression days) => + SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("HOUR"), days, datetime); - private static SqlUserFunctionCall DateAddYear(SqlExpression date, SqlExpression years) => - SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("YEAR"), years, date); + protected static SqlUserFunctionCall DateTimeAddMicrosecond(SqlExpression datetime, SqlExpression microseconds) => + SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MICROSECOND"), microseconds, datetime); +#if NET6_0_OR_GREATER - private static SqlUserFunctionCall DateAddMonth(SqlExpression date, SqlExpression months) => - SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MONTH"), months, date); + protected static SqlUserFunctionCall DateAddYear(SqlExpression date, SqlExpression years) => + SqlDml.FunctionCall("DATE_ADD", date, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(years, SqlDml.Native("YEAR")))); - private static SqlUserFunctionCall DateAddDay(SqlExpression date, SqlExpression days) => - SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("DAY"), days, date); + protected static SqlUserFunctionCall DateAddMonth(SqlExpression date, SqlExpression months) => + SqlDml.FunctionCall("DATE_ADD", date, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(months, SqlDml.Native("MONTH")))); - private static SqlUserFunctionCall DateAddMicrosecond(SqlExpression date, SqlExpression microseconds) => - SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MICROSECOND"), microseconds, date); + protected static SqlUserFunctionCall DateAddDay(SqlExpression date, SqlExpression days) => + SqlDml.FunctionCall("DATE_ADD", date, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(days, SqlDml.Native("DAY")))); + + protected static SqlUserFunctionCall TimeAddHour(SqlExpression time, SqlExpression hours) => + SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(hours, SqlDml.Native("HOUR")))); + + protected static SqlUserFunctionCall TimeAddMinute(SqlExpression time, SqlExpression minutes) => + SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(minutes, SqlDml.Native("MINUTE")))); + + protected static SqlUserFunctionCall TimeAddSecond(SqlExpression time, SqlExpression seconds) => + SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(seconds, SqlDml.Native("SECOND")))); + + protected static SqlUserFunctionCall TimeAddMillisecond(SqlExpression time, SqlExpression millisecond) => + SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(millisecond * 1000, SqlDml.Native("MICROSECOND")))); + + protected static SqlUserFunctionCall DateToString(SqlExpression dateTime) => + SqlDml.FunctionCall("DATE_FORMAT", dateTime, "%Y-%m-%d"); + + protected static SqlUserFunctionCall TimeToString(SqlExpression dateTime) => + SqlDml.FunctionCall("DATE_FORMAT", dateTime, "%H:%i:%s.%f0"); + + private static SqlExpression DateTimeToDate(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Date); + + private static SqlExpression DateToDateTime(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTime); + + private static SqlExpression DateTimeToTime(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Time); + + // can't convert via cast, because mysql shots to its head and creates + // value that it can't read later. This mimics conversion that occurs + // in newer versions (5.6+) and use current date as a source of year, + // month and day values :-) + private static SqlExpression TimeToDateTime(SqlExpression time) => + SqlDml.FunctionCall("DATE_ADD", + SqlDml.Literal(DateTime.Now.Date), + SqlDml.RawConcat( + SqlDml.RawConcat(SqlDml.Native("INTERVAL "), + SqlDml.FunctionCall("TIME_TO_SEC", time)), + SqlDml.Native("SECOND"))); +#endif protected static SqlUserFunctionCall DateTimeToStringIso(SqlExpression dateTime) => SqlDml.FunctionCall("DATE_FORMAT", dateTime, "%Y-%m-%dT%T"); diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.Queries.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.Queries.cs index 93568e894e..9e3c7e1906 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.Queries.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.Queries.cs @@ -120,6 +120,7 @@ protected string GetExtractForeignKeysQuery() r.table_name, r.constraint_name, r.delete_rule, + r.update_rule, c.column_name, c.ordinal_position, c.referenced_table_schema, diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.cs index 4a4a2aa5ae..5e9415786e 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Extractor.cs @@ -528,26 +528,27 @@ private static void ReadIndexColumnData(DbDataReader reader, ref IndexColumnRead // 1 table_name, // 2 constraint_name, // 3 delete_rule, - // 4 column_name, - // 5 ordinal_position, - // 6 referenced_table_schema, - // 7 referenced_table_name, - // 8 referenced_column_name + // 4 update_rule + // 5 column_name, + // 6 ordinal_position, + // 7 referenced_table_schema, + // 8 referenced_table_name, + // 9 referenced_column_name private static void ReadForeignKeyColumnData(DbDataReader reader, ref ForeignKeyReaderState state) { - var columnIndex = ReadInt(reader, 5); + var columnIndex = ReadInt(reader, 6); if (columnIndex <= state.LastColumnIndex) { var referencingSchema = state.Catalog.Schemas[reader.GetString(0)]; state.ReferencingTable = referencingSchema.Tables[reader.GetString(1)]; state.ForeignKey = state.ReferencingTable.CreateForeignKey(reader.GetString(2)); - ReadCascadeAction(state.ForeignKey, reader, 3); + ReadForeignKeyActions(state.ForeignKey, reader, 3, 4); var referencedSchema = state.Catalog.Schemas[reader.GetString(0)]; //Schema same as current - state.ReferencedTable = referencedSchema.Tables[reader.GetString(7)]; + state.ReferencedTable = referencedSchema.Tables[reader.GetString(8)]; state.ForeignKey.ReferencedTable = state.ReferencedTable; } - var referencingColumn = state.ReferencingTable.TableColumns[reader.GetString(4)]; - var referencedColumn = state.ReferencedTable.TableColumns[reader.GetString(8)]; + var referencingColumn = state.ReferencingTable.TableColumns[reader.GetString(5)]; + var referencedColumn = state.ReferencedTable.TableColumns[reader.GetString(9)]; state.ForeignKey.Columns.Add(referencingColumn); state.ForeignKey.ReferencedColumns.Add(referencedColumn); state.LastColumnIndex = columnIndex; @@ -601,7 +602,7 @@ private SqlValueType CreateValueType(IDataRecord row, int typeNameIndex, int precisionIndex, int scaleIndex, int charLengthIndex) { var typeName = row.GetString(typeNameIndex).ToUpperInvariant(); - var columnName = row.GetString(6).ToUpperInvariant(); + var dataTypeName = row.GetString(6).ToUpperInvariant(); var precision = row.IsDBNull(precisionIndex) ? DefaultPrecision : ReadInt(row, precisionIndex); var scale = row.IsDBNull(scaleIndex) ? DefaultScale : ReadInt(row, scaleIndex); @@ -617,69 +618,84 @@ private SqlValueType CreateValueType(IDataRecord row, return new SqlValueType(SqlType.Double); } - if (columnName == "TINYINT(1)") { + if (dataTypeName.Equals("TINYINT(1)", StringComparison.Ordinal)) { return new SqlValueType(SqlType.Boolean); } - if (typeName.StartsWith("TINYINT")) { + if (typeName.StartsWith("TINYINT", StringComparison.Ordinal)) { // ignoring "day precision" and "second precision" // although they can be read as "scale" and "precision" return new SqlValueType(SqlType.Int8); } - if (typeName.StartsWith("SMALLINT")) { + if (typeName.StartsWith("SMALLINT", StringComparison.Ordinal)) { // ignoring "day precision" and "second precision" // although they can be read as "scale" and "precision" return new SqlValueType(SqlType.Int16); } - if (typeName.StartsWith("MEDIUMINT")) { + if (typeName.StartsWith("MEDIUMINT", StringComparison.Ordinal)) { // There is not 34bit Int in SqlType // ignoring "day precision" and "second precision" // although they can be read as "scale" and "precision" return new SqlValueType(SqlType.Int32); } - if (typeName.StartsWith("INT")) { + if (typeName.StartsWith("INT", StringComparison.Ordinal)) { // ignoring "day precision" and "second precision" // although they can be read as "scale" and "precision" return new SqlValueType(SqlType.Int32); } - if (typeName.StartsWith("BIGINT")) { + if (typeName.StartsWith("BIGINT", StringComparison.Ordinal)) { // ignoring "day precision" and "second precision" // although they can be read as "scale" and "precision" return new SqlValueType(SqlType.Int64); } - - if (typeName.StartsWith("TIME")) { +#if NET6_0_OR_GREATER + if (typeName.Equals("TIME", StringComparison.Ordinal) || typeName.StartsWith("TIME(")) { + return new SqlValueType(SqlType.Time); + } + else if (typeName.StartsWith("TIME", StringComparison.Ordinal)) { // "timestamp precision" is saved as "scale", ignoring too return new SqlValueType(SqlType.DateTime); } - if (typeName.StartsWith("YEAR")) { +#else + if (typeName.StartsWith("TIME", StringComparison.Ordinal)) { + // "timestamp precision" is saved as "scale", ignoring too + return new SqlValueType(SqlType.DateTime); + } +#endif + if (typeName.StartsWith("YEAR", StringComparison.Ordinal)) { // "timestamp precision" is saved as "scale", ignoring too return new SqlValueType(SqlType.Decimal, 4, 0); } - if (typeName=="LONGTEXT") { + if (typeName.Equals("LONGTEXT", StringComparison.Ordinal)) { return new SqlValueType(SqlType.VarCharMax); } - if (typeName.Contains("TEXT")) { + if (typeName.Contains("TEXT", StringComparison.Ordinal)) { var length = ReadInt(row, charLengthIndex); return new SqlValueType(SqlType.VarCharMax, length); } - if (typeName.Contains("BLOB")) { + if (typeName.Contains("BLOB", StringComparison.Ordinal)) { return new SqlValueType(SqlType.VarBinaryMax); } - if (typeName == "VARBINARY") { + if (typeName.Equals("BINARY", StringComparison.Ordinal)) { + var length = ReadInt(row, charLengthIndex); + return new SqlValueType(SqlType.Binary, length); + } + if (typeName.Equals("VARBINARY", StringComparison.Ordinal)) { var length = ReadInt(row, charLengthIndex); + return new SqlValueType(SqlType.VarBinary, length); } - if (typeName == "VARCHAR" || typeName == "CHAR") { + if (typeName.Equals("VARCHAR", StringComparison.Ordinal) + || typeName.Equals("CHAR", StringComparison.Ordinal)) { var length = Convert.ToInt32(row[charLengthIndex]); var sqlType = typeName.Length==4 ? SqlType.Char : SqlType.VarChar; return new SqlValueType(sqlType, length); @@ -732,19 +748,25 @@ private static int ReadInt(IDataRecord row, int index) private static string ReadStringOrNull(IDataRecord row, int index) => row.IsDBNull(index) ? null : row.GetString(index); - private static void ReadCascadeAction(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex) + private static void ReadForeignKeyActions(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex, int updateRuleIndex) { var deleteRule = row.GetString(deleteRuleIndex); - switch (deleteRule) { - case "CASCADE": - foreignKey.OnDelete = ReferentialAction.Cascade; - return; - case "SET NULL": - foreignKey.OnDelete = ReferentialAction.SetNull; - return; - case "NO ACTION": - foreignKey.OnDelete = ReferentialAction.NoAction; - return; + foreignKey.OnDelete = GetEnumAction(deleteRule); + + var updateRule = row.GetString(updateRuleIndex); + foreignKey.OnUpdate = GetEnumAction(updateRule); + + + static ReferentialAction GetEnumAction(in string rawActionName) + { + return rawActionName switch { + "CASCADE" => ReferentialAction.Cascade, + "SET NULL" => ReferentialAction.SetNull, + "NO ACTION" => ReferentialAction.NoAction, + "RESTRICT" => ReferentialAction.NoAction, + "SET DEFAULT" => ReferentialAction.SetDefault, + _ => throw new ArgumentOutOfRangeException() + }; } } diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/ServiceInfoProvider.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/ServiceInfoProvider.cs index 487dea55dc..349ab79a14 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/ServiceInfoProvider.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/ServiceInfoProvider.cs @@ -261,9 +261,19 @@ public override DataTypeCollection GetDataTypesInfo() types.Double = DataTypeInfo.Range(SqlType.Double, common | index, ValueRange.Double, "double precision"); +#if NET6_0_OR_GREATER types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, + new ValueRange(new DateTime(1000, 1, 1), new DateTime(9999, 12, 31)), + "datetime"); + types.DateOnly = DataTypeInfo.Range(SqlType.Date, common | index, + new ValueRange(new DateOnly(1000, 1, 1), new DateOnly(9999, 12, 31)), + "date"); + types.TimeOnly = DataTypeInfo.Range(SqlType.Time, common | index, ValueRange.TimeOnly, "time"); +#else + types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, new ValueRange(new DateTime(1000, 1, 1), new DateTime(9999, 12, 31)), "datetime", "time"); +#endif types.Char = DataTypeInfo.Stream(SqlType.Char, common | index, 255, "char"); types.VarChar = DataTypeInfo.Stream(SqlType.VarChar, common | index, 4000, "varchar"); diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs index c2576d2dd1..ddd294a19d 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2022 Xtensive LLC. +// Copyright (C) 2011-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: Malisa Ncube @@ -22,6 +22,12 @@ namespace Xtensive.Sql.Drivers.MySql.v5_0 internal class Translator : SqlTranslator { public override string DateTimeFormatString => @"\'yyyy\-MM\-dd HH\:mm\:ss\.ffffff\'"; +#if NET6_0_OR_GREATER + + public override string DateOnlyFormatString => @"\'yyyy\-MM\-dd\'"; + + public override string TimeOnlyFormatString => @"\'HH\:mm\:ss\.ffffff\'"; +#endif public override string TimeSpanFormatString => string.Empty; @@ -106,6 +112,13 @@ public override void Translate(IOutput output, SqlFunctionType type) case SqlFunctionType.DateTimeAddYears: case SqlFunctionType.DateTimeAddMonths: case SqlFunctionType.DateTimeConstruct: +#if NET6_0_OR_GREATER + case SqlFunctionType.DateAddYears: + case SqlFunctionType.DateAddMonths: + case SqlFunctionType.DateAddDays: + case SqlFunctionType.DateConstruct: + case SqlFunctionType.TimeConstruct: +#endif case SqlFunctionType.IntervalToMilliseconds: return; //string @@ -158,10 +171,17 @@ public override void Translate(IOutput output, SqlNodeType type) { switch (type) { case SqlNodeType.Concat: _ = output.Append(","); break; - case SqlNodeType.DateTimePlusInterval: _ = output.Append("+"); + case SqlNodeType.DateTimePlusInterval: +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: +#endif + _ = output.Append("+"); break; case SqlNodeType.DateTimeMinusInterval: case SqlNodeType.DateTimeMinusDateTime: +#if NET6_0_OR_GREATER + case SqlNodeType.TimeMinusTime: +#endif _ = output.Append("-"); break; case SqlNodeType.Equals: _ = output.Append("="); break; case SqlNodeType.NotEquals: _ = output.Append("<>"); break; @@ -403,9 +423,8 @@ public override void Translate(SqlCompilerContext context, object literalValue) /// public override void Translate(SqlCompilerContext context, SqlExtract node, ExtractSection section) { - var isSecond = node.DateTimePart == SqlDateTimePart.Second || node.IntervalPart == SqlIntervalPart.Second; - var isMillisecond = node.DateTimePart == SqlDateTimePart.Millisecond - || node.IntervalPart == SqlIntervalPart.Millisecond; + var isSecond = node.IsSecondExtraction; + var isMillisecond = node.IsMillisecondExtraction; if (!(isSecond || isMillisecond)) { base.Translate(context, node, section); return; @@ -415,7 +434,7 @@ public override void Translate(SqlCompilerContext context, SqlExtract node, Extr _ = context.Output.AppendOpeningPunctuation("(extract("); break; case ExtractSection.Exit: - _ = context.Output.Append(isMillisecond ? ") % 1000)" : "))"); + _ = context.Output.Append(isMillisecond ? ") / 1000)" : "))"); break; default: base.Translate(context, node, section); @@ -510,6 +529,30 @@ public override void Translate(IOutput output, SqlDateTimePart dateTimePart) default: base.Translate(output, dateTimePart); break; } } +#if NET6_0_OR_GREATER + + /// + public override void Translate(IOutput output, SqlDatePart datePart) + { + switch (datePart) { + case SqlDatePart.Day: _ = output.Append("DAY"); break; + case SqlDatePart.Year: _ = output.Append("YEAR"); break; + case SqlDatePart.Month: _ = output.Append("MONTH"); break; + default: base.Translate(output, datePart); break; + } + } + + /// + public override void Translate(IOutput output, SqlTimePart dateTimePart) + { + switch (dateTimePart) { + case SqlTimePart.Millisecond: _ = output.Append("MICROSECOND"); break; + case SqlTimePart.Hour: _ = output.Append("HOUR"); break; + case SqlTimePart.Minute: _ = output.Append("MINUTE"); break; + default: base.Translate(output, dateTimePart); break; + } + } +#endif /// public override void Translate(IOutput output, SqlLockType lockType) diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/TypeMapper.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/TypeMapper.cs index a3550e672e..699acb9282 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2021 Xtensive LLC. +// Copyright (C) 2011-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: Malisa Ncube @@ -14,7 +14,11 @@ namespace Xtensive.Sql.Drivers.MySql.v5_0 { internal class TypeMapper : Sql.TypeMapper { +#if NET6_0_OR_GREATER + private static readonly Type[] CastRequiredTypes = new[] { typeof(Guid), typeof(TimeSpan), typeof(byte[]), typeof (DateOnly), typeof(TimeOnly) }; +#else private static readonly Type[] CastRequiredTypes = new[] { typeof(Guid), typeof(TimeSpan), typeof(byte[]) }; +#endif /// public override bool IsParameterCastRequired(Type type) @@ -62,6 +66,20 @@ public override void BindGuid(DbParameter parameter, object value) parameter.DbType = DbType.String; parameter.Value = value==null ? (object) DBNull.Value : SqlHelper.GuidToString((Guid) value); } +#if NET6_0_OR_GREATER + + public override void BindDateOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Date; + parameter.Value = value ?? DBNull.Value; + } + + public override void BindTimeOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Time; + parameter.Value = value ?? DBNull.Value; + } +#endif /// public override SqlValueType MapByte(int? length, int? precision, int? scale) 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 e35b3aebeb..debe6cd67f 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 @@ -1,13 +1,75 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2013-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: Alena Mikshina // Created: 2013.12.30 +using System; +using Xtensive.Sql.Dml; + namespace Xtensive.Sql.Drivers.MySql.v5_6 { internal class Compiler : v5_5.Compiler { +#if NET6_0_OR_GREATER + public override void Visit(SqlBinary node) + { + if (node.NodeType == SqlNodeType.TimePlusInterval) { + SqlDml.FunctionCall("TIME", TimeAddInterval(SqlDml.Cast(node.Left, SqlType.DateTime), node.Right)).AcceptVisitor(this); + } + else { + base.Visit(node); + } + } + + public override void Visit(SqlFunctionCall node) + { + var arguments = node.Arguments; + switch (node.FunctionType) { + case SqlFunctionType.TimeAddHours: + Visit( + SqlDml.FunctionCall("TIME", + SqlDml.FunctionCall("ADDTIME", + SqlDml.Cast(arguments[0], SqlType.DateTime), + arguments[1] * 10000))); // 10000 = 1:00:00 + return; + case SqlFunctionType.TimeAddMinutes: + Visit( + SqlDml.FunctionCall("TIME", + SqlDml.FunctionCall("ADDTIME", + 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; + default: + base.Visit(node); + return; + } + } + + protected override SqlExpression 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) => + 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 public Compiler(SqlDriver driver) diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/ServerInfoProvider.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/ServerInfoProvider.cs index e8586c20c8..be2cfd25ef 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/ServerInfoProvider.cs @@ -1,13 +1,32 @@ -// Copyright (C) 2003-2010 Xtensive LLC. +// Copyright (C) 2003-2010 Xtensive LLC. // All rights reserved. // For conditions of distribution and use, see license. // Created by: Alena Mikshina // Created: 2013.12.30 +using Xtensive.Sql.Info; + namespace Xtensive.Sql.Drivers.MySql.v5_6 { internal class ServerInfoProvider : v5_5.ServerInfoProvider { +#if NET6_0_OR_GREATER + /// + public override DataTypeCollection GetDataTypesInfo() + { + var types = base.GetDataTypesInfo(); + + var common = DataTypeFeatures.Default | DataTypeFeatures.Nullable | DataTypeFeatures.NonKeyIndexing | + DataTypeFeatures.Grouping | DataTypeFeatures.Ordering | DataTypeFeatures.Multiple; + + var index = DataTypeFeatures.Indexing | DataTypeFeatures.KeyConstraint; + + types.TimeOnly = DataTypeInfo.Range(SqlType.Time, common | index, ValueRange.TimeOnly, "time(6)"); + + return types; + } +#endif + // Constructors public ServerInfoProvider(SqlDriver driver) diff --git a/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj b/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj index 30a4faa26c..89d9852d80 100644 --- a/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj +++ b/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj @@ -30,8 +30,8 @@ - - + + 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 6f8a73d14e..168462fb70 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Compiler.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Compiler.cs @@ -1,6 +1,6 @@ -// 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 @@ -11,72 +11,157 @@ using Xtensive.Sql.Dml; using Xtensive.Sql.Model; using Xtensive.Sql.Drivers.Oracle.Resources; -using System.Linq; namespace Xtensive.Sql.Drivers.Oracle.v09 { internal class Compiler : SqlCompiler { + protected const string DayPartFormat = "DD"; + protected const string HourPartFormat = "HH24"; + protected const string MillisecondPartFormat = "FF3"; + protected const string NanosecondPartFormat = "FF9"; + protected const string MinutePartFormat = "MI"; + protected const string MonthPartFormat = "MM"; + protected const string SecondPartFormat = "SS"; + protected const string YearPartFormat = "YYYY"; + protected const string TimeZoneHourPartFormat = "TZH"; + protected const string TimeZoneMinutePartFormat = "TZM"; + protected const string DayOfYearPartFormat = "DDD"; + protected const string DayOfWeekPartFormat = "D"; + + protected const string YearIntervalPart = "YEAR"; + protected const string MonthIntervalPart = "MONTH"; + protected const string DayIntervalPart = "DAY"; + protected const string HourIntervalPart = "HOUR"; + protected const string MinuteIntervalPart = "MINUTE"; + protected const string SecondIntervalPart = "SECOND"; + + protected const string ToCharFunctionName = "TO_CHAR"; + protected const string NumToDSIntervalFunctionName = "NUMTODSINTERVAL"; + protected const string NumToYMIntervalFunctionName = "NUMTOYMINTERVAL"; + + protected const string ToDSIntervalFunctionName = "TO_DSINTERVAL"; + protected const string TimeFormat = "HH24:MI:SS.FF7"; + 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) { switch (node.FunctionType) { - case SqlFunctionType.PadLeft: - case SqlFunctionType.PadRight: - SqlHelper.GenericPad(node).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeOffsetAddYears: - case SqlFunctionType.DateTimeAddYears: - DateTimeAddComponent(node.Arguments[0], node.Arguments[1], true).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeOffsetAddMonths: - case SqlFunctionType.DateTimeAddMonths: - DateTimeAddComponent(node.Arguments[0], node.Arguments[1], false).AcceptVisitor(this); - return; - case SqlFunctionType.IntervalConstruct: - IntervalConstruct(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeConstruct: - DateTimeConstruct(node.Arguments[0], node.Arguments[1], node.Arguments[2]).AcceptVisitor(this); - return; - case SqlFunctionType.IntervalAbs: - SqlHelper.IntervalAbs(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.IntervalToMilliseconds: - SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.IntervalToNanoseconds: - SqlHelper.IntervalToNanoseconds(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.Position: - Position(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); - return; - case SqlFunctionType.CharLength: - SqlDml.Coalesce(SqlDml.FunctionCall("LENGTH", node.Arguments[0]), 0).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeToStringIso: - DateTimeToStringIso(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeOffsetConstruct: - DateTimeOffsetConstruct(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeOffsetTimeOfDay: - DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeOffsetToLocalTime: - DateTimeOffsetToLocalTime(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeToDateTimeOffset: - DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); - return; - case SqlFunctionType.DateTimeOffsetToUtcTime: - DateTimeOffsetToUtcTime(node.Arguments[0]).AcceptVisitor(this); - return; - default: - base.Visit(node); - return; + case SqlFunctionType.PadLeft: + case SqlFunctionType.PadRight: + SqlHelper.GenericPad(node).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetAddYears: + case SqlFunctionType.DateTimeAddYears: + DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], YearIntervalPart).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetAddMonths: + case SqlFunctionType.DateTimeAddMonths: + DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], MonthIntervalPart).AcceptVisitor(this); + return; + case SqlFunctionType.IntervalConstruct: + IntervalConstruct(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeConstruct: + DateTimeConstruct(node.Arguments[0], node.Arguments[1], node.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); + return; + case SqlFunctionType.TimeConstruct: + TimeConstruct(node.Arguments[0], node.Arguments[1], node.Arguments[2], node.Arguments[3]).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddYears: + DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], YearIntervalPart).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddMonths: + DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], MonthIntervalPart).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddDays: + DateTimeAddDSInterval(node.Arguments[0], node.Arguments[1], DayIntervalPart).AcceptVisitor(this); + return; + case SqlFunctionType.TimeAddHours: + TimeAddHourOrMinute(node.Arguments[0], node.Arguments[1], true).AcceptVisitor(this); + return; + case SqlFunctionType.TimeAddMinutes: + TimeAddHourOrMinute(node.Arguments[0], node.Arguments[1], false).AcceptVisitor(this); + return; + case SqlFunctionType.DateToString: + DateToString(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToString: + TimeToString(node.Arguments[0]).AcceptVisitor(this); + return; +#endif + case SqlFunctionType.IntervalAbs: + SqlHelper.IntervalAbs(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.IntervalToMilliseconds: + SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.IntervalToNanoseconds: + SqlHelper.IntervalToNanoseconds(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.Position: + Position(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.CharLength: + SqlDml.Coalesce(SqlDml.FunctionCall("LENGTH", node.Arguments[0]), 0).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToStringIso: + DateTimeToStringIso(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetConstruct: + DateTimeOffsetConstruct(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetTimeOfDay: + DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToLocalTime: + DateTimeOffsetToLocalTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToDateTimeOffset: + DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDateTime: + DateTimeOffsetToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToUtcTime: + DateTimeOffsetToUtcTime(node.Arguments[0]).AcceptVisitor(this); + return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateTimeToDate: + DateTimeToDate(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTime: + DateToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToTime: + DateTimeToTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTime: + TimeToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDate: + DateTimeOffsetToDate(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToTime: + DateTimeOffsetToTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTimeOffset: + DateToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTimeOffset: + TimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; +#endif + default: + base.Visit(node); + return; } } @@ -99,69 +184,77 @@ public override void Visit(SqlTrim node) public override void Visit(SqlExtract node) { switch (node.DateTimeOffsetPart) { - case SqlDateTimeOffsetPart.Day: - DateTimeOffsetExtractPart(node.Operand, "DD").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Hour: - DateTimeOffsetExtractPart(node.Operand, "HH24").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Millisecond: - DateTimeOffsetExtractPart(node.Operand, "FF3").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Nanosecond: - DateTimeOffsetExtractPart(node.Operand, "FF9").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Minute: - DateTimeOffsetExtractPart(node.Operand, "MI").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Month: - DateTimeOffsetExtractPart(node.Operand, "MM").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Second: - DateTimeOffsetExtractPart(node.Operand, "SS").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Year: - DateTimeOffsetExtractPart(node.Operand, "YYYY").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.TimeZoneHour: - DateTimeOffsetExtractPart(node.Operand, "TZH").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.TimeZoneMinute: - DateTimeOffsetExtractPart(node.Operand, "TZM").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.DayOfWeek: - DateTimeExtractDayOfWeek(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.DayOfYear: - DateTimeOffsetExtractPart(node.Operand, "DDD").AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Date: - DateTimeOffsetTruncate(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.DateTime: - DateTimeOffsetTruncateOffset(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.LocalDateTime: - DateTimeOffsetToLocalDateTime(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.UtcDateTime: - DateTimeOffsetToUtcDateTime(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Offset: - DateTimeOffsetPartOffset(node.Operand).AcceptVisitor(this); - return; + case SqlDateTimeOffsetPart.Day: + DateTimeOffsetExtractPart(node.Operand, DayPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Hour: + DateTimeOffsetExtractPart(node.Operand, HourPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Millisecond: + DateTimeOffsetExtractPart(node.Operand, MillisecondPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Nanosecond: + DateTimeOffsetExtractPart(node.Operand, NanosecondPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Minute: + DateTimeOffsetExtractPart(node.Operand, MinutePartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Month: + DateTimeOffsetExtractPart(node.Operand, MonthPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Second: + DateTimeOffsetExtractPart(node.Operand, SecondPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Year: + DateTimeOffsetExtractPart(node.Operand, YearPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.TimeZoneHour: + DateTimeOffsetExtractPart(node.Operand, TimeZoneHourPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.TimeZoneMinute: + DateTimeOffsetExtractPart(node.Operand, TimeZoneMinutePartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.DayOfWeek: + DateTimeExtractDayOfWeek(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.DayOfYear: + DateTimeOffsetExtractPart(node.Operand, DayOfYearPartFormat).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Date: + DateTimeOffsetTruncate(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.DateTime: + DateTimeOffsetTruncateOffset(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.LocalDateTime: + DateTimeOffsetToLocalDateTime(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.UtcDateTime: + DateTimeOffsetToUtcDateTime(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Offset: + DateTimeOffsetPartOffset(node.Operand).AcceptVisitor(this); + return; } switch (node.DateTimePart) { - case SqlDateTimePart.DayOfYear: - DateTimeExtractDayOfYear(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimePart.DayOfWeek: - DateTimeExtractDayOfWeek(node.Operand).AcceptVisitor(this); - return; - default: - base.Visit(node); - return; + case SqlDateTimePart.DayOfYear: + DateTimeExtractDayOfYear(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimePart.DayOfWeek: + DateTimeExtractDayOfWeek(node.Operand).AcceptVisitor(this); + return; } +#if NET6_0_OR_GREATER + switch (node.DatePart) { + case SqlDatePart.DayOfYear: + DateTimeExtractDayOfYear(node.Operand).AcceptVisitor(this); + return; + case SqlDatePart.DayOfWeek: + DateTimeExtractDayOfWeek(node.Operand).AcceptVisitor(this); + return; + } +#endif + base.Visit(node); } protected override void VisitSelectFrom(SqlSelect node) @@ -232,155 +325,210 @@ public override void Visit(SqlUnary node) public override void Visit(SqlBinary node) { switch (node.NodeType) { - case SqlNodeType.Modulo: - SqlDml.FunctionCall("MOD", node.Left, node.Right).AcceptVisitor(this); - return; - case SqlNodeType.BitAnd: - BitAnd(node.Left, node.Right).AcceptVisitor(this); - return; - case SqlNodeType.BitOr: - BitOr(node.Left, node.Right).AcceptVisitor(this); - return; - case SqlNodeType.BitXor: - BitXor(node.Left, node.Right).AcceptVisitor(this); - return; - default: - base.Visit(node); - return; + case SqlNodeType.Modulo: + SqlDml.FunctionCall("MOD", node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.BitAnd: + BitAnd(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.BitOr: + BitOr(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.BitXor: + BitXor(node.Left, node.Right).AcceptVisitor(this); + return; +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: + TimeAddInterval(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.TimeMinusTime: + TimeAddInterval(node.Left, node.Right, true).AcceptVisitor(this); + return; +#endif + default: + base.Visit(node); + return; } } - private static SqlExpression DateTimeAddComponent(SqlExpression dateTime, SqlExpression units, bool isYear) - { - return dateTime + SqlDml.FunctionCall( - "NumToYmInterval", units, AnsiString(isYear ? "year" : "month")); - } + private static SqlExpression DateTimeAddYMInterval(SqlExpression dateTime, SqlExpression units, in string component) => + dateTime + SqlDml.FunctionCall(NumToYMIntervalFunctionName, units, AnsiString(component)); + + private static SqlExpression DateTimeAddDSInterval(SqlExpression dateTime, SqlExpression units, in string component) => + dateTime + SqlDml.FunctionCall(NumToDSIntervalFunctionName, units, AnsiString(component)); private static SqlExpression IntervalConstruct(SqlExpression nanoseconds) { const long nanosecondsPerSecond = 1000000000; - return SqlDml.FunctionCall("NumToDsInterval", - nanoseconds / SqlDml.Literal(nanosecondsPerSecond), AnsiString("second")); + return SqlDml.FunctionCall(NumToDSIntervalFunctionName, + nanoseconds / SqlDml.Literal(nanosecondsPerSecond), AnsiString(SecondIntervalPart)); } - private static SqlExpression DateTimeConstruct(SqlExpression years, SqlExpression months, SqlExpression days) - { - return SqlDml.FunctionCall("TO_TIMESTAMP", - SqlDml.FunctionCall("TO_CHAR", ((years * 100) + months) * 100 + days), + private static SqlExpression DateTimeConstruct(SqlExpression years, SqlExpression months, SqlExpression days) => + SqlDml.FunctionCall("TO_TIMESTAMP", + SqlDml.FunctionCall(ToCharFunctionName, ((years * 100) + months) * 100 + days), AnsiString("YYYYMMDD")); +#if NET6_0_OR_GREATER + + private static SqlExpression DateConstruct(SqlExpression years, SqlExpression months, SqlExpression days) => + SqlDml.FunctionCall("TO_DATE", + SqlDml.FunctionCall(ToCharFunctionName, ((years * 100) + months) * 100 + days), + AnsiString("YYYYMMDD")); + + private static SqlExpression TimeAddHourOrMinute(SqlExpression time, SqlExpression hourOrMinute, bool isHour) + { + var intervalLiteral = isHour ? "INTERVAL '1' HOUR" : "INTERVAL '1' MINUTE"; + return TimeAddInterval(time, hourOrMinute * SqlDml.Native(intervalLiteral)); + } + + private static SqlExpression TimeAddInterval(SqlExpression time, SqlExpression intervalToAdd, bool negateInterval = false) + { + var baseOp = (negateInterval) ? RefTimestamp + time - intervalToAdd + : RefTimestamp + time + intervalToAdd; + var getTimeOnly = SqlDml.FunctionCall(ToCharFunctionName, baseOp, AnsiString(TimeFormat)); + var pretendZeroDays = (SqlExpression) SqlDml.Concat(AnsiString("0 "), getTimeOnly); + var dsInterval = SqlDml.FunctionCall(ToDSIntervalFunctionName, pretendZeroDays); + var castToCorrectInterval = SqlDml.Cast(dsInterval, SqlType.Time); + 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"); + + private static SqlExpression TimeToString(SqlExpression time) => + SqlDml.FunctionCall(ToCharFunctionName, RefTimestamp + time, AnsiString("HH24:MI:SS.FF7")); +#endif + private static SqlExpression DateTimeExtractDayOfWeek(SqlExpression dateTime) { // TO_CHAR with 'D' returns values depending on NLS_TERRITORY setting, // so sunday can be 1 or 7 // there is no equivalent for sqlserver's @@DATEFIRST function // so we need to emulate it with very stupid code - return (SqlDml.FunctionCall("TO_NUMBER", SqlDml.FunctionCall("TO_CHAR", dateTime, AnsiString("D"))) + 7 - SundayNumber) % 7; + return (SqlDml.FunctionCall("TO_NUMBER", SqlDml.FunctionCall(ToCharFunctionName, dateTime, AnsiString(DayOfWeekPartFormat))) + 7 - SundayNumber) % 7; } - private static SqlExpression DateTimeExtractDayOfYear(SqlExpression dateTime) - { - return SqlDml.FunctionCall("TO_NUMBER", SqlDml.FunctionCall("TO_CHAR", dateTime, AnsiString("DDD"))); - } + private static SqlExpression DateTimeExtractDayOfYear(SqlExpression dateTime) => + SqlDml.FunctionCall("TO_NUMBER", SqlDml.FunctionCall(ToCharFunctionName, dateTime, AnsiString(DayOfYearPartFormat))); - private static SqlExpression BitAnd(SqlExpression left, SqlExpression right) - { - return SqlDml.FunctionCall("BITAND", left, right); - } + private static SqlExpression BitAnd(SqlExpression left, SqlExpression right) => + SqlDml.FunctionCall("BITAND", left, right); - private static SqlExpression BitOr(SqlExpression left, SqlExpression right) - { - return left + right - BitAnd(left, right); - } + private static SqlExpression BitOr(SqlExpression left, SqlExpression right) => + left + right - BitAnd(left, right); - private static SqlExpression BitXor(SqlExpression left, SqlExpression right) - { - return BitOr(left, right) - BitAnd(left, right); - } + private static SqlExpression BitXor(SqlExpression left, SqlExpression right) => + BitOr(left, right) - BitAnd(left, right); - private static SqlExpression BitNot(SqlExpression operand) - { - return -1 - operand; - } + private static SqlExpression BitNot(SqlExpression operand) => -1 - operand; - private static SqlExpression Position(SqlExpression substring, SqlExpression _string) //TODO : look into this (Malisa) - { - return SqlDml.FunctionCall("INSTR", _string, substring) - 1; - } + private static SqlExpression Position(SqlExpression substring, SqlExpression _string) => //TODO : look into this (Malisa) + SqlDml.FunctionCall("INSTR", _string, substring) - 1; - private static SqlExpression DateTimeToStringIso(SqlExpression dateTime) - { - return SqlDml.FunctionCall("To_Char", dateTime, "YYYY-MM-DD\"T\"HH24:MI:SS"); - } + private static SqlExpression DateTimeToStringIso(SqlExpression dateTime) => + SqlDml.FunctionCall(ToCharFunctionName, dateTime, "YYYY-MM-DD\"T\"HH24:MI:SS"); private static SqlExpression DateTimeOffsetConstruct(SqlExpression dateTime, SqlExpression offset) { - var offsetToInt = offset as SqlLiteral; + if (offset is not SqlLiteral intOffset) { + throw new InvalidOperationException(); + } + var offsetValue = intOffset.Value; return SqlDml.FunctionCall("FROM_TZ", dateTime, - AnsiString($"{((offsetToInt.Value < 0) ? "-" : "+")}{offsetToInt.Value / 60}:{offsetToInt.Value % 60}") + AnsiString($"{((offsetValue < 0) ? "-" : "+")}{offsetValue / 60}:{offsetValue % 60}") ); } - private static SqlExpression DateTimeOffsetPartOffset(SqlExpression dateTimeOffset) - { - return SqlDml.Cast(dateTimeOffset, SqlType.DateTime) + private static SqlExpression DateTimeOffsetPartOffset(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.DateTime) - SqlDml.Cast(DateTimeOffsetToUtcDateTime(dateTimeOffset), SqlType.DateTime); - } - private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) - { - return SqlDml.Cast(dateTimeOffset, SqlType.DateTime) + private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.DateTime) - SqlDml.Truncate(SqlDml.Cast(dateTimeOffset, SqlType.DateTime)); - } - private static SqlExpression DateTimeOffsetTruncateOffset(SqlExpression dateTimeOffset) - { - return SqlDml.Cast(dateTimeOffset, SqlType.DateTime); - } + private static SqlExpression DateTimeOffsetTruncateOffset(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.DateTime); - private static SqlExpression DateTimeOffsetTruncate(SqlExpression dateTimeOffset) - { - return SqlDml.Truncate(dateTimeOffset); - } + private static SqlExpression DateTimeOffsetTruncate(SqlExpression dateTimeOffset) => + SqlDml.Truncate(dateTimeOffset); - private static SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression dateTimeOffset) - { - return SqlDml.FunctionCall("SYS_EXTRACT_UTC", dateTimeOffset); - } + private static SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression dateTimeOffset) => + SqlDml.FunctionCall("SYS_EXTRACT_UTC", dateTimeOffset); - private static SqlExpression DateTimeOffsetExtractPart(SqlExpression dateTimeOffset, string dateTimeOffsetPart) - { - return SqlDml.FunctionCall("TO_CHAR", dateTimeOffset, AnsiString(dateTimeOffsetPart)); - } + private static SqlExpression DateTimeOffsetExtractPart(SqlExpression dateTimeOffset, string dateTimeOffsetPart) => + SqlDml.FunctionCall(ToCharFunctionName, dateTimeOffset, AnsiString(dateTimeOffsetPart)); - private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTimeOffset) - { - return SqlDml.Cast(DateTimeOffsetToLocalTime(dateTimeOffset), SqlType.DateTime); - } + private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTimeOffset) => + SqlDml.Cast(DateTimeOffsetToLocalTime(dateTimeOffset), SqlType.DateTime); + private static SqlExpression DateTimeOffsetToLocalTime(SqlExpression dateTimeOffset) => + SqlDml.RawConcat(dateTimeOffset, SqlDml.Native(" AT LOCAL")); - private static SqlExpression DateTimeOffsetToLocalTime(SqlExpression dateTimeOffset) - { - return SqlDml.RawConcat(dateTimeOffset, SqlDml.Native(" AT LOCAL")); - } + private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.DateTimeOffset); - private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) - { - return SqlDml.Cast(dateTime, SqlType.DateTimeOffset); - } + private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.DateTime); - private static SqlExpression DateTimeOffsetToUtcTime(SqlExpression dateTimeOffset) - { - return SqlDml.RawConcat(dateTimeOffset, SqlDml.Native(" at time zone 'UTC'")); - } + private static SqlExpression DateTimeOffsetToUtcTime(SqlExpression dateTimeOffset) => + SqlDml.RawConcat(dateTimeOffset, SqlDml.Native(" at time zone 'UTC'")); - private static SqlExpression AnsiString(string value) - { - return SqlDml.Native("'" + value + "'"); - } + private static SqlExpression DateToDateTimeOffset(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTimeOffset); +#if NET6_0_OR_GREATER + + private static SqlExpression DateTimeToDate(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Date); + + private static SqlExpression DateToDateTime(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTime); + + private static SqlExpression DateTimeToTime(SqlExpression dateTime) => + SqlDml.Cast(SqlDml.FunctionCall(ToDSIntervalFunctionName, + (SqlExpression) SqlDml.Concat( + AnsiString("0 "), + SqlDml.FunctionCall(ToCharFunctionName, + dateTime, + AnsiString(TimeFormat) + ) + ) + ), SqlType.Time); + + private static SqlExpression TimeToDateTime(SqlExpression time) => + SqlDml.Cast( + SqlDml.Literal(DateTime.MinValue) + time, + SqlType.DateTime); + + private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.Date); + + private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset) => + SqlDml.Cast(SqlDml.FunctionCall(ToDSIntervalFunctionName, + (SqlExpression)SqlDml.Concat( + AnsiString("0 "), + SqlDml.FunctionCall(ToCharFunctionName, + dateTimeOffset, + AnsiString(TimeFormat) + ) + ) + ), SqlType.Time); + + private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => + SqlDml.Cast( + SqlDml.Literal(DateTime.MinValue) + time, + SqlType.DateTimeOffset); +#endif + + private static SqlExpression AnsiString(string value) => SqlDml.Native("'" + value + "'"); // Constructors diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs index e456e72f30..3f2d77d819 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs @@ -101,8 +101,12 @@ public ExtractionContext(Catalog catalog, Dictionary replacement } } - private const int DefaultPrecision = 38; - private const int DefaultScale = 0; + private const int DefaultDecimalPrecision = 38; + private const int DefaultDecimalScale = 0; +#if NET6_0_OR_GREATER + private const int DefaultDayPrecision = 2; + private const int DefaultFSecondsPrecision = 6; +#endif private readonly object accessGuard = new object(); @@ -494,7 +498,7 @@ private static void ReadForeignKeyColumnData(DbDataReader reader, ref ForeignKey state.ReferencingTable = referencingSchema.Tables[reader.GetString(1)]; state.ForeignKey = state.ReferencingTable.CreateForeignKey(reader.GetString(2)); ReadConstraintProperties(state.ForeignKey, reader, 3, 4); - ReadCascadeAction(state.ForeignKey, reader, 5); + ReadForeignKeyDeleteAction(state.ForeignKey, reader, 5); var referencedSchema = state.Catalog.Schemas[reader.GetString(8)]; state.ReferencedTable = referencedSchema.Tables[reader.GetString(9)]; state.ForeignKey.ReferencedTable = state.ReferencedTable; @@ -563,25 +567,37 @@ private SqlValueType CreateValueType(IDataRecord row, int typeNameIndex, int precisionIndex, int scaleIndex, int charLengthIndex) { var typeName = row.GetString(typeNameIndex); - if (typeName == "NUMBER") { - var precision = row.IsDBNull(precisionIndex) ? DefaultPrecision : ReadInt(row, precisionIndex); - var scale = row.IsDBNull(scaleIndex) ? DefaultScale : ReadInt(row, scaleIndex); + if (string.Equals(typeName, "NUMBER", StringComparison.OrdinalIgnoreCase)) { + var precision = row.IsDBNull(precisionIndex) ? DefaultDecimalPrecision : ReadInt(row, precisionIndex); + var scale = row.IsDBNull(scaleIndex) ? DefaultDecimalScale : ReadInt(row, scaleIndex); return new SqlValueType(SqlType.Decimal, precision, scale); } - if (typeName.StartsWith("INTERVAL DAY")) { +#if NET6_0_OR_GREATER + if (typeName.StartsWith("INTERVAL DAY", StringComparison.OrdinalIgnoreCase)) { + var dayPrecision = row.IsDBNull(precisionIndex) ? DefaultDayPrecision : ReadInt(row, precisionIndex); + var fSecondsPrecision = row.IsDBNull(scaleIndex) ? DefaultFSecondsPrecision : ReadInt(row, scaleIndex); + + return (dayPrecision == 0) + ? new SqlValueType(SqlType.Time) + : new SqlValueType(SqlType.Interval); + } +#else + if (typeName.StartsWith("INTERVAL DAY", StringComparison.OrdinalIgnoreCase)) { // ignoring "day precision" and "second precision" // although they can be read as "scale" and "precision" return new SqlValueType(SqlType.Interval); } - if (typeName.StartsWith("TIMESTAMP")) { +#endif + if (typeName.StartsWith("TIMESTAMP", StringComparison.OrdinalIgnoreCase)) { // "timestamp precision" is saved as "scale", ignoring too - if (typeName.Contains("WITH TIME ZONE")) { + if (typeName.Contains("WITH TIME ZONE", StringComparison.OrdinalIgnoreCase)) { return new SqlValueType(SqlType.DateTimeOffset); } return new SqlValueType(SqlType.DateTime); } - if (typeName == "NVARCHAR2" || typeName == "NCHAR") { + if (string.Equals(typeName, "NVARCHAR2", StringComparison.OrdinalIgnoreCase) + || string.Equals(typeName, "NCHAR", StringComparison.OrdinalIgnoreCase)) { var length = ReadInt(row, charLengthIndex); var sqlType = typeName.Length == 5 ? SqlType.Char : SqlType.VarChar; return new SqlValueType(sqlType, length); @@ -665,7 +681,7 @@ private static void ReadConstraintProperties(Constraint constraint, constraint.IsInitiallyDeferred = row.GetString(isInitiallyDeferredIndex) == "DEFERRED"; } - private static void ReadCascadeAction(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex) + private static void ReadForeignKeyDeleteAction(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex) { var deleteRule = row.GetString(deleteRuleIndex); switch (deleteRule) { diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/ServerInfoProvider.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/ServerInfoProvider.cs index 029367b750..07e4c74a50 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/ServerInfoProvider.cs @@ -225,12 +225,19 @@ public override DataTypeCollection GetDataTypesInfo() ValueRange.Float, "real"); types.Double = DataTypeInfo.Range(SqlType.Double, common | index, ValueRange.Double, "double precision", "float"); + types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, ValueRange.DateTime, "timestamp"); +#if NET6_0_OR_GREATER + types.DateOnly = DataTypeInfo.Range(SqlType.Date, common | index, + ValueRange.DateOnly, "DATE"); + types.TimeOnly = DataTypeInfo.Range(SqlType.Time, common | index, + ValueRange.TimeOnly, "interval day(0) to second(7)"); +#endif types.DateTimeOffset = DataTypeInfo.Range(SqlType.DateTimeOffset, common | index, ValueRange.DateTimeOffset, "TIMESTAMP WITH TIME ZONE"); types.Interval = DataTypeInfo.Range(SqlType.Interval, common | index, - ValueRange.TimeSpan, "interval day to second"); + ValueRange.TimeSpan, "interval day(2) to second(6)"); types.Char = DataTypeInfo.Stream(SqlType.Char, common | index | DataTypeFeatures.ZeroLengthValueIsNull, 2000, "nchar"); diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Translator.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Translator.cs index 5c3c92167c..4fb59e564d 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Translator.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Translator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2022 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 @@ -29,6 +29,14 @@ internal class Translator : SqlTranslator /// public override string DateTimeFormatString => @"'(TIMESTAMP '\'yyyy\-MM\-dd HH\:mm\:ss\.fff\'\)"; +#if NET6_0_OR_GREATER + + /// + public override string DateOnlyFormatString => @"'(DATE '\'yyyy\-MM\-dd\'\)"; + + /// + public override string TimeOnlyFormatString => @"'(INTERVAL '\'0 HH\:mm\:ss\.fffffff\'\ DAY(0) TO SECOND(7))"; +#endif /// public override string TimeSpanFormatString => "(INTERVAL '{0}{1} {2}:{3}:{4}.{5:000}' DAY(6) TO SECOND(3))"; @@ -108,7 +116,7 @@ public override void Translate(SqlCompilerContext context, SqlNextValue node, No /// public override void Translate(SqlCompilerContext context, SqlExtract node, ExtractSection section) { - if (node.DateTimePart == SqlDateTimePart.Second || node.IntervalPart == SqlIntervalPart.Second) { + if (node.IsSecondExtraction) { switch (section) { case ExtractSection.Entry: _ = context.Output.Append("TRUNC(EXTRACT("); @@ -119,7 +127,7 @@ public override void Translate(SqlCompilerContext context, SqlExtract node, Extr } } - if (node.DateTimePart == SqlDateTimePart.Millisecond || node.IntervalPart == SqlIntervalPart.Millisecond) { + if (node.IsMillisecondExtraction) { switch (section) { case ExtractSection.Entry: _ = context.Output.Append("MOD(EXTRACT("); @@ -327,10 +335,19 @@ public override void Translate(SqlCompilerContext context, SqlCast node, NodeSec /// public override string Translate(SqlValueType type) { +#if NET6_0_OR_GREATER + // we need to explicitly specify maximum interval precision + return type.Type == SqlType.Interval + ? "INTERVAL DAY(6) TO SECOND(3)" + : type.Type == SqlType.Time + ? "INTERVAL DAY(0) TO SECOND(7)" + : base.Translate(type); +#else // we need to explicitly specify maximum interval precision return type.Type == SqlType.Interval ? "INTERVAL DAY(6) TO SECOND(3)" : base.Translate(type); +#endif } /// @@ -348,6 +365,32 @@ public override void Translate(IOutput output, SqlDateTimePart dateTimePart) break; } } +#if NET6_0_OR_GREATER + + /// + public override void Translate(IOutput output, SqlDatePart datePart) + { + switch (datePart) { + case SqlDatePart.DayOfWeek: + case SqlDatePart.DayOfYear: + throw new NotSupportedException(); + default: + base.Translate(output, datePart); + break; + } + } + + /// + public override void Translate(IOutput output, SqlTimePart timePart) + { + if (timePart== SqlTimePart.Millisecond) { + _ = output.Append("SECOND"); + } + else { + base.Translate(output, timePart); + } + } +#endif /// public override void Translate(IOutput output, SqlIntervalPart part) @@ -358,7 +401,6 @@ public override void Translate(IOutput output, SqlIntervalPart part) else { base.Translate(output, part); } - } /// @@ -385,6 +427,9 @@ public override void Translate(IOutput output, SqlNodeType type) switch (type) { case SqlNodeType.DateTimeOffsetPlusInterval: case SqlNodeType.DateTimePlusInterval: +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: +#endif _ = output.Append("+"); break; case SqlNodeType.DateTimeOffsetMinusDateTimeOffset: case SqlNodeType.DateTimeOffsetMinusInterval: diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/TypeMapper.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/TypeMapper.cs index c6b0802042..a9e3149e13 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/TypeMapper.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/TypeMapper.cs @@ -87,6 +87,24 @@ public override void BindDouble(DbParameter parameter, object value) nativeParameter.OracleDbType = OracleDbType.Double; nativeParameter.Value = value ?? DBNull.Value; } +#if NET6_0_OR_GREATER + + public override void BindDateOnly(DbParameter parameter, object value) + { + var nativeParameter = (OracleParameter) parameter; + nativeParameter.OracleDbType = OracleDbType.Date; + nativeParameter.Value = value == null + ? (object) DBNull.Value + : new OracleDate(((DateOnly) value).ToDateTime(TimeOnly.MinValue)); + } + + public override void BindTimeOnly(DbParameter parameter, object value) + { + var nativeParameter = (OracleParameter) parameter; + nativeParameter.OracleDbType = OracleDbType.IntervalDS; + nativeParameter.Value = value == null ? (object) DBNull.Value : new OracleIntervalDS(((TimeOnly) value).ToTimeSpan()); + } +#endif public override void BindDateTimeOffset(DbParameter parameter, object value) { @@ -194,7 +212,8 @@ public override object ReadDouble(DbDataReader reader, int index) public override object ReadDateTimeOffset(DbDataReader reader, int index) { var nativeReader = (OracleDataReader) reader; - return new DateTimeOffset(nativeReader.GetOracleTimeStampTZ(index).Value, nativeReader.GetOracleTimeStampTZ(index).GetTimeZoneOffset()); + var timeStampTZ = nativeReader.GetOracleTimeStampTZ(index); + return new DateTimeOffset(timeStampTZ.Value, timeStampTZ.GetTimeZoneOffset()); } public override object ReadTimeSpan(DbDataReader reader, int index) diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/Translator.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/Translator.cs index e9e61349eb..67c9a80be7 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/Translator.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/Translator.cs @@ -26,7 +26,7 @@ public override string Translate(SqlValueType type) { // we need to explicitly specify maximum interval precision return type.Type == SqlType.Interval - ? "INTERVAL DAY(6) TO SECOND(6)" + ? "INTERVAL DAY(6) TO SECOND(7)" : base.Translate(type); } 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 60eab1c1e3..3f3c550c59 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 @@ -1,13 +1,48 @@ -// Copyright (C) 2019-2020 Xtensive LLC. +// Copyright (C) 2019-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: Alexey Kulakov // Created: 2019.09.25 +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; +#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 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)); +#if NET6_0_OR_GREATER + + protected static SqlUserFunctionCall MakeDate(SqlExpression year, SqlExpression month, SqlExpression day) => + 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)); +#endif + // Constructors public Compiler(SqlDriver driver) 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 4d278826b1..24d0d626aa 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 @@ -1,4 +1,4 @@ -// Copyright (C) 2003-2022 Xtensive LLC. +// Copyright (C) 2003-2023 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -13,14 +13,29 @@ namespace Xtensive.Sql.Drivers.PostgreSql.v8_0 { internal class Compiler : SqlCompiler { - private readonly static Type SqlPlaceholderType = typeof(SqlPlaceholder); + private const string DateTimeIsoFormat = "YYYY-MM-DD\"T\"HH24:MI:SS"; +#if NET6_0_OR_GREATER + private const string DateFormat = "YYYY-MM-DD"; + private const string TimeFormat = "HH24:MI:SS.US0"; +#endif + + private static readonly Type SqlPlaceholderType = typeof(SqlPlaceholder); private static readonly SqlNative OneYearInterval = SqlDml.Native("interval '1 year'"); private static readonly SqlNative OneMonthInterval = SqlDml.Native("interval '1 month'"); private static readonly SqlNative OneDayInterval = SqlDml.Native("interval '1 day'"); + + private static readonly SqlNative OneHourInterval = SqlDml.Native("interval '1 hour'"); private static readonly SqlNative OneMinuteInterval = SqlDml.Native("interval '1 minute'"); private static readonly SqlNative OneSecondInterval = SqlDml.Native("interval '1 second'"); + private static readonly SqlLiteral ReferenceDateTimeLiteral = SqlDml.Literal(new DateTime(2001, 1, 1)); + private static readonly SqlLiteral EpochLiteral = SqlDml.Literal(new DateTime(1970, 1, 1)); +#if NET6_0_OR_GREATER + private static readonly SqlLiteral ReferenceDateLiteral = SqlDml.Literal(new DateOnly(2001, 1, 1)); + private static readonly SqlLiteral ZeroTimeLiteral = SqlDml.Literal(new TimeOnly(0, 0, 0)); +#endif + /// public override void Visit(SqlDeclareCursor node) { @@ -54,20 +69,30 @@ public override void Visit(SqlBinary node) var row = SqlDml.Row(right.GetValues().Select(value => SqlDml.Literal(value)).ToArray()); base.Visit(node.NodeType == SqlNodeType.In ? SqlDml.In(node.Left, row) : SqlDml.NotIn(node.Left, row)); } + return; } - else { - switch (node.NodeType) { - case SqlNodeType.DateTimeOffsetMinusDateTimeOffset: - (node.Left - node.Right).AcceptVisitor(this); - return; - case SqlNodeType.DateTimeOffsetMinusInterval: - (node.Left - node.Right).AcceptVisitor(this); - return; - case SqlNodeType.DateTimeOffsetPlusInterval: - (node.Left + node.Right).AcceptVisitor(this); - return; - } - base.Visit(node); + switch (node.NodeType) { + case SqlNodeType.DateTimeOffsetMinusDateTimeOffset: + (node.Left - node.Right).AcceptVisitor(this); + return; + case SqlNodeType.DateTimeOffsetMinusInterval: + (node.Left - node.Right).AcceptVisitor(this); + return; + case SqlNodeType.DateTimeOffsetPlusInterval: + (node.Left + node.Right).AcceptVisitor(this); + return; +#if NET6_0_OR_GREATER + case SqlNodeType.TimeMinusTime: + SqlDml.Cast( + SqlDml.Cast( + (ReferenceDateLiteral + node.Left) - (ReferenceDateLiteral + node.Right), + SqlType.Time), + SqlType.Interval).AcceptVisitor(this); + return; +#endif + default: + base.Visit(node); + return; } } @@ -100,12 +125,26 @@ public override void Visit(SqlFunctionCall node) SqlHelper.IntervalAbs(node.Arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeConstruct: - var newNode = SqlDml.Literal(new DateTime(2001, 1, 1)) + var newNode = ReferenceDateTimeLiteral + (OneYearInterval * (node.Arguments[0] - 2001)) + (OneMonthInterval * (node.Arguments[1] - 1)) + (OneDayInterval * (node.Arguments[2] - 1)); newNode.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); + 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); + return; +#endif case SqlFunctionType.DateTimeTruncate: (SqlDml.FunctionCall("date_trunc", "day", node.Arguments[0])).AcceptVisitor(this); return; @@ -115,8 +154,31 @@ public override void Visit(SqlFunctionCall node) case SqlFunctionType.DateTimeAddYears: (node.Arguments[0] + node.Arguments[1] * OneYearInterval).AcceptVisitor(this); return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateAddYears: + (node.Arguments[0] + node.Arguments[1] * OneYearInterval).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddMonths: + (node.Arguments[0] + node.Arguments[1] * OneMonthInterval).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddDays: + (node.Arguments[0] + node.Arguments[1] * OneDayInterval).AcceptVisitor(this); + return; + case SqlFunctionType.DateToString: + DateTimeToStringIso(node.Arguments[0], DateFormat).AcceptVisitor(this); + return; + case SqlFunctionType.TimeAddHours: + (node.Arguments[0] + node.Arguments[1] * OneHourInterval).AcceptVisitor(this); + return; + case SqlFunctionType.TimeAddMinutes: + (node.Arguments[0] + node.Arguments[1] * OneMinuteInterval).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToString: + DateTimeToStringIso(node.Arguments[0], TimeFormat).AcceptVisitor(this); + return; +#endif case SqlFunctionType.DateTimeToStringIso: - DateTimeToStringIso(node.Arguments[0]).AcceptVisitor(this); + DateTimeToStringIso(node.Arguments[0], DateTimeIsoFormat).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetTimeOfDay: DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); @@ -131,8 +193,37 @@ public override void Visit(SqlFunctionCall node) ConstructDateTimeOffset(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToDateTimeOffset: - SqlDml.Cast(node.Arguments[0], SqlType.DateTimeOffset).AcceptVisitor(this); + DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDateTime: + DateTimeOffsetToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateTimeToDate: + DateTimeToDate(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTime: + DateToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToTime: + DateTimeToTime(node.Arguments[0]).AcceptVisitor(this); return; + case SqlFunctionType.TimeToDateTime: + TimeToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDate: + DateTimeOffsetToDate(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTimeOffset: + DateToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToTime: + DateTimeOffsetToTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTimeOffset: + TimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; +#endif } base.Visit(node); } @@ -206,9 +297,8 @@ public override void Visit(SqlCustomFunctionCall node) base.Visit(node); } - - private static SqlExpression DateTimeToStringIso(SqlExpression dateTime) => - SqlDml.FunctionCall("To_Char", dateTime, "YYYY-MM-DD\"T\"HH24:MI:SS"); + private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat) => + SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat); private static SqlExpression IntervalToIsoString(SqlExpression interval, bool signed) { @@ -283,24 +373,26 @@ private static SqlExpression NpgsqlTypeConstructor(SqlExpression left, SqlExpres } public override void Visit(SqlExtract node) - { - switch (node.DateTimeOffsetPart) { - case SqlDateTimeOffsetPart.Date: - DateTimeOffsetExtractDate(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.DateTime: - DateTimeOffsetExtractDateTime(node.Operand).AcceptVisitor(this); - return; + { + if (node.IsDateTimeOffsetPart) { + switch (node.DateTimeOffsetPart) { + case SqlDateTimeOffsetPart.Date: + DateTimeOffsetExtractDate(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.DateTime: + DateTimeOffsetExtractDateTime(node.Operand).AcceptVisitor(this); + return; - case SqlDateTimeOffsetPart.UtcDateTime: - DateTimeOffsetToUtcDateTime(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.LocalDateTime: - DateTimeOffsetToLocalDateTime(node.Operand).AcceptVisitor(this); - return; - case SqlDateTimeOffsetPart.Offset: - DateTimeOffsetExtractOffset(node); - return; + case SqlDateTimeOffsetPart.UtcDateTime: + DateTimeOffsetToUtcDateTime(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.LocalDateTime: + DateTimeOffsetToLocalDateTime(node.Operand).AcceptVisitor(this); + return; + case SqlDateTimeOffsetPart.Offset: + DateTimeOffsetExtractOffset(node); + return; + } } base.Visit(node); } @@ -361,6 +453,38 @@ protected SqlExpression GetOffsetAsStringExpression(SqlExpression offsetInMinute return IntervalToIsoString(intervalExpression, true); } + private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.DateTimeOffset); + + private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.DateTime); +#if NET6_0_OR_GREATER + + private static SqlExpression DateTimeToDate(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Date); + + private static SqlExpression DateToDateTime(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTime); + + private static SqlExpression DateTimeToTime(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Time); + + private static SqlExpression TimeToDateTime(SqlExpression time) => + SqlDml.Cast(EpochLiteral + time, SqlType.DateTime); + + private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.Date); + + private static SqlExpression DateToDateTimeOffset(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTimeOffset); + + private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.Time); + + private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => + SqlDml.Cast(EpochLiteral + time, SqlType.DateTimeOffset); +#endif + private string ZoneStringFromParts(int hours, int minutes) => $"{(hours < 0 ? "-" : "+")}{Math.Abs(hours):00}:{Math.Abs(minutes):00}"; diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/ServerInfoProvider.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/ServerInfoProvider.cs index 1a811ecf3c..2ab4e9f93a 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/ServerInfoProvider.cs @@ -177,6 +177,11 @@ public override DataTypeCollection GetDataTypesInfo() dtc.Interval = DataTypeInfo.Range(SqlType.Interval, commonFeatures, ValueRange.TimeSpan, "interval"); +#if NET6_0_OR_GREATER + + dtc.DateOnly = DataTypeInfo.Range(SqlType.Date, commonFeatures, ValueRange.DateOnly, "date"); + dtc.TimeOnly = DataTypeInfo.Range(SqlType.Time, commonFeatures, ValueRange.TimeOnly, "time"); +#endif dtc.Char = DataTypeInfo.Stream(SqlType.Char, commonFeatures, MaxCharLength, "character", "char", "bpchar"); dtc.VarChar = DataTypeInfo.Stream(SqlType.VarChar, commonFeatures, MaxCharLength, "character varying", "varchar"); diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Translator.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Translator.cs index 852f4b9c79..b0d25c48c8 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Translator.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Translator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2022 Xtensive LLC. +// 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. @@ -53,6 +53,14 @@ public SqlFunctionTypeTranslations(int count) /// public override string DateTimeFormatString => @"\'yyyyMMdd HHmmss.ffffff\''::timestamp(6)'"; +#if NET6_0_OR_GREATER + + /// + public override string DateOnlyFormatString => @"\'yyyyMMdd\''::date'"; + + /// + public override string TimeOnlyFormatString => @"\'HH:mm:ss.ffffff\''::time'"; +#endif /// public override string TimeSpanFormatString => "'{0}{1} days {0}{2}:{3}:{4}.{5:000}'::interval"; @@ -197,8 +205,14 @@ public override void Translate(IOutput output, SqlNodeType type) case SqlNodeType.Modulo: _ = output.Append("%"); break; case SqlNodeType.Overlaps: _ = output.Append("OVERLAPS"); break; case SqlNodeType.DateTimePlusInterval: _ = output.Append("+"); break; +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: _ = output.Append("+"); break; +#endif case SqlNodeType.DateTimeMinusInterval: case SqlNodeType.DateTimeMinusDateTime: +#if NET6_0_OR_GREATER + case SqlNodeType.TimeMinusTime: +#endif _ = output.Append("-"); break; default: base.Translate(output, type); break; }; @@ -388,12 +402,8 @@ public override void Translate(SqlCompilerContext context, SqlArray node, ArrayS /// public override void Translate(SqlCompilerContext context, SqlExtract node, ExtractSection section) { - var isSecond = node.DateTimePart == SqlDateTimePart.Second - || node.IntervalPart == SqlIntervalPart.Second - || node.DateTimeOffsetPart == SqlDateTimeOffsetPart.Second; - var isMillisecond = node.DateTimePart == SqlDateTimePart.Millisecond - || node.IntervalPart == SqlIntervalPart.Millisecond - || node.DateTimeOffsetPart == SqlDateTimeOffsetPart.Millisecond; + var isSecond = node.IsSecondExtraction; + var isMillisecond = node.IsMillisecondExtraction; if (!(isSecond || isMillisecond)) { base.Translate(context, node, section); return; @@ -824,6 +834,7 @@ public override void Translate(SqlCompilerContext context, SqlCast node, NodeSec } } + /// public override void Translate(IOutput output, SqlDateTimePart part) { switch (part) { @@ -834,6 +845,7 @@ public override void Translate(IOutput output, SqlDateTimePart part) } } + /// public override void Translate(IOutput output, SqlDateTimeOffsetPart part) { switch (part) { @@ -846,6 +858,29 @@ public override void Translate(IOutput output, SqlDateTimeOffsetPart part) default: base.Translate(output, part); break; } } +#if NET6_0_OR_GREATER + + /// + public override void Translate(IOutput output, SqlDatePart part) + { + switch (part) { + case SqlDatePart.DayOfYear: _ = output.Append("DOY"); break; + case SqlDatePart.DayOfWeek: _ = output.Append("DOW"); break; + default: base.Translate(output, part); break; + } + } + + /// + public override void Translate(IOutput output, SqlTimePart part) + { + if (part == SqlTimePart.Millisecond) { + _ = output.Append("MILLISECONDS"); + } + else { + base.Translate(output, part); + } + } +#endif /// public override void Translate(IOutput output, SqlLockType lockType) 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 6d1ce50ad8..fa4798592b 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2022 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 @@ -150,9 +150,10 @@ public override void Visit(SqlAlterTable node) /// public override void Visit(SqlFunctionCall node) { + var arguments = node.Arguments; switch (node.FunctionType) { case SqlFunctionType.CharLength: - (SqlDml.FunctionCall("DATALENGTH", node.Arguments) / 2).AcceptVisitor(this); + (SqlDml.FunctionCall("DATALENGTH", arguments) / 2).AcceptVisitor(this); return; case SqlFunctionType.PadLeft: case SqlFunctionType.PadRight: @@ -160,10 +161,10 @@ public override void Visit(SqlFunctionCall node) return; case SqlFunctionType.Round: // Round should always be called with 2 arguments - if (node.Arguments.Count == 1) { + if (arguments.Count == 1) { Visit(SqlDml.FunctionCall( translator.TranslateToString(SqlFunctionType.Round), - node.Arguments[0], + arguments[0], SqlDml.Literal(0))); return; } @@ -173,42 +174,92 @@ public override void Visit(SqlFunctionCall node) // It's stupid, isn't it? Visit(SqlDml.FunctionCall( translator.TranslateToString(SqlFunctionType.Round), - node.Arguments[0], + arguments[0], SqlDml.Literal(0), SqlDml.Literal(1))); return; case SqlFunctionType.Substring: - if (node.Arguments.Count == 2) { - SqlExpression len = SqlDml.CharLength(node.Arguments[0]); - node = SqlDml.Substring(node.Arguments[0], node.Arguments[1], len); + if (arguments.Count == 2) { + SqlExpression len = SqlDml.CharLength(arguments[0]); + node = SqlDml.Substring(arguments[0], arguments[1], len); Visit(node); return; } break; case SqlFunctionType.IntervalToMilliseconds: - Visit(CastToLong(node.Arguments[0]) / NanosecondsPerMillisecond); + Visit(CastToLong(arguments[0]) / NanosecondsPerMillisecond); return; case SqlFunctionType.IntervalConstruct: case SqlFunctionType.IntervalToNanoseconds: - Visit(CastToLong(node.Arguments[0])); + Visit(CastToLong(arguments[0])); return; case SqlFunctionType.DateTimeAddMonths: - Visit(DateAddMonth(node.Arguments[0], node.Arguments[1])); + Visit(DateAddMonth(arguments[0], arguments[1])); return; case SqlFunctionType.DateTimeAddYears: - Visit(DateAddYear(node.Arguments[0], node.Arguments[1])); + Visit(DateAddYear(arguments[0], arguments[1])); return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateAddYears: + Visit(DateAddYear(arguments[0], arguments[1])); + return; + case SqlFunctionType.DateAddMonths: + Visit(DateAddMonth(arguments[0], arguments[1])); + return; + case SqlFunctionType.DateAddDays: + Visit(DateAddDay(arguments[0], arguments[1])); + return; + case SqlFunctionType.TimeAddHours: + Visit(DateAddHour(arguments[0], arguments[1])); + return; + case SqlFunctionType.TimeAddMinutes: + Visit(DateAddMinute(arguments[0], arguments[1])); + return; +#endif case SqlFunctionType.DateTimeTruncate: - DateTimeTruncate(node.Arguments[0]).AcceptVisitor(this); + DateTimeTruncate(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeConstruct: Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)), - node.Arguments[0] - 2001), - node.Arguments[1] - 1), - node.Arguments[2] - 1)); + arguments[0] - 2001), + arguments[1] - 1), + arguments[2] - 1)); + 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)); + 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)); + return; + case SqlFunctionType.DateToString: + Visit(DateToString(arguments[0])); + return; + case SqlFunctionType.TimeToString: + Visit(TimeToString(arguments[0])); return; + case SqlFunctionType.DateTimeToDate: + DateTimeToDate(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTime: + DateToDateTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToTime: + DateTimeToTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTime: + TimeToDateTime(arguments[0]).AcceptVisitor(this); + return; +#endif case SqlFunctionType.DateTimeToStringIso: - Visit(DateTimeToStringIso(node.Arguments[0])); + Visit(DateTimeToStringIso(arguments[0])); return; } @@ -236,6 +287,12 @@ public override void Visit(SqlExtract node) Visit((DatePartWeekDay(node.Operand) + DateFirst + 6) % 7); return; } +#if NET6_0_OR_GREATER + if (node.DatePart == SqlDatePart.DayOfWeek) { + Visit((DatePartWeekDay(node.Operand) + DateFirst + 6) % 7); + return; + } +#endif switch (node.IntervalPart) { case SqlIntervalPart.Day: @@ -273,6 +330,14 @@ public override void Visit(SqlBinary node) case SqlNodeType.DateTimeMinusInterval: DateTimeAddInterval(node.Left, -node.Right).AcceptVisitor(this); return; +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: + TimeAddInterval(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.TimeMinusTime: + TimeSubtractTime(node.Left, node.Right).AcceptVisitor(this); + return; +#endif default: base.Visit(node); return; @@ -416,6 +481,29 @@ protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpre DateAddDay(date, interval / NanosecondsPerDay), (interval / NanosecondsPerMillisecond) % (MillisecondsPerDay)); } +#if NET6_0_OR_GREATER + + /// + /// Creates expression that represents addition to the given . + /// + /// Time expression. + /// Interval expression to add. + /// + protected virtual SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval) => + DateAddMillisecond(time, (interval / NanosecondsPerMillisecond) % (MillisecondsPerDay)); + + /// + /// Creates expression that represents subtraction of two expressions. + /// + /// First expression. + /// Second expression. + /// Result expression. + /// + protected virtual SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) => + SqlDml.Modulo( + NanosecondsPerDay + CastToDecimal(DateDiffMillisecond(time2, time1), 18,0) * NanosecondsPerMillisecond, + NanosecondsPerDay); +#endif private SqlExpression GenericPad(SqlFunctionCall node) { @@ -475,6 +563,9 @@ protected static SqlUserFunctionCall DatePartWeekDay(SqlExpression date) => protected static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2) => SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(DayPart), date1, date2); + protected static SqlUserFunctionCall DateDiffHour(SqlExpression date1, SqlExpression date2) => + SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(HourPart), date1, date2); + protected static SqlUserFunctionCall DateDiffMillisecond(SqlExpression date1, SqlExpression date2) => SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(MillisecondPart), date1, date2); @@ -498,6 +589,26 @@ protected static SqlUserFunctionCall DateAddSecond(SqlExpression date, SqlExpres protected static SqlUserFunctionCall DateAddMillisecond(SqlExpression date, SqlExpression milliseconds) => SqlDml.FunctionCall("DATEADD", SqlDml.Native(MillisecondPart), milliseconds, date); +#if NET6_0_OR_GREATER + + protected static SqlUserFunctionCall TimeToString(SqlExpression time) => + SqlDml.FunctionCall("CONVERT", SqlDml.Native("NVARCHAR(16)"), time, SqlDml.Native("114")); + + protected static SqlUserFunctionCall DateToString(SqlExpression time) => + SqlDml.FunctionCall("CONVERT", SqlDml.Native("NVARCHAR(10)"), time, SqlDml.Native("23")); + + protected static SqlExpression DateTimeToDate(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Date); + + protected static SqlExpression DateToDateTime(SqlExpression date) => + SqlDml.Cast(date, SqlType.DateTime); + + protected static SqlExpression DateTimeToTime(SqlExpression dateTime) => + SqlDml.Cast(dateTime, SqlType.Time); + + protected static SqlExpression TimeToDateTime(SqlExpression time) => + SqlDml.Cast(time, SqlType.DateTime); +#endif protected static SqlUserFunctionCall DateTimeToStringIso(SqlExpression dateTime) => SqlDml.FunctionCall("CONVERT", SqlDml.Native("NVARCHAR(19)"), dateTime, SqlDml.Native("126")); diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/ServerInfoProvider.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/ServerInfoProvider.cs index c3368bf6f8..325da57b5a 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/ServerInfoProvider.cs @@ -252,8 +252,12 @@ public override DataTypeCollection GetDataTypesInfo() ValueRange.Double, "float"); types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, - new ValueRange(new DateTime(1753, 1, 1), new DateTime(9999, 12,31)), + new ValueRange(new DateTime(1753, 1, 1), new DateTime(9999, 12, 31)), "datetime", "smalldatetime"); +#if NET6_0_OR_GREATER + types.DateOnly = DataTypeInfo.Range(SqlType.Date, common | index, new ValueRange(new DateOnly(1, 1, 1), new DateOnly(9999, 12, 31)), "date"); + types.TimeOnly = DataTypeInfo.Range(SqlType.Time, common | index, new ValueRange(TimeOnly.MinValue, TimeOnly.MaxValue), "time"); +#endif types.Char = DataTypeInfo.Stream(SqlType.Char, common | index, 4000, "nchar", "char"); types.VarChar = DataTypeInfo.Stream(SqlType.VarChar, common | index, 4000, "nvarchar", "varchar"); diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Translator.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Translator.cs index cd34db9e3b..534562c877 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Translator.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Translator.cs @@ -20,6 +20,10 @@ namespace Xtensive.Sql.Drivers.SqlServer.v09 internal class Translator : SqlTranslator { public override string DateTimeFormatString => @"'cast ('\'yyyy\-MM\-ddTHH\:mm\:ss\.fff\'' as datetime)'"; +#if NET6_0_OR_GREATER + public override string DateOnlyFormatString => @"'cast ('\'yyyy\-MM\-dd\'' as date)'"; + public override string TimeOnlyFormatString => @"'cast ('\'HH\:mm\:ss\.fff\'' as time)'"; +#endif public override string TimeSpanFormatString => string.Empty; public override void Initialize() @@ -567,6 +571,13 @@ public override void Translate(SqlCompilerContext context, object literalValue) case long v: _ = output.Append($"CAST({v} as BIGINT)"); break; +#if NET6_0_OR_GREATER + case DateOnly dateOnly: + var dateOnlyRange = (ValueRange) Driver.ServerInfo.DataTypes.DateTime.ValueRange; + var newDateOnly = ValueRangeValidator.Correct(dateOnly.ToDateTime(TimeOnly.MinValue), dateOnlyRange).Date; + output.Append(newDateOnly.ToString(DateOnlyFormatString, DateTimeFormat)); + break; +#endif default: base.Translate(context, literalValue); break; diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/TypeMapper.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/TypeMapper.cs index b998c4dbe2..8e47a6cd7e 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/TypeMapper.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/TypeMapper.cs @@ -79,6 +79,20 @@ public override void BindString(DbParameter parameter, object value) ? NVarCharLength : NVarCharMaxLength; } +#if NET6_0_OR_GREATER + + public override void BindDateOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Date; + parameter.Value = value != null ? (DateOnly) value : DBNull.Value; + } + + public override void BindTimeOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Time; + parameter.Value = value != null ? (TimeOnly) value : DBNull.Value; + } +#endif public override SqlValueType MapSByte(int? length, int? precision, int? scale) { @@ -117,6 +131,14 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) { return ((SqlDataReader) reader).GetDateTimeOffset(index); } +#if NET6_0_OR_GREATER + + public override object ReadDateOnly(DbDataReader reader, int index) => + reader.GetFieldValue(index); + + public override object ReadTimeOnly(DbDataReader reader, int index) => + reader.GetFieldValue(index); +#endif public override void Initialize() { diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs index 9e952ad69a..9b167b4277 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2022 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 @@ -45,13 +45,7 @@ protected override SqlExpression DateTimeTruncate(SqlExpression date) => SqlDml.Cast(date, new SqlValueType(SqlDateTypeName)), new SqlValueType(SqlDateTime2TypeName)); - /// - /// Creates expression that represents subtraction of two expressions. - /// - /// First expression. - /// Second expression. - /// Result expression. - /// + /// protected override SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2) { return base.DateTimeSubtractDateTime(date1, date2) @@ -138,6 +132,23 @@ public override void Visit(SqlFunctionCall node) case SqlFunctionType.DateTimeToDateTimeOffset: DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); return; + case SqlFunctionType.DateTimeOffsetToDateTime: + DateTimeOffsetToDateTime(node.Arguments[0]).AcceptVisitor(this); + return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateToDateTimeOffset: + DateToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTimeOffset: + TimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToTime: + DateTimeOffsetToTime(node.Arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDate: + DateTimeOffsetToDate(node.Arguments[0]).AcceptVisitor(this); + return; +#endif } base.Visit(node); @@ -206,6 +217,23 @@ private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) => SqlDml.Native(OffsetPart), SqlDml.Native("SYSDATETIMEOFFSET()"))); + private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.DateTime); +#if NET6_0_OR_GREATER + + private static SqlExpression DateToDateTimeOffset(SqlExpression date) => + DateTimeToDateTimeOffset(SqlDml.Cast(date, SqlType.DateTime)); + + private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => + DateTimeToDateTimeOffset(SqlDml.Cast(time, SqlType.DateTime)); + + private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset)=> + SqlDml.Cast(dateTimeOffset, SqlType.Time); + + private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset) => + SqlDml.Cast(dateTimeOffset, SqlType.Date); +#endif + #endregion // Constructors @@ -215,4 +243,6 @@ public Compiler(SqlDriver driver) { } } -} \ No newline at end of file +} + + diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/ServerInfoProvider.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/ServerInfoProvider.cs index 57afca22ff..47ff925a90 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/ServerInfoProvider.cs @@ -29,14 +29,20 @@ public override DataTypeCollection GetDataTypesInfo() var index = DataTypeFeatures.Indexing | DataTypeFeatures.Clustering | DataTypeFeatures.FillFactor | DataTypeFeatures.KeyConstraint; - types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, - new ValueRange(new DateTime(1, 1, 1), new DateTime(9999, 12, 31)), - "datetime2", "datetime", "date", "time", "smalldatetime"); types.DateTimeOffset = DataTypeInfo.Range(SqlType.DateTimeOffset, common | index, new ValueRange(new DateTimeOffset(1, 1, 1, 0, 0, 0, 0, new TimeSpan(0)), new DateTimeOffset(9999, 12, 31, 0, 0, 0, 0, new TimeSpan(0))), "datetimeoffset"); +#if NET6_0_OR_GREATER + types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, + new ValueRange(new DateTime(1, 1, 1), new DateTime(9999, 12, 31)), + "datetime2", "datetime", "smalldatetime"); +#else + types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, + new ValueRange(new DateTime(1, 1, 1), new DateTime(9999, 12, 31)), + "datetime2", "datetime", "smalldatetime", "date", "time"); +#endif types.VarBinaryMax = DataTypeInfo.Regular(SqlType.VarBinaryMax, common, "varbinary(max)", "image"); diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Translator.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Translator.cs index 4cacf3b964..43acc916e7 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Translator.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Translator.cs @@ -17,6 +17,11 @@ internal class Translator : v09.Translator /// public override string DateTimeFormatString => @"'cast ('\'yyyy\-MM\-ddTHH\:mm\:ss\.fffffff\'' as datetime2)'"; +#if NET6_0_OR_GREATER + /// + public override string TimeOnlyFormatString => @"'cast ('\'HH\:mm\:ss\.fffffff\'' as time)'"; +#endif + public string DateTimeOffsetFormatString => @"'cast ('\'yyyy\-MM\-dd HH\:mm\:ss\.fffffff\ zzz\'' as datetimeoffset)'"; /// 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 63736de380..be5c543cd3 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v11/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v11/Compiler.cs @@ -44,6 +44,31 @@ 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; +#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); + } + public Compiler(SqlDriver driver) : base(driver) { diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs index 371cc0f398..938e0d4a82 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2021 Xtensive LLC. +// Copyright (C) 2018-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: Alexey Kulakov @@ -13,6 +13,50 @@ internal class Compiler : v12.Compiler protected const string MicrosecondPart = "MCS"; protected const long NanosecondsPerMicrosecond = 1000; +#if NET6_0_OR_GREATER //DO_DATEONLY + /// + public override void Visit(SqlFunctionCall node) + { + switch (node.FunctionType) { + case SqlFunctionType.DateTimeOffsetTimeOfDay: + DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); + break; + case SqlFunctionType.IntervalToMilliseconds: { + if (node.Arguments[0] is SqlBinary binary + && (binary.NodeType is SqlNodeType.DateTimeMinusDateTime or SqlNodeType.DateTimeOffsetMinusDateTimeOffset or SqlNodeType.TimeMinusTime)) { + Visit(DateDiffBigMicrosecond(binary.Right, binary.Left) / CastToLong(1000)); + } + else { + base.Visit(node); + } + break; + } + case SqlFunctionType.IntervalToNanoseconds: { + if (node.Arguments[0] is SqlBinary binary) { + if (binary.NodeType is SqlNodeType.DateTimeMinusDateTime or SqlNodeType.DateTimeOffsetMinusDateTimeOffset) { + // we have to use time consuming algorithm here because + // DATEDIFF_BIG can throw arithmetic overflow on nanoseconds + // so we should handle it by this big formula + Visit(CastToLong(DateTimeSubtractDateTimeExpensive(binary.Left, binary.Right))); + } + else if (binary.NodeType is SqlNodeType.TimeMinusTime) { + //but for time it is OK + Visit(DateDiffBigMicrosecond(binary.Right, binary.Left)); + } + else { + base.Visit(node); + } + } + else { + base.Visit(node); + } + break; + } + default: + base.Visit(node); break; + } + } +#else /// public override void Visit(SqlFunctionCall node) { @@ -47,6 +91,7 @@ public override void Visit(SqlFunctionCall node) base.Visit(node); break; } } +#endif protected override SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2) { @@ -59,7 +104,7 @@ private SqlExpression DateTimeSubtractDateTimeExpensive(SqlExpression date1, Sql + CastToDecimal(DateDiffBigMillisecond(DateAddDay(date2, DateDiffBigDay(date2, date1)), date1), 18, 0) * NanosecondsPerMillisecond; } - #region Static Helpers +#region Static Helpers protected static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) => DateDiffBigNanosecond( @@ -78,7 +123,7 @@ protected static SqlUserFunctionCall DateDiffBigMillisecond(SqlExpression date1, protected static SqlUserFunctionCall DateDiffBigDay(SqlExpression date1, SqlExpression date2) => SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(DayPart), date1, date2); - #endregion +#endregion public Compiler(SqlDriver driver) : base(driver) diff --git a/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj b/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj index 1717bd3525..c9b18b906f 100644 --- a/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj +++ b/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj @@ -16,7 +16,7 @@ 2 - + 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 cf044ff98e..b00bcffaf5 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2022 Xtensive LLC. +// Copyright (C) 2011-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: Malisa Ncube @@ -16,18 +16,23 @@ namespace Xtensive.Sql.Drivers.Sqlite.v3 { internal class Compiler : SqlCompiler { + private const long NanosecondsPerMillisecond = 1000000L; + private const string DateWithZeroTimeFormat = "%Y-%m-%d 00:00:00.000"; +#if NET6_0_OR_GREATER + private const string DateFormat = "%Y-%m-%d"; + private const string TimeFormat = "%H:%M:%f0000"; + private const string TimeToStringFormat = "%H:%M:%f0000"; +#endif + 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 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('+'); - private const long NanosecondsPerMillisecond = 1000000L; - private const string DateFormat = "%Y-%m-%d 00:00:00.000"; - 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"; - protected override bool VisitCreateTableConstraints(SqlCreateTable node, IEnumerable constraints, bool hasItems) { // SQLite has special syntax for autoincrement primary keys @@ -73,6 +78,14 @@ public override void Visit(SqlBinary node) DateTimeOffsetExtractOffsetAsString(node.Left)) .AcceptVisitor(this); return; +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: + TimeAddInterval(node.Left, node.Right).AcceptVisitor(this); + return; + case SqlNodeType.TimeMinusTime: + TimeSubtractTime(node.Left, node.Right).AcceptVisitor(this); + return; +#endif default: base.Visit(node); return; @@ -110,18 +123,25 @@ public override void Visit(SqlAlterTable node) /// public override void Visit(SqlExtract node) { - if (node.IntervalPart != SqlIntervalPart.Nothing) { + if (node.IsIntervalPart) { VisitInterval(node); return; } - if (node.DateTimePart != SqlDateTimePart.Nothing) { + if (node.IsDateTimePart) { VisitDateTime(node); return; } - if (node.DateTimeOffsetPart != SqlDateTimeOffsetPart.Nothing) { + if (node.IsDateTimeOffsetPart) { VisitDateTimeOffset(node); return; } +#if NET6_0_OR_GREATER + if (node.IsTimePart) { + VisitTime(node); + return; + } +#endif + base.Visit(node); } @@ -131,77 +151,148 @@ public override void Visit(SqlExtract node) /// public override void Visit(SqlFunctionCall node) { + var arguments = node.Arguments; switch (node.FunctionType) { case SqlFunctionType.CharLength: - (SqlDml.FunctionCall("LENGTH", node.Arguments) / 2).AcceptVisitor(this); + (SqlDml.FunctionCall("LENGTH", arguments) / 2).AcceptVisitor(this); return; case SqlFunctionType.PadLeft: case SqlFunctionType.PadRight: return; case SqlFunctionType.Concat: - var nod = node.Arguments[0]; + var nod = arguments[0]; return; case SqlFunctionType.Round: // Round should always be called with 2 arguments if (node.Arguments.Count == 1) { - Visit(SqlDml.FunctionCall(translator.TranslateToString(SqlFunctionType.Round), node.Arguments[0], SqlDml.Literal(0))); + Visit(SqlDml.FunctionCall(translator.TranslateToString(SqlFunctionType.Round), arguments[0], SqlDml.Literal(0))); return; } break; case SqlFunctionType.Truncate: - Visit(CastToLong(node.Arguments[0])); + Visit(CastToLong(arguments[0])); return; case SqlFunctionType.IntervalConstruct: - Visit(CastToLong(node.Arguments[0])); + Visit(CastToLong(arguments[0])); return; case SqlFunctionType.IntervalToNanoseconds: - Visit(CastToLong(node.Arguments[0])); + Visit(CastToLong(arguments[0])); return; case SqlFunctionType.IntervalToMilliseconds: - Visit(CastToLong(node.Arguments[0] / NanosecondsPerMillisecond)); + Visit(CastToLong(arguments[0] / NanosecondsPerMillisecond)); return; case SqlFunctionType.DateTimeAddMonths: - DateAddMonth(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); + DateTimeAddMonth(arguments[0], arguments[1]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeAddYears: - DateAddYear(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); + DateTimeAddYear(arguments[0], arguments[1]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeTruncate: - DateTimeTruncate(node.Arguments[0]).AcceptVisitor(this); + DateTimeTruncate(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeConstruct: - DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)), - node.Arguments[0] - 2001), - node.Arguments[1] - 1), - node.Arguments[2] - 1).AcceptVisitor(this); + DateTimeAddDay(DateTimeAddMonth(DateTimeAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)), + arguments[0] - 2001), + arguments[1] - 1), + arguments[2] - 1).AcceptVisitor(this); + return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateAddYears: + DateAddYear(arguments[0], arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddMonths: + DateAddMonth(arguments[0], arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.DateAddDays: + DateAddDay(arguments[0], arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToString: + DateToString(arguments[0]).AcceptVisitor(this); return; + case SqlFunctionType.DateConstruct: + DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)), + arguments[0] - 2001), + 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); + return; + case SqlFunctionType.TimeAddHours: + TimeAddHours(arguments[0], arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeAddMinutes: + TimeAddMinutes(arguments[0], arguments[1]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToString: + TimeToString(arguments[0]).AcceptVisitor(this); + return; +#endif case SqlFunctionType.DateTimeToStringIso: - DateTimeToStringIso(node.Arguments[0]).AcceptVisitor(this); + DateTimeToStringIso(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetAddMonths: - SqlDml.Concat(DateAddMonth(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), node.Arguments[1]), DateTimeOffsetExtractOffsetAsString(node.Arguments[0])).AcceptVisitor(this); + SqlDml.Concat(DateTimeAddMonth( + DateTimeOffsetExtractDateTimeAsString(arguments[0]), arguments[1]), + DateTimeOffsetExtractOffsetAsString(arguments[0])) + .AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetAddYears: - SqlDml.Concat(DateAddYear(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), node.Arguments[1]), DateTimeOffsetExtractOffsetAsString(node.Arguments[0])).AcceptVisitor(this); + SqlDml.Concat( + DateTimeAddYear(DateTimeOffsetExtractDateTimeAsString(arguments[0]), arguments[1]), + DateTimeOffsetExtractOffsetAsString(arguments[0])) + .AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetConstruct: - SqlDml.Concat(node.Arguments[0], OffsetToOffsetAsString(node.Arguments[1])).AcceptVisitor(this); + SqlDml.Concat(arguments[0], OffsetToOffsetAsString(arguments[1])).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToLocalTime: SqlDml.Concat(DateTimeOffsetToLocalDateTime(node.Arguments[0]), ServerOffsetAsString()).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToUtcTime: - SqlDml.Concat(DateTimeOffsetToUtcDateTime(node.Arguments[0]), "+00:00").AcceptVisitor(this); + SqlDml.Concat(DateTimeOffsetToUtcDateTime(arguments[0]), "+00:00").AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetTimeOfDay: SqlDml.DateTimeMinusDateTime( - DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), - DateTimeTruncate(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]))) + DateTimeOffsetExtractDateTimeAsString(arguments[0]), + DateTimeTruncate(DateTimeOffsetExtractDateTimeAsString(arguments[0]))) .AcceptVisitor(this); return; case SqlFunctionType.DateTimeToDateTimeOffset: - SqlDml.Concat(DateTime(node.Arguments[0]), ServerOffsetAsString()).AcceptVisitor(this); + DateTimeToDateTimeOffset(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDateTime: + DateTimeOffsetToDateTime(arguments[0]).AcceptVisitor(this); return; +#if NET6_0_OR_GREATER + case SqlFunctionType.DateTimeToDate: + DateTimeToDate(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTime: + DateToDateTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeToTime: + DateTimeToTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTime: + TimeToDateTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToTime: + DateTimeOffsetToTime(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateTimeOffsetToDate: + DateTimeOffsetToDate(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToDateTimeOffset: + TimeToDateTimeOffset(arguments[0]).AcceptVisitor(this); + return; + case SqlFunctionType.DateToDateTimeOffset: + DateToDateTimeOffset(arguments[0]).AcceptVisitor(this); + return; +#endif } base.Visit(node); } @@ -315,12 +406,23 @@ private void VisitInterval(SqlExtract node) private void VisitDateTime(SqlExtract node) { - if (node.DateTimePart==SqlDateTimePart.Millisecond) { - Visit(CastToLong(DateGetMilliseconds(node.Operand))); + if (node.IsMillisecondExtraction) { + Visit(CastToLong(DateOrTimeGetMilliseconds(node.Operand))); + return; + } + base.Visit(node); + } +#if NET6_0_OR_GREATER //DO_DATEONLY + + private void VisitTime(SqlExtract node) + { + if (node.IsMillisecondExtraction) { + Visit(CastToLong(DateOrTimeGetMilliseconds(node.Operand))); return; } base.Visit(node); } +#endif private void VisitDateTimeOffset(SqlExtract node) { @@ -347,14 +449,17 @@ private void VisitDateTimeOffset(SqlExtract node) (((DateTimeOffsetExtractOffsetAsTotalNanoseconds(node.Operand)) % NanosecondsPerHour) / (60 * NanosecondsPerSecond)).AcceptVisitor(this); return; } - Visit(SqlDml.Extract(ConvertDateTimeOffsetPartToDateTimePart(node.DateTimeOffsetPart), DateTimeOffsetExtractDateTimeAsString(node.Operand))); + Visit( + SqlDml.Extract( + ConvertDateTimeOffsetPartToDateTimePart(node.DateTimeOffsetPart), + DateTimeOffsetExtractDateTimeAsString(node.Operand))); } private static SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpression interval) => - DateAddSeconds(date, interval / Convert.ToDouble(NanosecondsPerSecond)); + DateTimeAddSeconds(date, interval / Convert.ToDouble(NanosecondsPerSecond)); private static SqlExpression DateTimeTruncate(SqlExpression date) => - DateTime(SqlDml.FunctionCall("STRFTIME", DateFormat, date)); + DateTime(SqlDml.FunctionCall("STRFTIME", DateWithZeroTimeFormat, date)); private static SqlExpression DateTime(SqlExpression date) => SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date); @@ -402,35 +507,109 @@ private static SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression dateTimeO private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTimeOffset) => SqlDml.FunctionCall("STRFTIME", 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); - private static SqlExpression DateAddYear(SqlExpression date, SqlExpression years) => + private static SqlExpression DateTimeAddYear(SqlExpression date, SqlExpression years) => SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(years, " ", "YEARS")); - - private static SqlExpression DateAddMonth(SqlExpression date, SqlExpression months) => + private static SqlExpression DateTimeAddMonth(SqlExpression date, SqlExpression months) => SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(months, " ", "MONTHS")); - private static SqlExpression DateAddDay(SqlExpression date, SqlExpression days) => + private static SqlExpression DateTimeAddDay(SqlExpression date, SqlExpression days) => SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(days, " ", "DAYS")); - - private static SqlExpression DateAddSeconds(SqlExpression date, SqlExpression seconds) => + private static SqlExpression DateTimeAddSeconds(SqlExpression date, SqlExpression seconds) => SqlDml.FunctionCall("STRFTIME", 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")); + + private static SqlExpression DateAddMonth(SqlExpression date, SqlExpression months) => + SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(months, " ", "MONTHS")); + + private static SqlExpression DateAddDay(SqlExpression date, SqlExpression days) => + SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(days, " ", "DAYS")); + + private static SqlExpression TimeAddHours(SqlExpression time, SqlExpression seconds) => + SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, " ", "HOURS")); + + private static SqlExpression TimeAddMinutes(SqlExpression time, SqlExpression seconds) => + SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, " ", "MINUTES")); + + private static SqlExpression TimeAddSeconds(SqlExpression time, SqlExpression seconds, SqlExpression milliseconds) => + SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, ".", milliseconds, " ", "SECONDS")); + private static SqlExpression TimeAddSeconds(SqlExpression time, SqlExpression seconds) => + SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, " ", "SECONDS")); + + private static SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval) => + TimeAddSeconds(time, interval / Convert.ToDouble(NanosecondsPerSecond)); + + private static SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) + { + var hoursInSecs1 = SqlDml.Extract(SqlTimePart.Hour, time1) * 3600; + var hoursInSecs2 = SqlDml.Extract(SqlTimePart.Hour, time2) * 3600; + + 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 difference = ((hoursInSecs1 + minutesInSecs1 + seconds1) * NanosecondsPerSecond) + - ((hoursInSecs2 + minutesInSecs2 + seconds2) * NanosecondsPerSecond); + + return SqlDml.Modulo(NanosecondsPerDay + difference, NanosecondsPerDay); + } + + private static SqlExpression DateToString(SqlExpression dateTime) => + SqlDml.FunctionCall("STRFTIME", DateFormat, dateTime); + + private static SqlExpression TimeToString(SqlExpression dateTime) => + SqlDml.FunctionCall("STRFTIME", TimeToStringFormat, dateTime); + + private static SqlExpression DateTimeToDate(SqlExpression dateTime) => + SqlDml.FunctionCall("STRFTIME", DateFormat, dateTime); + + private static SqlExpression DateToDateTime(SqlExpression date) => + SqlDml.FunctionCall("STRFTIME", DateTimeFormat, SqlDml.Concat(date, " 00:00:00")); + + private static SqlExpression DateTimeToTime(SqlExpression dateTime) => + SqlDml.FunctionCall("STRFTIME", TimeFormat, dateTime); + private static SqlExpression TimeToDateTime(SqlExpression time) => + SqlDml.FunctionCall("STRFTIME", "1900-01-01 " + TimeFormat, time); + + private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset) => + DateTimeToTime(SqlDml.FunctionCall("STRFTIME", TimeFormat, DateTimeOffsetExtractDateTimeAsString(dateTimeOffset))); + + private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset) => + DateTimeToDate(SqlDml.FunctionCall("STRFTIME", DateFormat, DateTimeOffsetExtractDateTimeAsString(dateTimeOffset))); + + private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => + SqlDml.Concat(SqlDml.FunctionCall("STRFTIME", 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()); +#endif - private static SqlExpression DateGetMilliseconds(SqlExpression date) => + private static SqlExpression DateOrTimeGetMilliseconds(SqlExpression date) => CastToLong(SqlDml.FunctionCall("STRFTIME", "%f", date) * MillisecondsPerSecond) - CastToLong(SqlDml.FunctionCall("STRFTIME", "%S", date) * MillisecondsPerSecond); - - private static SqlExpression DateGetTotalSeconds(SqlExpression date) => + private static SqlExpression DateOrTimeGetTotalSeconds(SqlExpression date) => SqlDml.FunctionCall("STRFTIME", "%s", date); private static SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2) => - (((DateGetTotalSeconds(date1) - DateGetTotalSeconds(date2)) * MillisecondsPerSecond) - + DateGetMilliseconds(date1) - DateGetMilliseconds(date2)) * NanosecondsPerMillisecond; - + (((DateOrTimeGetTotalSeconds(date1) - DateOrTimeGetTotalSeconds(date2)) * MillisecondsPerSecond) + + DateOrTimeGetMilliseconds(date1) - DateOrTimeGetMilliseconds(date2)) * NanosecondsPerMillisecond; private static SqlExpression ServerOffsetAsString() { diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Extractor.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Extractor.cs index 47f6591a38..3d7b0c1921 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Extractor.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Extractor.cs @@ -416,7 +416,7 @@ private static void ReadForeignKeyColumnData(DbDataReader reader, Table table, r state.ForeignKey = state.ReferencingTable.CreateForeignKey(foreignKeyName); - ReadCascadeAction(state.ForeignKey, reader, 6); + ReadForeignKeyActions(state.ForeignKey, reader, 6, 5); var referencedSchema = table.Schema; //Schema same as current var referencedTable = referencedSchema.Tables[ReadStringOrNull(reader, 2)]; state.ReferencedTable = referencedTable; @@ -444,25 +444,29 @@ private SqlValueType ParseValueType(string typeDefinition) // (rules are taken from sqlite docs) // (1) If the declared type contains the string "INT" then it is assigned INTEGER affinity. - if (typeName.Contains("int")) { + if (typeName.Contains("int", StringComparison.OrdinalIgnoreCase)) { return new SqlValueType(SqlType.Int64); } // (2) If the declared type of the column contains any of the strings "CHAR", "CLOB", or "TEXT" // then that column has TEXT affinity. - if (typeName.Contains("char") || typeName.Contains("clob") || typeName.Contains("text")) { + if (typeName.Contains("char", StringComparison.OrdinalIgnoreCase) + || typeName.Contains("clob", StringComparison.OrdinalIgnoreCase) + || typeName.Contains("text", StringComparison.OrdinalIgnoreCase)) { return new SqlValueType(SqlType.VarCharMax); } // (3) If the declared type for a column contains the string "BLOB" // or if no type is specified then the column has affinity NONE. - if (typeName.Contains("blob") || typeName==string.Empty) { + if (typeName.Contains("blob", StringComparison.OrdinalIgnoreCase) || typeName==string.Empty) { return new SqlValueType(SqlType.VarBinaryMax); } // (4) If the declared type for a column contains any of the strings // "REAL", "FLOA", or "DOUB" then the column has REAL affinity. - if (typeName.Contains("real") || typeName.Contains("floa") || typeName.Contains("doub")) { + if (typeName.Contains("real", StringComparison.OrdinalIgnoreCase) + || typeName.Contains("floa", StringComparison.OrdinalIgnoreCase) + || typeName.Contains("doub", StringComparison.OrdinalIgnoreCase)) { return new SqlValueType(SqlType.Double); } @@ -491,19 +495,25 @@ private static string ReadStringOrNull(IDataRecord row, int index) => private static int? ReadNullableInt(IDataRecord reader, string column) => Convert.IsDBNull(reader[column]) ? null : (int?) Convert.ToInt32(reader[column]); - private static void ReadCascadeAction(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex) + private static void ReadForeignKeyActions(ForeignKey foreignKey, IDataRecord row, int deleteRuleIndex, int updateRuleIndex) { var deleteRule = row.GetString(deleteRuleIndex); - switch (deleteRule) { - case "CASCADE": - foreignKey.OnDelete = ReferentialAction.Cascade; - return; - case "SET NULL": - foreignKey.OnDelete = ReferentialAction.SetNull; - return; - case "NO ACTION": - foreignKey.OnDelete = ReferentialAction.NoAction; - return; + foreignKey.OnDelete = GetEnumAction(deleteRule); + + var updateRule = row.GetString(updateRuleIndex); + foreignKey.OnUpdate = GetEnumAction(updateRule); + + + static ReferentialAction GetEnumAction(in string rawActionName) + { + return rawActionName switch { + "CASCADE" => ReferentialAction.Cascade, + "SET NULL" => ReferentialAction.SetNull, + "NO ACTION" => ReferentialAction.NoAction, + "RESTRICT" => ReferentialAction.NoAction, + "SET DEFAULT" => ReferentialAction.SetDefault, + _ => throw new ArgumentOutOfRangeException() + }; } } diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/ServerInfoProvider.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/ServerInfoProvider.cs index 304c4abb37..7db0490b0c 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/ServerInfoProvider.cs @@ -228,6 +228,12 @@ public override DataTypeCollection GetDataTypesInfo() types.DateTime = DataTypeInfo.Range(SqlType.DateTime, common | index, ValueRange.DateTime, "datetime"); types.DateTimeOffset = DataTypeInfo.Range(SqlType.DateTimeOffset, common | index, ValueRange.DateTimeOffset, "datetimeoffset"); +#if NET6_0_OR_GREATER + + types.DateOnly = DataTypeInfo.Range(SqlType.Date, common | index, ValueRange.DateOnly, "date"); + + types.TimeOnly = DataTypeInfo.Range(SqlType.Time, common | index, ValueRange.TimeOnly, "time"); +#endif types.VarCharMax = DataTypeInfo.Regular(SqlType.VarCharMax, common | index, "varchar", "nvarchar", "nchar", "char", "text", "xml"); diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Translator.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Translator.cs index 737c31aedf..c59e56208a 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Translator.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Translator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2022 Xtensive LLC. +// Copyright (C) 2011-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: Malisa Ncube @@ -22,6 +22,14 @@ internal class Translator : SqlTranslator /// public override string DateTimeFormatString => @"\'yyyy\-MM\-dd HH\:mm\:ss.fff\'"; +#if NET6_0_OR_GREATER + + /// + public override string DateOnlyFormatString => @"\'yyyy\-MM\-dd\'"; + + /// + public override string TimeOnlyFormatString => @"\'HH\:mm\:ss.fffffff\'"; +#endif public virtual string DateTimeOffsetFormatString => @"\'yyyy\-MM\-dd HH\:mm\:ss.fffK\'"; @@ -338,6 +346,32 @@ public override void Translate(IOutput output, SqlDateTimePart dateTimePart) default: base.Translate(output, dateTimePart); break; } } +#if NET6_0_OR_GREATER + + /// + public override void Translate(IOutput output, SqlDatePart dateTimePart) + { + switch (dateTimePart) { + case SqlDatePart.Year: _ = output.Append("'%Y'"); break; + case SqlDatePart.Month: _ = output.Append("'%m'"); break; + case SqlDatePart.Day: _ = output.Append("'%d'"); break; + case SqlDatePart.DayOfWeek: _ = output.Append("'%w'"); break; + case SqlDatePart.DayOfYear: _ = output.Append("'%j'"); break; + default: base.Translate(output, dateTimePart); break; + } + } + + /// + public override void Translate(IOutput output, SqlTimePart dateTimePart) + { + switch (dateTimePart) { + case SqlTimePart.Hour: _ = output.Append("'%H'"); break; + case SqlTimePart.Minute: _ = output.Append("'%M'"); break; + case SqlTimePart.Second: _ = output.Append("'%S'"); break; + default: base.Translate(output, dateTimePart); break; + } + } +#endif /// public override void Translate(IOutput output, SqlIntervalPart intervalPart) => throw SqlHelper.NotSupported(intervalPart.ToString()); @@ -482,12 +516,18 @@ public override void Translate(IOutput output, SqlNodeType type) switch (type) { case SqlNodeType.DateTimePlusInterval: case SqlNodeType.DateTimeOffsetPlusInterval: +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: +#endif _ = output.Append("+"); break; case SqlNodeType.DateTimeMinusInterval: case SqlNodeType.DateTimeMinusDateTime: case SqlNodeType.DateTimeOffsetMinusInterval: case SqlNodeType.DateTimeOffsetMinusDateTimeOffset: +#if NET6_0_OR_GREATER + case SqlNodeType.TimeMinusTime: +#endif _ = output.Append("-"); break; case SqlNodeType.Overlaps: diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/TypeMapper.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/TypeMapper.cs index 900b64d8c9..ef72270a5c 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/TypeMapper.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2021 Xtensive LLC. +// Copyright (C) 2011-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: Malisa Ncube @@ -9,23 +9,44 @@ using System.Data.Common; using System.Data.SQLite; using System.Globalization; +using System.Security.AccessControl; using Xtensive.Sql.Info; namespace Xtensive.Sql.Drivers.Sqlite.v3 { internal class TypeMapper : Sql.TypeMapper { +#if NET6_0_OR_GREATER + private ValueRange dateOnlyRange; + private ValueRange timeOnlyRange; +#endif private ValueRange dateTimeRange; private ValueRange dateTimeOffsetRange; private const string DateTimeOffsetFormat = "yyyy-MM-dd HH:mm:ss.fffK"; private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fffffff"; + private const string DateFormat = "yyyy-MM-dd"; + private const string TimeFormat = "HH:mm:ss.fffffff"; public override object ReadBoolean(DbDataReader reader, int index) { var value = reader.GetDecimal(index); return SQLiteConvert.ToBoolean(value); } +#if NET6_0_OR_GREATER + + public override object ReadDateOnly(DbDataReader reader, int index) + { + var value = reader.GetString(index); + return DateOnly.ParseExact(value, DateFormat, CultureInfo.InvariantCulture); + } + + public override object ReadTimeOnly(DbDataReader reader, int index) + { + var value = reader.GetString(index); + return TimeOnly.ParseExact(value, TimeFormat, CultureInfo.InvariantCulture); + } +#endif public override object ReadDateTimeOffset(DbDataReader reader, int index) { @@ -67,6 +88,30 @@ public override void BindDateTime(DbParameter parameter, object value) var correctValue = ValueRangeValidator.Correct((DateTime) value, dateTimeRange); parameter.Value = correctValue.ToString(DateTimeFormat, CultureInfo.InvariantCulture); } +#if NET6_0_OR_GREATER + + public override void BindDateOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.String; + if (value == null) { + parameter.Value = DBNull.Value; + return; + } + var correctValue = ValueRangeValidator.Correct((DateOnly) value, dateOnlyRange); + parameter.Value = correctValue.ToString(DateFormat, CultureInfo.InvariantCulture); + } + + public override void BindTimeOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.String; + if (value == null) { + parameter.Value = DBNull.Value; + return; + } + var correctValue = ValueRangeValidator.Correct((TimeOnly) value, timeOnlyRange); + parameter.Value = correctValue.ToString(TimeFormat, CultureInfo.InvariantCulture); + } +#endif public override void BindDateTimeOffset(DbParameter parameter, object value) { @@ -129,6 +174,10 @@ public override void Initialize() base.Initialize(); dateTimeRange = (ValueRange) Driver.ServerInfo.DataTypes.DateTime.ValueRange; dateTimeOffsetRange = (ValueRange) Driver.ServerInfo.DataTypes.DateTimeOffset.ValueRange; +#if NET6_0_OR_GREATER + dateOnlyRange = (ValueRange) Driver.ServerInfo.DataTypes.DateOnly.ValueRange; + timeOnlyRange = (ValueRange) Driver.ServerInfo.DataTypes.TimeOnly.ValueRange; +#endif } // Constructors diff --git a/Orm/Xtensive.Orm.Tests.Framework/Interfaces/IStorageTimeZoneProvider.cs b/Orm/Xtensive.Orm.Tests.Framework/Interfaces/IStorageTimeZoneProvider.cs new file mode 100644 index 0000000000..c8ab060896 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Framework/Interfaces/IStorageTimeZoneProvider.cs @@ -0,0 +1,13 @@ +// Copyright (C) 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; + +namespace Xtensive.Orm.Tests +{ + public interface IStorageTimeZoneProvider + { + public TimeSpan TimeZoneOffset { get; } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Framework/StorageProviderInfo.cs b/Orm/Xtensive.Orm.Tests.Framework/StorageProviderInfo.cs index b7910be38c..13d3a40222 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/StorageProviderInfo.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/StorageProviderInfo.cs @@ -1,11 +1,12 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2013-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: 2013.07.23 using System; using Xtensive.Orm.Providers; +using Xtensive.Sql; namespace Xtensive.Orm.Tests { @@ -30,44 +31,37 @@ public static StorageProviderInfo Instance public ProviderInfo Info { get; private set; } - public bool CheckProviderIs(StorageProvider requiredProviders) - { - return (Provider & requiredProviders)!=0; - } + public IStorageTimeZoneProvider TimeZoneProvider { get; private set; } - public bool CheckProviderIsNot(StorageProvider disallowedProviders) - { - return (Provider & disallowedProviders)==0; - } + public bool CheckProviderIs(StorageProvider requiredProviders) => + (Provider & requiredProviders) != 0; - public bool CheckProviderVersionIsAtLeast(Version minimalVersion) - { - return Info.StorageVersion >= minimalVersion; - } + public bool CheckProviderIsNot(StorageProvider disallowedProviders) => + (Provider & disallowedProviders) == 0; - public bool CheckProviderVersionIsAtMost(Version maximalVersion) - { - return Info.StorageVersion <= maximalVersion; - } + public bool CheckProviderVersionIsAtLeast(Version minimalVersion) => + Info.StorageVersion >= minimalVersion; - public bool CheckAllFeaturesSupported(ProviderFeatures requiredFeatures) - { - return (Info.ProviderFeatures & requiredFeatures)==requiredFeatures; - } + public bool CheckProviderVersionIsAtMost(Version maximalVersion) => + Info.StorageVersion <= maximalVersion; - public bool CheckAllFeaturesNotSupported(ProviderFeatures disallowedFeatures) - { - return (Info.ProviderFeatures & disallowedFeatures)==0; - } + public bool CheckAllFeaturesSupported(ProviderFeatures requiredFeatures) => + (Info.ProviderFeatures & requiredFeatures) == requiredFeatures; - public bool CheckAnyFeatureSupported(ProviderFeatures requiredFeatures) - { - return (Info.ProviderFeatures & requiredFeatures)!=0; - } + public bool CheckAllFeaturesNotSupported(ProviderFeatures disallowedFeatures) => + (Info.ProviderFeatures & disallowedFeatures) == 0; + + public bool CheckAnyFeatureSupported(ProviderFeatures requiredFeatures) => + (Info.ProviderFeatures & requiredFeatures) != 0; + + public bool CheckAnyFeatureNotSupported(ProviderFeatures disallowedFeatures) => + (Info.ProviderFeatures & disallowedFeatures) != disallowedFeatures; - public bool CheckAnyFeatureNotSupported(ProviderFeatures disallowedFeatures) + public IDisposable ReplaceTimeZoneProvider(IStorageTimeZoneProvider newProvder) { - return (Info.ProviderFeatures & disallowedFeatures)!=disallowedFeatures; + var oldProvider = TimeZoneProvider; + TimeZoneProvider = newProvder; + return new Core.Disposable((b) => TimeZoneProvider = oldProvider); } private StorageProviderInfo() @@ -76,28 +70,46 @@ private StorageProviderInfo() var providerName = config.ConnectionInfo.Provider; Provider = ParseProvider(providerName); - Info = ProviderInfoBuilder.Build(providerName, TestSqlDriver.Create(config.ConnectionInfo)); + var sqlDriver = TestSqlDriver.Create(config.ConnectionInfo); + TimeZoneProvider = GetTimeZoneProvider(Provider, sqlDriver); + Info = ProviderInfoBuilder.Build(providerName, sqlDriver); } private static StorageProvider ParseProvider(string provider) { switch (provider) { - case WellKnown.Provider.SqlServer: - return StorageProvider.SqlServer; - case WellKnown.Provider.SqlServerCe: - return StorageProvider.SqlServerCe; - case WellKnown.Provider.PostgreSql: - return StorageProvider.PostgreSql; - case WellKnown.Provider.Oracle: - return StorageProvider.Oracle; - case WellKnown.Provider.MySql: - return StorageProvider.MySql; - case WellKnown.Provider.Firebird: - return StorageProvider.Firebird; - case WellKnown.Provider.Sqlite: - return StorageProvider.Sqlite; - default: - throw new ArgumentOutOfRangeException("provider"); + case WellKnown.Provider.SqlServer: + return StorageProvider.SqlServer; + case WellKnown.Provider.SqlServerCe: + return StorageProvider.SqlServerCe; + case WellKnown.Provider.PostgreSql: + return StorageProvider.PostgreSql; + case WellKnown.Provider.Oracle: + return StorageProvider.Oracle; + case WellKnown.Provider.MySql: + return StorageProvider.MySql; + case WellKnown.Provider.Firebird: + return StorageProvider.Firebird; + case WellKnown.Provider.Sqlite: + return StorageProvider.Sqlite; + default: + throw new ArgumentOutOfRangeException("provider"); + } + } + + private static IStorageTimeZoneProvider GetTimeZoneProvider(StorageProvider provider, SqlDriver sqlDriver) + { + switch (provider) { + case StorageProvider.SqlServer: + return new SqlServerTimeZoneProvider(sqlDriver); + case StorageProvider.PostgreSql: + return new PgSqlTimeZoneProvider(sqlDriver); + case StorageProvider.Oracle: + return new OracleTimeZoneProvider(sqlDriver); + case StorageProvider.Sqlite: + return new SqliteTimeZoneProvider(sqlDriver); + default: + return new LocalTimeZoneProvider(); } } } diff --git a/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs b/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs index 2d5202ad86..c45858ca46 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs @@ -33,8 +33,11 @@ public static class StorageProviderVersion public static Version PostgreSql92 = new Version(9, 2); public static Version PostgreSql100 = new Version(10, 0); public static Version PostgreSql110 = new Version(11, 0); + public static Version PostgreSql120 = new Version(12, 0); public static Version MySql55 = new Version(5, 5); public static Version MySql56 = new Version(5, 6); + public static Version MySql57 = new Version(5, 7); + public static Version Mysql80 = new Version(8, 0); } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Framework/StorageTimeZoneProvider.cs b/Orm/Xtensive.Orm.Tests.Framework/StorageTimeZoneProvider.cs new file mode 100644 index 0000000000..c5c587c42d --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Framework/StorageTimeZoneProvider.cs @@ -0,0 +1,131 @@ +// Copyright (C) 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 Xtensive.Sql; + +namespace Xtensive.Orm.Tests +{ + internal abstract class StorageTimeZoneProvider : IStorageTimeZoneProvider + { + public TimeSpan TimeZoneOffset { get; private set; } + + protected abstract TimeSpan GetServerTimezone(SqlDriver sqlDriver); + + public StorageTimeZoneProvider(SqlDriver sqlDriver) + { + TimeZoneOffset = GetServerTimezone(sqlDriver); + } + } + + internal sealed class LocalTimeZoneProvider : IStorageTimeZoneProvider + { + public TimeSpan TimeZoneOffset => TimeZoneInfo.Local.BaseUtcOffset; + } + + internal sealed class SqlServerTimeZoneProvider : StorageTimeZoneProvider + { + protected override TimeSpan GetServerTimezone(SqlDriver sqlDriver) + { + using (var c = sqlDriver.CreateConnection()) { + c.Open(); + using (var cmd = c.CreateCommand("SELECT SYSDATETIMEOFFSET();")) { + var dateTimeOffset = (DateTimeOffset) cmd.ExecuteScalar(); + return dateTimeOffset.Offset; + } + } + } + + public SqlServerTimeZoneProvider(SqlDriver sqlDriver) + : base(sqlDriver) + { + } + } + + internal sealed class OracleTimeZoneProvider : StorageTimeZoneProvider + { + protected override TimeSpan GetServerTimezone(SqlDriver sqlDriver) + { + var mappings = sqlDriver.TypeMappings[typeof(DateTimeOffset)]; + + TimeSpan value; + using (var connection = sqlDriver.CreateConnection()) { + connection.Open(); + var commandText = "select CAST(systimestamp AS timestamp with time zone) as \"local_time\" from dual"; + using (var cmd = connection.CreateCommand(commandText)) { + using (var reader = cmd.ExecuteReader()) { + _ = reader.Read(); + var dateTimeOffset = (DateTimeOffset) mappings.ReadValue(reader, 0); + value = dateTimeOffset.Offset; + } + } + connection.Close(); + } + return value; + } + + public OracleTimeZoneProvider(SqlDriver sqlDriver) + : base(sqlDriver) + { + } + } + + internal sealed class PgSqlTimeZoneProvider : StorageTimeZoneProvider + { + protected override TimeSpan GetServerTimezone(SqlDriver sqlDriver) + { + var mappings = sqlDriver.TypeMappings[typeof(DateTimeOffset)]; + var referenceDate = new DateTime(2016, 10, 23); + TimeSpan value; + using (var connection = sqlDriver.CreateConnection()) { + connection.Open(); + var commandText = $"select CAST('{referenceDate.ToString("yyyy-MM-dd HH:mm:ss")}' AS timestamp with time zone) as \"local_time\""; + using (var cmd = connection.CreateCommand(commandText)) { + using (var reader = cmd.ExecuteReader()) { + _ = reader.Read(); + var dateTimeOffset = (DateTimeOffset) mappings.ReadValue(reader, 0); + + value = dateTimeOffset.Offset; + if (dateTimeOffset.DateTime != referenceDate) { + value = value.Add(referenceDate - dateTimeOffset.DateTime); + } + } + } + connection.Close(); + } + return value; + } + + public PgSqlTimeZoneProvider(SqlDriver sqlDriver) : base(sqlDriver) + { + } + } + + internal sealed class SqliteTimeZoneProvider : StorageTimeZoneProvider + { + protected override TimeSpan GetServerTimezone(SqlDriver sqlDriver) + { + TimeSpan value; + using (var connection = sqlDriver.CreateConnection()) { + connection.Open(); + var commandText = "SELECT ((STRFTIME ('%s', '2016-01-01 12:00:00') - STRFTIME ('%s', '2016-01-01 12:00:00', 'UTC')) / 60)"; + using (var cmd = connection.CreateCommand(commandText)) { + using (var reader = cmd.ExecuteReader()) { + _ = reader.Read(); + var offsetInMinutes = reader.GetInt32(0); + + value = new TimeSpan(offsetInMinutes / 60, offsetInMinutes % 60, 0); + } + } + connection.Close(); + } + return value; + } + + public SqliteTimeZoneProvider(SqlDriver sqlDriver) + : base(sqlDriver) + { + } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs b/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs index c4bc8884bf..8ac5e363c5 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs @@ -50,13 +50,97 @@ public static System.Configuration.Configuration GetConfigurationForAssembly(thi return instanceOfTypeFromAssembly.GetType().Assembly.GetAssemblyConfiguration(); } + /// + /// Cuts down resolution of value if needed, according to current . + /// + /// The value to adjust. + /// New value with less resolution if the provider requires it, otherwise, untouched . + public static DateTime AdjustDateTimeForCurrentProvider(this DateTime origin) + { + var provider = StorageProviderInfo.Instance; + return AdjustDateTimeForProvider(origin, provider); + } + /// /// Cuts down resolution of value if needed. /// - /// The value to fix. - /// Type of provider. - /// New value with less resolution if requires it or untouched if the provider doesn't - public static DateTime FixDateTimeForProvider(this DateTime origin, StorageProviderInfo providerInfo) + /// The value to adjust. + /// Type of provider. + /// New value with less resolution if the provider requires it, otherwise, untouched . + public static DateTime AdjustDateTimeForProvider(this DateTime origin, StorageProviderInfo providerInfo) + { + var provider = providerInfo.Provider; + switch (provider) { + case StorageProvider.MySql: + return providerInfo.Info.StorageVersion < StorageProviderVersion.MySql56 + ? AdjustDateTime(origin, 0) + : AdjustDateTime(origin, 6); + case StorageProvider.Firebird: + return AdjustDateTime(origin, 4); + case StorageProvider.PostgreSql: + return AdjustDateTime(origin, 6); + case StorageProvider.Oracle: + return AdjustDateTime(origin, 7); + default: + return origin; + } + } + + /// + /// Cuts down fractions of value (nanoseconds, milliseconds, etc) to desired value. + /// + /// + /// Number of fractional points to keep (from 0 to 7). + /// Indicates whether value should be rounded after cutting off. + /// Result value. + /// Valid fractions should be between 0 and 7 (7 included). + public static DateTime AdjustDateTime(this DateTime origin, byte desiredFractions, bool requireRound = false) + { + if (desiredFractions == 7) { + return origin; + } + + const int baseDivider = 10_000_000; // no fractions + var currentDivider = baseDivider / (desiredFractions switch { + 0 => 1, + 1 => 10, + 2 => 100, + 3 => 1000, + 4 => 10000, + 5 => 100000, + 6 => 1000000, + _ => throw new ArgumentOutOfRangeException(nameof(desiredFractions)) + }); + + var ticks = origin.Ticks; + + var newTicks = requireRound + ? ((ticks % currentDivider) / (currentDivider / 10)) >= 5 + ? ticks - (ticks % currentDivider) + currentDivider + : ticks - (ticks % currentDivider) + : ticks - (ticks % currentDivider); + return new DateTime(newTicks); + } + +#if NET6_0_OR_GREATER + /// + /// Cuts down resolution of value if needed, according to current . + /// + /// The value to adjust. + /// New value with less resolution if the provider requires it, otherwise, untouched . + public static TimeOnly AdjustTimeOnlyForCurrentProvider(this TimeOnly origin) + { + var provider = StorageProviderInfo.Instance; + return AdjustTimeOnlyForProvider(origin, provider); + } + + /// + /// Cuts down resolution of value if needed. + /// + /// The value to adjust. + /// Type of provider. + /// New value with less resolution if the provider requires it, otherwise, untouched . + public static TimeOnly AdjustTimeOnlyForProvider(this TimeOnly origin, StorageProviderInfo providerInfo) { long? divider; var provider = providerInfo.Provider; @@ -80,7 +164,8 @@ public static DateTime FixDateTimeForProvider(this DateTime origin, StorageProvi } var ticks = origin.Ticks; var newTicks = ticks - (ticks % divider.Value); - return new DateTime(newTicks); + return new TimeOnly(newTicks); } +#endif } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs b/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs index abd968b9e2..5b6ed42d5b 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs @@ -15,6 +15,15 @@ public abstract class DateTimeIntervalTest : SqlTest { protected static readonly DateTime DefaultDateTime = new DateTime(2001, 2, 3, 4, 5, 6, 334); protected static readonly DateTime SecondDateTime = new DateTime(2000, 12, 11, 10, 9, 8, 765); +#if NET6_0_OR_GREATER + + protected static readonly DateOnly DefaultDateOnly = new DateOnly(2001, 2, 3); + protected static readonly DateOnly SecondDateOnly = new DateOnly(2000, 12, 11); + + protected static readonly TimeOnly DefaultTimeOnly = new TimeOnly(4, 5, 6, 334); + protected static readonly TimeOnly SecondTimeOnly = new TimeOnly(10, 9, 8, 765); +#endif + protected static readonly TimeSpan DefaultTimeSpan = new TimeSpan(10, 9, 8, 7, 652); protected static readonly int AddYearsConst = 5; protected static readonly int AddMonthsConst = 15; @@ -146,6 +155,120 @@ public virtual void DateTimeExtractDayOfYearTest() SqlDml.Extract(SqlDateTimePart.DayOfYear, DefaultDateTime), DefaultDateTime.DayOfYear); } +#if NET6_0_OR_GREATER + + [Test] + public virtual void DateOnlyAddYearsTest() + { + CheckEquality( + SqlDml.DateAddYears(DefaultDateOnly, AddYearsConst), + DefaultDateOnly.AddYears(AddYearsConst)); + } + + [Test] + public virtual void DateOnlyAddMonthsTest() + { + CheckEquality( + SqlDml.DateAddMonths(DefaultDateOnly, AddMonthsConst), + DefaultDateOnly.AddMonths(AddMonthsConst)); + } + + [Test] + public virtual void DateOnlyConstructTest() + { + CheckEquality( + SqlDml.DateConstruct(DefaultDateOnly.Year, DefaultDateOnly.Month, DefaultDateOnly.Day), + DefaultDateOnly); + } + + [Test] + public virtual void DateOnlyExtractYearTest() + { + CheckEquality( + SqlDml.Extract(SqlDatePart.Year, DefaultDateOnly), + DefaultDateOnly.Year); + } + + [Test] + public virtual void DateOnlyExtractMonthTest() + { + CheckEquality( + SqlDml.Extract(SqlDatePart.Month, DefaultDateOnly), + DefaultDateOnly.Month); + } + + [Test] + public virtual void DateOnlyExtractDayTest() + { + CheckEquality( + SqlDml.Extract(SqlDatePart.Day, DefaultDateOnly), + DefaultDateOnly.Day); + } + + [Test] + public virtual void DateOnlyExtractDayOfWeekTest() + { + CheckEquality( + SqlDml.Extract(SqlDatePart.DayOfWeek, DefaultDateOnly), + (int) DefaultDateOnly.DayOfWeek); + } + + [Test] + public virtual void DateOnlyExtractDayOfYearTest() + { + CheckEquality( + SqlDml.Extract(SqlDatePart.DayOfYear, DefaultDateOnly), + DefaultDateOnly.DayOfYear); + } + + [Test] + public virtual void TimeOnlyExtractHourTest() + { + CheckEquality( + SqlDml.Extract(SqlTimePart.Hour, DefaultTimeOnly), + DefaultTimeOnly.Hour); + } + + [Test] + public virtual void TimeOnlyExtractMinuteTest() + { + CheckEquality( + SqlDml.Extract(SqlTimePart.Minute, DefaultTimeOnly), + DefaultTimeOnly.Minute); + } + + [Test] + public virtual void TimeOnlyExtractSecondTest() + { + CheckEquality( + SqlDml.Extract(SqlTimePart.Second, DefaultTimeOnly), + DefaultTimeOnly.Second); + } + + [Test] + public virtual void TimeOnlyExtractMillisecondTest() + { + CheckEquality( + SqlDml.Extract(SqlTimePart.Millisecond, DefaultTimeOnly), + DefaultTimeOnly.Millisecond); + } + + [Test] + public virtual void TimeOnlyConstructTest() + { + CheckEquality( + SqlDml.TimeConstruct(DefaultTimeOnly.Hour, DefaultTimeOnly.Minute, DefaultTimeOnly.Second, DefaultTimeOnly.Millisecond), + DefaultTimeOnly); + } + + [Test] + public virtual void TimeOnlySubtractTimeOnlyTest() + { + CheckEquality( + SqlDml.TimeMinusTime(DefaultTimeOnly, SecondTimeOnly), + DefaultTimeOnly - SecondTimeOnly); + } +#endif [Test] public virtual void IntervalConstructTest() diff --git a/Orm/Xtensive.Orm.Tests.Sql/ExtractorTestBase.cs b/Orm/Xtensive.Orm.Tests.Sql/ExtractorTestBase.cs new file mode 100644 index 0000000000..cf9b41ccc8 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/ExtractorTestBase.cs @@ -0,0 +1,522 @@ +// Copyright (C) 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; +using System.Linq; +using System.Text; +using Microsoft.VisualBasic; +using NUnit.Framework; +using Xtensive.Sql; +using Xtensive.Sql.Info; +using Xtensive.Sql.Model; + +namespace Xtensive.Orm.Tests.Sql +{ + [TestFixture] + public abstract class ExtractorTestBase : SqlTest + { + protected const int CharLength = 4; + protected const int VarCharLength = 6; + + protected const int BinaryLength = 3; + protected const int VarBinaryLength = 6; + + protected const int DecimalScale = 4; + protected const int DecimalPrecision = 12; + + private readonly List schemasToCheck = new(); + private readonly List cleanups = new(); + + protected readonly Dictionary TypeToColumnName = new(); + + protected virtual bool CheckContstraintExtracted => true; + protected virtual bool SeqStartEqualsToMin => false; + + protected bool IgnoreTests { get; set; } = false; + protected bool NonKeyColumnsSupported => Driver.ServerInfo.Index.Features.HasFlag(IndexFeatures.NonKeyColumns); + protected bool PartialIndexesSupported => Driver.ServerInfo.Index.Features.HasFlag(IndexFeatures.Filtered); + protected bool FulltextIndexesSupported => Driver.ServerInfo.Index.Features.HasFlag(IndexFeatures.FullText); + protected bool SortOrderSupported => Driver.ServerInfo.Index.Features.HasFlag(IndexFeatures.SortOrder); + + + protected override void TestFixtureSetUp() + { + if (IgnoreTests) { + throw new IgnoreException(string.Empty); + } + base.TestFixtureSetUp(); + + PopulateTypeToColumnName(); + PopulateSchemasToCheck(); + } + + protected override void TestFixtureTearDown() + { + if (!IgnoreTests) { + foreach (var query in cleanups) { + ExecuteQueryLineByLine(query, true); + } + } + + base.TestFixtureTearDown(); + } + + [Test] + public void SchemaExtractionTest() + { + Assert.That(ExtractDefaultSchema(), Is.Not.Null); + + var catalog = ExtractCatalog(); + if (StorageProviderInfo.Instance.CheckAllFeaturesNotSupported(Providers.ProviderFeatures.Multischema)) { + Assert.That(schemasToCheck.All(s => catalog.Schemas[s] != null), Is.True); + } + } + + protected abstract string GetTypesExtractionPrepareScript(string tableName); + protected abstract string GetTypesExtractionCleanUpScript(string tableName); + + // Test expects a table with the given name containing column for each supported by + // the tested storage, column names for each SqlType are in TypeToColumnName dictionary + [Test] + public void TypeExtractionTest() + { + var createTableQuery = GetTypesExtractionPrepareScript("dataTypesTestTable"); + RegisterCleanupScript(GetTypesExtractionCleanUpScript, "dataTypesTestTable"); + ExecuteQuery(createTableQuery); + + var testTable = ExtractDefaultSchema().Tables["dataTypesTestTable"]; + + foreach (var keyValue in TypeToColumnName) { + var sqlType = keyValue.Key; + var columnName = keyValue.Value; + var tableColumn = testTable.TableColumns[columnName]; + if (tableColumn == null) { + continue; + } + Assert.That(tableColumn.DataType.Type, Is.EqualTo(sqlType)); + if (sqlType == SqlType.Char || sqlType == SqlType.VarChar + || sqlType == SqlType.Binary || sqlType == SqlType.VarBinary) { + Assert.That(tableColumn.DataType.Length, Is.EqualTo(GetExpectedLength(sqlType))); + } + if (sqlType == SqlType.Decimal && StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.Sqlite)) { + Assert.That(tableColumn.DataType.Precision, Is.EqualTo(DecimalPrecision)); + Assert.That(tableColumn.DataType.Scale, Is.EqualTo(DecimalScale)); + } + } + + + static int GetExpectedLength(SqlType sqlType1) + { + if (sqlType1 == SqlType.Char) { + return CharLength; + } + else if (sqlType1 == SqlType.VarChar) { + return VarCharLength; + } + else if (sqlType1 == SqlType.Binary) { + return BinaryLength; + } + else if (sqlType1 == SqlType.VarBinary) { + return VarBinaryLength; + } + throw new ArgumentOutOfRangeException(nameof(sqlType1)); + } + } + + + protected abstract string GetForeignKeyExtractionPrepareScript(); + protected abstract string GetForeignKeyExtractionCleanUpScript(); + + // Test expects Storage variant of following structure + // CREATE TABLE B1 (b_id int primary key); + // CREATE TABLE A1 (b_id int references B1(b_id)); + // CREATE TABLE B2 (b_id_1 int, b_id_2 int, + // CONSTRAINT [B2_PK] PRIMARY KEY CLUSTERED (b_id_1, b_id_2) ON [PRIMARY]); + // CREATE TABLE A2 (b_id_1 int, b_id_2 int, + // CONSTRAINT [A2_FK] FOREIGN KEY (b_id_1, b_id_2) + // REFERENCES B2 (b_id_1, b_id_2) ON DELETE CASCADE ON UPDATE NO ACTION); + // CREATE TABLE B3 (b_id_1 int, b_id_2 int, b_id_3 int, + // CONSTRAINT [B3_PK] PRIMARY KEY CLUSTERED (b_id_1, b_id_2, b_id_3) ON [PRIMARY]); + // CREATE TABLE A3 (A_col1 int, b_id_3 int, b_id_1 int, b_id_2 int, + // CONSTRAINT [A3_FK] FOREIGN KEY (b_id_1, b_id_2, b_id_3) + // REFERENCES B3 (b_id_1, b_id_2, b_id_3) ON DELETE NO ACTION ON UPDATE CASCADE); + [Test] + public void ForeignKeyExtractionTest() + { + var query = GetForeignKeyExtractionPrepareScript(); + RegisterCleanupScript(GetForeignKeyExtractionCleanUpScript); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + + // Validating. + var fk1 = (ForeignKey) schema.Tables["A1"].TableConstraints[0]; + Assert.IsNotNull(fk1); + Assert.IsTrue(fk1.Columns[0].Name.Equals("b_id", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk1.ReferencedColumns[0].Name.Equals("b_id", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk1.ReferencedColumns.Count == 1); + Assert.IsTrue(fk1.Columns.Count == 1); + + var fk2 = (ForeignKey) schema.Tables["A2"].TableConstraints[0]; + Assert.IsNotNull(fk1); + Assert.IsTrue(fk2.Columns[0].Name.Equals("b_id_1", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk2.ReferencedColumns[0].Name.Equals("b_id_1", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk2.Columns[1].Name.Equals("b_id_2", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk2.ReferencedColumns[1].Name.Equals("b_id_2", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk2.ReferencedColumns.Count == 2); + Assert.IsTrue(fk2.Columns.Count == 2); + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.Oracle)) { + Assert.IsTrue(fk2.OnDelete == ReferentialAction.SetNull); + } + else { + Assert.IsTrue(fk2.OnDelete == ReferentialAction.Cascade); + Assert.IsTrue(fk2.OnUpdate == ReferentialAction.NoAction); + } + + + var fk3 = (ForeignKey) schema.Tables["A3"].TableConstraints[0]; + Assert.IsNotNull(fk3); + Assert.IsTrue(fk3.Columns[0].Name.Equals("b_id_1", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk3.ReferencedColumns[0].Name.Equals("b_id_1", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk3.Columns[1].Name.Equals("b_id_2", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk3.ReferencedColumns[1].Name.Equals("b_id_2", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk3.Columns[2].Name.Equals("b_id_3", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk3.ReferencedColumns[2].Name.Equals("b_id_3", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(fk3.ReferencedColumns.Count == 3); + Assert.IsTrue(fk3.Columns.Count == 3); + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.Oracle)) { + Assert.IsTrue(fk3.OnDelete == ReferentialAction.Cascade); + } + else { + Assert.IsTrue(fk3.OnDelete == ReferentialAction.NoAction); + Assert.IsTrue(fk3.OnUpdate == ReferentialAction.Cascade); + } + } + + + protected abstract string GetIndexExtractionPrepareScript(string tableName); + protected abstract string GetIndexExtractionCleanUpScript(string tableName); + + // Test expects storage variant of following structure + // CREATE TABLE table1 (column1 int, column2 int); + // CREATE INDEX table1_index1_desc_asc on table1 (column1 desc, column2 asc); + // CREATE UNIQUE INDEX table1_index1_u_asc_desc on table1 (column1 asc, column2 desc); + // CREATE UNIQUE INDEX table1_index_with_included_columns on table1 (column1 asc) include (column2); + // + // if non-key columns are not supported then skip their declaration. + [Test] + public void IndexExtractionTest() + { + var query = GetIndexExtractionPrepareScript("table1"); + RegisterCleanupScript(GetIndexExtractionCleanUpScript, "table1"); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + + Assert.IsTrue(schema.Tables["table1"] != null); + Assert.IsNotNull(schema.Tables["table1"].Indexes["table1_index1_desc_asc"]); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns.Count == 2); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[0].Name.Equals("column1", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[1].Name.Equals("column2", StringComparison.OrdinalIgnoreCase)); + if (SortOrderSupported) { + Assert.IsTrue(!schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[0].Ascending); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[1].Ascending); + } + else { + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[0].Ascending); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[1].Ascending); + } + + + Assert.IsNotNull(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"]); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns.Count == 2); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[0].Name.Equals("column1", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[1].Name.Equals("column2", StringComparison.OrdinalIgnoreCase)); + + if (SortOrderSupported) { + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[0].Ascending); + Assert.IsTrue(!schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[1].Ascending); + } + else { + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[0].Ascending); + Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[1].Ascending); + } + + if (NonKeyColumnsSupported) { + Assert.IsNotNull(schema.Tables["table1"].Indexes["table1_index_with_included_columns"]); + Assert.AreEqual(1, schema.Tables["table1"].Indexes["table1_index_with_included_columns"].Columns.Count, + "Key columns"); + + Assert.AreEqual(1, schema.Tables["table1"].Indexes["table1_index_with_included_columns"].NonkeyColumns.Count, + "Included columns"); + } + } + + + protected virtual string GetPartialIndexExtractionPrepareScript(string tableName) => null; + protected virtual string GetPartialIndexExtractionCleanUpScript(string tableName) => null; + + [Test] + public void PartialIndexExtractionTest() + { + Require.AllFeaturesSupported(Providers.ProviderFeatures.PartialIndexes); + + var query = GetPartialIndexExtractionPrepareScript("partialIndexTestTable"); + RegisterCleanupScript(GetPartialIndexExtractionCleanUpScript, "partialIndexTestTable"); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + + Assert.IsTrue(schema.Tables["partialIndexTestTable"] != null); + Assert.IsNotNull(schema.Tables["partialIndexTestTable"].Indexes["partialIndexTestTable_index1_filtered"]); + Assert.IsTrue(schema.Tables["partialIndexTestTable"].Indexes["partialIndexTestTable_index1_filtered"].Columns.Count == 2); + Assert.IsTrue(schema.Tables["partialIndexTestTable"].Indexes["partialIndexTestTable_index1_filtered"].Columns[0].Name == "column1"); + Assert.IsNotNull(schema.Tables["partialIndexTestTable"].Indexes["partialIndexTestTable_index1_filtered"].Where); + } + + + protected virtual string GetFulltextIndexExtractionPrepareScript(string tableName) => null; + protected virtual string GetFulltextIndexExtractionCleanUpScript(string tableName) => null; + + // Test expects storage variant of following structure + // CREATE TABLE fullTextTestTable (Id int NOT NULL, + // Name nvarchar(100) NULL, + // Comments nvarchar(1000) NULL, + // CONSTRAINT [PK_fullTextTestTable] PRIMARY KEY CLUSTERED (Id) ON [PRIMARY]); + // + // CREATE FULLTEXT INDEX ON fullTextTestTable(Name LANGUAGE 1033, Comments LANGUAGE 1033) + // KEY INDEX PK_fullTextTestTable; + [Test] + public void FulltextIndexExtractionTest() + { + Require.AllFeaturesSupported(Providers.ProviderFeatures.FullText); + + var query = GetFulltextIndexExtractionPrepareScript("fullTextTestTable"); + RegisterCleanupScript(GetFulltextIndexExtractionCleanUpScript, "fullTextTestTable"); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + var ftIndex = (FullTextIndex) schema.Tables["fullTextTestTable"].Indexes.FirstOrDefault(i => i.IsFullText); + Assert.IsNotNull(ftIndex); + Assert.That(ftIndex.Columns.Count, Is.EqualTo(2)); + Assert.That( + ftIndex.Columns.All(c => c.Languages.Count == 1 && c.Languages[0].Name.Equals("English", StringComparison.OrdinalIgnoreCase)), + Is.True); + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.SqlServer)) { + Assert.That(ftIndex.FullTextCatalog, Is.Null); + Assert.That(ftIndex.ChangeTrackingMode, Is.EqualTo(ChangeTrackingMode.Auto)); + Assert.That(ftIndex.UnderlyingUniqueIndex, Is.EqualTo("PK_fullTextTestTable")); + } + } + + + protected virtual string GetUniqueConstraintExtractionPrepareScript(string tableName) => ""; + protected virtual string GetUniqueConstraintExtractionCleanUpScript(string tableName) => ""; + + // Test expects storage variant of following structure + // CREATE TABLE uniqueConstraintTable ( + // col_11 int, col_12 int, col_13 int, + // col_21 int, col_22 int, col_23 int, + // CONSTRAINT A_UNIQUE_1 UNIQUE(col_11,col_12,col_13), + // CONSTRAINT A_UNIQUE_2 UNIQUE(col_21,col_22,col_23)) + [Test] + public void UniqueConstraintExtractionTest() + { + Require.ProviderIsNot(StorageProvider.Sqlite); + + var query = GetUniqueConstraintExtractionPrepareScript("uniqueConstraintTable"); + RegisterCleanupScript(GetUniqueConstraintExtractionCleanUpScript, "uniqueConstraintTable"); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + + // Validating. + var uniqueConstraint = (UniqueConstraint) schema.Tables["uniqueConstraintTable"].TableConstraints["A_UNIQUE_1"]; + Assert.IsNotNull(uniqueConstraint); + Assert.IsTrue(uniqueConstraint.Columns[0].Name.Equals("col_11", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(uniqueConstraint.Columns[1].Name.Equals("col_12", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(uniqueConstraint.Columns[2].Name.Equals("col_13", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(uniqueConstraint.Columns.Count == 3); + + uniqueConstraint = (UniqueConstraint) schema.Tables["uniqueConstraintTable"].TableConstraints["A_UNIQUE_2"]; + Assert.IsNotNull(uniqueConstraint); + Assert.IsTrue(uniqueConstraint.Columns[0].Name.Equals("col_21", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(uniqueConstraint.Columns[1].Name.Equals("col_22", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(uniqueConstraint.Columns[2].Name.Equals("col_23", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(uniqueConstraint.Columns.Count == 3); + } + + + protected virtual string GetCheckConstraintExtractionPrepareScript(string tableName) => null; + protected virtual string GetCheckConstraintExtractionCleanUpScript(string tableName) => null; + + // Test expects storage variant of following structure + // CREATE TABLE checkConstraintTable ( + // col_11 int, col_12 int, col_13 int, + // col_21 int, col_22 int, col_23 int, + // CONSTRAINT A_CHECK_1 CHECK(col_11 > 0 OR col_12 > 10 OR col_13 > 20), + // CONSTRAINT A_CHECK_2 CHECK(col_21 <0 AND col_22 < 10 AND col_23 < 20)) + [Test] + public void CheckConstraintExtractionTest() + { + if (!CheckContstraintExtracted) + throw new IgnoreException("CheckConstraints are not extracted"); + + var query = GetCheckConstraintExtractionPrepareScript("checkConstraintTable"); + RegisterCleanupScript(GetCheckConstraintExtractionCleanUpScript, "checkConstraintTable"); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + + // Validating. + var checkConstraint = (CheckConstraint) schema.Tables["checkConstraintTable"].TableConstraints["A_CHECK_1"]; + Assert.IsNotNull(checkConstraint); + Assert.IsNotNull(checkConstraint.Condition); + + checkConstraint = (CheckConstraint) schema.Tables["checkConstraintTable"].TableConstraints["A_CHECK_2"]; + Assert.IsNotNull(checkConstraint); + Assert.IsNotNull(checkConstraint.Condition); + } + + + protected virtual string GetCheckSequenceExtractionPrepareScript() + { + return "CREATE SEQUENCE \"seq1\" START WITH 11 INCREMENT BY 100 MINVALUE 10 MAXVALUE 10000 NO CYCLE;" + + "CREATE SEQUENCE \"seq2\" START WITH 110 INCREMENT BY 10 MINVALUE 10 MAXVALUE 100000 CYCLE;"; + } + + protected virtual string GetCheckSequenceExtractionCleanupScript() => "DROP SEQUENCE \"seq1\"; DROP SEQUENCE \"seq2\""; + + [Test] + public void SequenceExtractionTest() + { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Sequences); + + var query = GetCheckSequenceExtractionPrepareScript(); + RegisterCleanupScript(GetCheckSequenceExtractionCleanupScript); + ExecuteQuery(query); + + var schema = ExtractDefaultSchema(); + Assert.That(schema.Sequences["seq1"], Is.Not.Null); + var seq1Descriptor = schema.Sequences["seq1"].SequenceDescriptor; + if (StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.Oracle)) + Assert.That(seq1Descriptor.StartValue, Is.EqualTo(SeqStartEqualsToMin ? 10 : 11)); + Assert.That(seq1Descriptor.Increment, Is.EqualTo(100)); + Assert.That(seq1Descriptor.MinValue, Is.EqualTo(10)); + Assert.That(seq1Descriptor.MaxValue, Is.EqualTo(10000)); + + Assert.That(seq1Descriptor.IsCyclic, Is.False); + + Assert.That(schema.Sequences["seq2"], Is.Not.Null); + var seq2Descriptor = schema.Sequences["seq2"].SequenceDescriptor; + if (StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.Oracle)) + Assert.That(seq2Descriptor.StartValue, Is.EqualTo(SeqStartEqualsToMin ? 10 : 110)); + Assert.That(seq2Descriptor.Increment, Is.EqualTo(10)); + Assert.That(seq2Descriptor.MinValue, Is.EqualTo(10)); + Assert.That(seq2Descriptor.MaxValue, Is.EqualTo(100000)); + + Assert.That(seq2Descriptor.IsCyclic, Is.True); + } + + private void PopulateTypeToColumnName() + { + TypeToColumnName[SqlType.Boolean] = "boolean_column"; + TypeToColumnName[SqlType.Int8] = "int8_column"; + TypeToColumnName[SqlType.Int16] = "int16_column"; + TypeToColumnName[SqlType.Int32] = "int32_column"; + TypeToColumnName[SqlType.Int64] = "int64_column"; + TypeToColumnName[SqlType.UInt8] = "uint8_column"; + TypeToColumnName[SqlType.UInt16] = "uint16_column"; + TypeToColumnName[SqlType.UInt32] = "uint32_column"; + TypeToColumnName[SqlType.UInt64] = "uint64_column"; + TypeToColumnName[SqlType.Decimal] = $"decimal_p{DecimalPrecision}_s{DecimalScale}_column"; + + TypeToColumnName[SqlType.Float] = "float_column"; + TypeToColumnName[SqlType.Double] = "double_column"; + + TypeToColumnName[SqlType.Interval] = "interval_column"; + TypeToColumnName[SqlType.DateTime] = "datetime_column"; + TypeToColumnName[SqlType.DateTimeOffset] = "datetimeoffset_column"; +#if NET6_0_OR_GREATER + TypeToColumnName[SqlType.Date] = "date_column"; + TypeToColumnName[SqlType.Time] = "time_column"; +#endif + + TypeToColumnName[SqlType.Char] = $"char_l{CharLength}_column"; + TypeToColumnName[SqlType.VarChar] = $"varchar_l{VarCharLength}_column"; + TypeToColumnName[SqlType.VarCharMax] = "varcharmax_column"; + + TypeToColumnName[SqlType.Binary] = $"binary_l{BinaryLength}_column"; + TypeToColumnName[SqlType.VarBinary] = $"varbinary_l{VarBinaryLength}_column"; + TypeToColumnName[SqlType.VarBinaryMax] = "varbinarymax_column"; + + TypeToColumnName[SqlType.Guid] = "guid_column"; + + PopulateCustomTypeToColumnName(); + } + + protected virtual void PopulateCustomTypeToColumnName() + { + } + + private void PopulateSchemasToCheck() + { + if (!IsMultischemaSupported) { + return; + } + + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.PostgreSql)) { + schemasToCheck.Add(WellKnownSchemas.PgSqlDefalutSchema); + } + else { + schemasToCheck.Add(WellKnownSchemas.SqlServerDefaultSchema); + } + schemasToCheck.Add(WellKnownSchemas.Schema1); + schemasToCheck.Add(WellKnownSchemas.Schema2); + schemasToCheck.Add(WellKnownSchemas.Schema3); + schemasToCheck.Add(WellKnownSchemas.Schema4); + schemasToCheck.Add(WellKnownSchemas.Schema5); + schemasToCheck.Add(WellKnownSchemas.Schema6); + schemasToCheck.Add(WellKnownSchemas.Schema7); + schemasToCheck.Add(WellKnownSchemas.Schema8); + schemasToCheck.Add(WellKnownSchemas.Schema9); + schemasToCheck.Add(WellKnownSchemas.Schema10); + schemasToCheck.Add(WellKnownSchemas.Schema11); + schemasToCheck.Add(WellKnownSchemas.Schema12); + } + + protected void ExecuteQuery(string sqlQuery) + { + if (string.IsNullOrEmpty(sqlQuery)) + return; + if(Driver.ServerInfo.Query.Features.HasFlag(QueryFeatures.DdlBatches)) { + _ = ExecuteNonQuery(sqlQuery); + } + else { + ExecuteQueryLineByLine(sqlQuery); + } + } + + protected void ExecuteQueryLineByLine(string sqlQuery, bool ignoreExceptions = false) + { + if (string.IsNullOrEmpty(sqlQuery)) + return; + foreach (var q in sqlQuery.Split(';')) { + if (string.IsNullOrEmpty(q)) + continue; + try { + _ = ExecuteNonQuery(q); + } + catch { + if (!ignoreExceptions) + throw; + } + } + } + + protected void RegisterCleanupScript(Func func) => cleanups.Add(func()); + protected void RegisterCleanupScript(Func func, string param) => cleanups.Add(func(param)); + } +} diff --git a/Orm/Xtensive.Orm.Tests.Sql/Firebird/ExtractorTest.cs b/Orm/Xtensive.Orm.Tests.Sql/Firebird/ExtractorTest.cs index cd15f3e060..3d164fb888 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/Firebird/ExtractorTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/Firebird/ExtractorTest.cs @@ -1,39 +1,97 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2011-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: Csaba Beer // Created: 2011.01.13 using NUnit.Framework; using System; +using System.Text; +using Xtensive.Sql; namespace Xtensive.Orm.Tests.Sql.Firebird { - public class ExtractorTest : SqlTest + public class ExtractorTest : ExtractorTestBase { - [Test] - public void BaseTest() + protected override bool CheckContstraintExtracted => false; + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.Firebird); + + protected override string GetTypesExtractionPrepareScript(string tableName) { - var schema = ExtractDefaultSchema(); + var dataTypes = Driver.ServerInfo.DataTypes; + var sb = new StringBuilder(); + _ = sb.Append($"CREATE TABLE {tableName} ("); + sb.AppendLine($"{TypeToColumnName[SqlType.Int16]} smallint,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int32]} integer,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int64]} bigint,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Decimal]} decimal({DecimalPrecision}, {DecimalScale}),"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Float]} float,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Double]} double precision,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.DateTime]} timestamp,"); +#if NET6_0_OR_GREATER + sb.AppendLine($"{TypeToColumnName[SqlType.Date]} date,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Time]} time,"); +#endif + + sb.AppendLine($"{TypeToColumnName[SqlType.Char]} char({CharLength}),"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarChar]} varchar({VarCharLength}),"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarCharMax]} blob sub_type 1,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.VarBinaryMax]} blob sub_type 0"); + sb.AppendLine(")"); + + return sb.ToString(); } + protected override string GetTypesExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; - public override void SetUp() + protected override string GetForeignKeyExtractionPrepareScript() { - base.SetUp(); - // hack because Visual Nunit doesn't use TestFixtureSetUp attribute, just SetUp attribute - RealTestFixtureSetUp(); + return "CREATE TABLE B1 (b_id integer primary key);" + + "CREATE TABLE A1 (b_id integer references B1(b_id));" + + "CREATE TABLE B2 (b_id_1 integer, b_id_2 integer, " + + " CONSTRAINT B2_PK PRIMARY KEY (b_id_1, b_id_2));" + + "CREATE TABLE A2 (b_id_1 integer, b_id_2 integer," + + " CONSTRAINT A2_FK FOREIGN KEY (b_id_1, b_id_2)" + + " REFERENCES B2 (b_id_1, b_id_2) ON DELETE CASCADE ON UPDATE NO ACTION);" + + "CREATE TABLE B3 (b_id_1 integer, b_id_2 integer, b_id_3 integer," + + " CONSTRAINT B3_PK PRIMARY KEY(b_id_1, b_id_2, b_id_3));" + + "CREATE TABLE A3 (A_col1 integer, b_id_3 integer, b_id_1 integer, b_id_2 integer," + + " CONSTRAINT A3_FK FOREIGN KEY (b_id_1, b_id_2, b_id_3)" + + " REFERENCES B3 (b_id_1, b_id_2, b_id_3) ON DELETE NO ACTION ON UPDATE CASCADE);"; } - public override void TearDown() + protected override string GetForeignKeyExtractionCleanUpScript() => + "drop table A1;" + + "\n drop table A2;" + + "\n drop table A3;" + + "\n drop table B1;" + + "\n drop table B2;" + + "\n drop table B3;"; + + + protected override string GetIndexExtractionPrepareScript(string tableName) { - base.TearDown(); - // hack because Visual Nunit doesn't use TestFixtureTearDown attribute, just TearDown attribute - RealTestFixtureTearDown(); + return + $"CREATE TABLE {tableName} (column1 integer, column2 integer);" + + $"\n CREATE ASC INDEX {tableName}_index1_desc_asc on {tableName} (column1, column2);" + + $"\n CREATE UNIQUE ASC INDEX {tableName}_index1_u_asc_desc on {tableName} (column1, column2);"; } - protected override void CheckRequirements() + protected override string GetIndexExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; + + + protected override string GetUniqueConstraintExtractionPrepareScript(string tableName) { - Require.ProviderIs(StorageProvider.Firebird); + return $"CREATE TABLE {tableName} (" + + "\n col_11 integer, col_12 integer, col_13 integer," + + "\n col_21 integer, col_22 integer, col_23 integer," + + "\n CONSTRAINT A_UNIQUE_1 UNIQUE(col_11,col_12,col_13), " + + "\n CONSTRAINT A_UNIQUE_2 UNIQUE(col_21,col_22,col_23));"; } + + protected override string GetUniqueConstraintExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; } } diff --git a/Orm/Xtensive.Orm.Tests.Sql/MySQL/ExtractorTest.cs b/Orm/Xtensive.Orm.Tests.Sql/MySQL/ExtractorTest.cs index c7a850c971..c4d01ff879 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/MySQL/ExtractorTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/MySQL/ExtractorTest.cs @@ -1,180 +1,127 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2011-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: Malisa Ncube // Created: 2011.02.25 -using System; -using System.Data.Common; -using NUnit.Framework; +using System.Text; using Xtensive.Sql; -using Xtensive.Sql.Model; - + namespace Xtensive.Orm.Tests.Sql.MySQL { - [TestFixture] - public class ExtractorTest : SqlTest + public class ExtractorTest : ExtractorTestBase + { + protected override bool CheckContstraintExtracted => false; + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.MySql); + + #region Base test class members + protected override string GetTypesExtractionPrepareScript(string tableName) + { + var dataTypes = Driver.ServerInfo.DataTypes; + var sb = new StringBuilder(); + _ = sb.Append($"CREATE TABLE {tableName} ("); + sb.AppendLine($"{TypeToColumnName[SqlType.Boolean]} boolean NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int8]} tinyint NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Int16]} smallint NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int32]} int NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int64]} bigint NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Decimal]} decimal({DecimalPrecision}, {DecimalScale}) NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Float]} float NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Double]} double precision NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.DateTime]} datetime NULL,"); +#if NET6_0_OR_GREATER + sb.AppendLine($"{TypeToColumnName[SqlType.Date]} date NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Time]} time NULL,"); +#endif + + sb.AppendLine($"{TypeToColumnName[SqlType.Char]} char({CharLength}) NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarChar]} varchar({VarCharLength}) NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarCharMax]} longtext NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Binary]} binary({BinaryLength}) NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarBinary]} varbinary({VarBinaryLength}) NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarBinaryMax]} longblob NULL"); + + sb.AppendLine(");"); + + return sb.ToString(); + } + protected override string GetTypesExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; + + protected override string GetForeignKeyExtractionPrepareScript() + { + return "CREATE TABLE B1 (b_id int primary key);" + + "\n CREATE TABLE A1 (b_id int, CONSTRAINT A1_FK FOREIGN KEY (b_id) REFERENCES B1 (b_id));" + + "\n CREATE TABLE B2 (b_id_1 int, b_id_2 int, CONSTRAINT B2_PK PRIMARY KEY (b_id_1, b_id_2)); " + + "\n CREATE TABLE A2 (b_id_1 int, b_id_2 int," + + " CONSTRAINT A2_FK FOREIGN KEY (b_id_1, b_id_2)" + + " REFERENCES B2 (b_id_1, b_id_2) ON DELETE CASCADE ON UPDATE NO ACTION); " + + "\n CREATE TABLE B3 (b_id_1 int, b_id_2 int, b_id_3 int," + + " CONSTRAINT B3_PK PRIMARY KEY (b_id_1, b_id_2, b_id_3)); " + + "\n CREATE TABLE A3 (A_col1 int, b_id_3 int, b_id_1 int, b_id_2 int," + + " CONSTRAINT A3_FK FOREIGN KEY (b_id_1, b_id_2, b_id_3)" + + " REFERENCES B3 (b_id_1, b_id_2, b_id_3) ON DELETE NO ACTION ON UPDATE CASCADE); "; + } + protected override string GetForeignKeyExtractionCleanUpScript() => + "drop table if exists A1;" + + "\n drop table if exists A2;" + + "\n drop table if exists A3;" + + "\n drop table if exists B1;" + + "\n drop table if exists B2;" + + "\n drop table if exists B3;"; + + protected override string GetIndexExtractionPrepareScript(string tableName) + { + return + $"CREATE TABLE {tableName} (column1 int, column2 int);" + + $"\n CREATE INDEX {tableName}_index1_desc_asc on {tableName} (column1 desc, column2 asc);" + + $"\n CREATE UNIQUE INDEX {tableName}_index1_u_asc_desc on {tableName} (column1 asc, column2 desc);"; + } + protected override string GetIndexExtractionCleanUpScript(string tableName) => $"drop table if exists {tableName};"; + + protected override string GetPartialIndexExtractionPrepareScript(string tableName) + { + return + $"CREATE TABLE {tableName} (column1 int, column2 int);" + + $"\n CREATE INDEX {tableName}_index1_filtered on {tableName} (column1, column2) WHERE column1 > 10;"; + } + protected override string GetPartialIndexExtractionCleanUpScript(string tableName) => $"drop table if exists {tableName};"; + + protected override string GetFulltextIndexExtractionPrepareScript(string tableName) + { + return $"CREATE TABLE {tableName} (Id int NOT NULL," + + "\n Name nvarchar(100) NULL," + + "\n Comments nvarchar(1000) NULL," + + $"\n CONSTRAINT [PK_{tableName}] PRIMARY KEY CLUSTERED (Id) ON [PRIMARY]);" + + $"\n CREATE FULLTEXT INDEX ON {tableName}(Name LANGUAGE 1033, Comments LANGUAGE 1033)" + + $"\n KEY INDEX PK_{tableName} WITH CHANGE_TRACKING AUTO;"; + } + protected override string GetFulltextIndexExtractionCleanUpScript(string tableName) => $"drop table if exists {tableName};"; + + protected override string GetUniqueConstraintExtractionPrepareScript(string tableName) { - #region Test DDL - - const string DropBadSetTableQuery = @"drop table if exists dataTypesBadSetTable"; - private const string CreateBadSetTableQuery = - @"CREATE TABLE dataTypesBadSetTable ( - set_162 SET('a', 'b', 'c', 'd') - )"; - - const string DropBadEnumTableQuery = @"drop table if exists dataTypesBadEnumTable"; - private const string CreateBadEnumTableQuery = - @"CREATE TABLE dataTypesBadEnumTable ( - num_231 ENUM('small', 'medium', 'large') - )"; - - const string DropBadBitTableQuery = @"drop table if exists dataTypesBadBitTable"; - private const string CreateBadBitTableQuery = - @"CREATE TABLE dataTypesBadBitTable ( - bit_l1 bit NULL - )"; - - const string DropGoodTableQuery = @"drop table if exists dataTypesGoodTable"; - const string CreateGoodTableQuery = - @"CREATE TABLE dataTypesGoodTable ( - int_l4 int NULL , - binary_l50 binary (50) NULL , - char_10 char (10) COLLATE utf8_unicode_ci NULL , - datetime_l8 datetime NULL , - decimal_p18_s0 decimal(18, 0) NULL , - decimal_p12_s11_l9 decimal(12, 11) NULL , - float_p53 float NULL , - image_16 blob NULL , - image_17 tinyblob NULL , - image_18 mediumblob NULL , - image_19 longblob NULL , - money_p19_s4_l8 decimal(18,2) NULL , - nchar_l100 nchar (100) COLLATE utf8_unicode_ci NULL , - tiny_text_01 tinytext COLLATE utf8_unicode_ci NULL , - long_text_01 longtext COLLATE utf8_unicode_ci NULL , - medium_text_01 mediumtext COLLATE utf8_unicode_ci NULL , - numeric_p5_s5 numeric(5, 5) NULL , - nvarchar_l50 nvarchar (50) COLLATE utf8_unicode_ci NULL , - real_p24_s0_l4 real NULL , - smalldatetime_l4 datetime NULL , - tinyint_l2 tinyint NULL , - smallint_l2 smallint NULL , - int_l2 int NULL , - mediumint_148 mediumint null, - big_int_120 bigint null, - small_money_p10_s4_l4 decimal(18,2) NULL , - text_16 varchar (50) COLLATE utf8_unicode_ci NULL , - timestamp_l8 timestamp NULL , - tinyint_1_p3_s0_l1 tinyint NULL , - varbinary_l150 varbinary (150) NULL , - varchar_l50 varchar (50) COLLATE utf8_unicode_ci NULL - )"; - - #endregion - - protected override void CheckRequirements() - { - Require.ProviderIs(StorageProvider.MySql); - } - - private void DropBadTables() - { - this.ExecuteNonQuery(DropBadSetTableQuery); - this.ExecuteNonQuery(DropBadEnumTableQuery); - this.ExecuteNonQuery(DropBadBitTableQuery); - } - - [Test] - public void DefaultSchemaIsAvailable() - { - this.DropBadTables(); - - var schema = this.ExtractDefaultSchema(); - Assert.IsNotNull(schema); - } - - [Test] - public void TestCatalogExtraction() - { - var catalog = ExtractCatalog(); - Assert.GreaterOrEqual(catalog.Schemas.Count, 1); - } - - [Test] - public void ExtractObjectsFromDefaultSchema() - { - this.DropBadTables(); - - var schema = this.ExtractDefaultSchema(); - var catalog = this.ExtractSchema(schema.Name); - Assert.IsNotNull(catalog); - } - - [Test] - public void TestForSupportedDataTypes() - { - this.DropBadTables(); - - ExecuteNonQuery(DropGoodTableQuery); - ExecuteNonQuery(CreateGoodTableQuery); - - var schema = ExtractDefaultSchema(); - var catalog = schema.Catalog; - - Table table = catalog.DefaultSchema.Tables["dataTypesGoodTable"]; - Assert.IsTrue(table.TableColumns["int_l4"].DataType.Type == SqlType.Int32); - } - - [Test] - //[ExpectedException(typeof(NotSupportedException))] - [Ignore("")] - public void TestForUnsupportedSETDatatypes() - { - this.DropBadTables(); - - ExecuteNonQuery(DropBadSetTableQuery); - ExecuteNonQuery(CreateBadSetTableQuery); - - var schema = ExtractDefaultSchema(); - var catalog = schema.Catalog; - - Table table = catalog.DefaultSchema.Tables["dataTypesBadSetTable"]; - Assert.IsNotNull(table); - } - - [Test] - //[ExpectedException(typeof(NotSupportedException))] - [Ignore("")] - public void TestForUnsupportedENUMDatatypes() - { - this.DropBadTables(); - - ExecuteNonQuery(DropBadEnumTableQuery); - ExecuteNonQuery(CreateBadEnumTableQuery); - - var schema = ExtractDefaultSchema(); - var catalog = schema.Catalog; - - Table table = catalog.DefaultSchema.Tables["dataTypesBadEnumTable"]; - Assert.IsNotNull(table); - } - - [Test] - //[ExpectedException(typeof(NotSupportedException))] - [Ignore("")] - public void TestForUnsupportedBITDatatypes() - { - this.DropBadTables(); - - ExecuteNonQuery(DropBadBitTableQuery); - ExecuteNonQuery(CreateBadBitTableQuery); - - var schema = ExtractDefaultSchema(); - var catalog = schema.Catalog; - - Table table = catalog.DefaultSchema.Tables["dataTypesBadBitTable"]; - Assert.IsNotNull(table); - } + return $"CREATE TABLE {tableName} (" + + "\n col_11 int, col_12 int, col_13 int," + + "\n col_21 int, col_22 int, col_23 int," + + "\n CONSTRAINT A_UNIQUE_1 UNIQUE(col_11,col_12,col_13), " + + "\n CONSTRAINT A_UNIQUE_2 UNIQUE(col_21,col_22,col_23));"; } + protected override string GetUniqueConstraintExtractionCleanUpScript(string tableName) => $"drop table if exists {tableName}"; + + // not supported yet + //protected override string GetCheckConstraintExtractionPrepareScript(string tableName) + //{ + // return $"CREATE TABLE {tableName} (" + + // "\n col_11 int, col_12 int, col_13 int," + + // "\n col_21 int, col_22 int, col_23 int," + + // "\n CONSTRAINT A_CHECK_1 CHECK(col_11 > 0 OR col_12 > 10 OR col_13 > 20), " + + // "\n CONSTRAINT A_CHECK_2 CHECK(col_21 <0 AND col_22 < 10 AND col_23 < 20));"; + //} + //protected override string GetCheckConstraintExtractionCleanUpScript(string tableName) => $"drop table {tableName}"; + #endregion + } } diff --git a/Orm/Xtensive.Orm.Tests.Sql/Oracle/ExtractorTest.cs b/Orm/Xtensive.Orm.Tests.Sql/Oracle/ExtractorTest.cs index 7fce21bdd4..326c474fe9 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/Oracle/ExtractorTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/Oracle/ExtractorTest.cs @@ -1,60 +1,125 @@ -// 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.29 using System; +using System.Text; using NUnit.Framework; using Xtensive.Sql; using Xtensive.Sql.Model; namespace Xtensive.Orm.Tests.Sql.Oracle { - [TestFixture, Explicit] - public class ExtractorTest : SqlTest + public class ExtractorTest : ExtractorTestBase { - protected override void CheckRequirements() + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.Oracle); + + #region Base test class members + protected override string GetTypesExtractionPrepareScript(string tableName) { - Require.ProviderIs(StorageProvider.Oracle); + var dataTypes = Driver.ServerInfo.DataTypes; + var sb = new StringBuilder(); + _ = sb.Append($"CREATE TABLE \"{tableName}\" ("); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Decimal]}\" number({DecimalPrecision}, {DecimalScale}) NULL,"); + + if (Driver.CoreServerInfo.ServerVersion.Major >= 10) { + sb.AppendLine($"\"{TypeToColumnName[SqlType.Float]}\" binary_float NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Double]}\" binary_double NULL,"); + } + else { + sb.AppendLine($"\"{TypeToColumnName[SqlType.Float]}\" real NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Double]}\" float NULL,"); + } + sb.AppendLine($"\"{TypeToColumnName[SqlType.Interval]}\" interval day to second NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.DateTime]}\" timestamp NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.DateTimeOffset]}\" TIMESTAMP WITH TIME ZONE NULL,"); +#if NET6_0_OR_GREATER + sb.AppendLine($"\"{TypeToColumnName[SqlType.Date]}\" date NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Time]}\" interval day(0) to second NULL,"); +#endif + + sb.AppendLine($"\"{TypeToColumnName[SqlType.Char]}\" nchar ({CharLength}) NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.VarChar]}\" nvarchar2({VarCharLength}) NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.VarCharMax]}\" nclob NULL,"); + + sb.AppendLine($"\"{TypeToColumnName[SqlType.VarBinaryMax]}\" blob NULL"); + sb.AppendLine(")"); + + return sb.ToString(); } + protected override string GetTypesExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; - [Test] - public void BaseTest() + protected override string GetForeignKeyExtractionPrepareScript() { - var schema = ExtractDefaultSchema(); + return "CREATE TABLE \"B1\" (\"b_id\" int primary key);" + + "CREATE TABLE \"B2\" (\"b_id_1\" int, \"b_id_2\" int, CONSTRAINT \"B2_PK\" PRIMARY KEY (\"b_id_1\", \"b_id_2\"));" + + "CREATE TABLE \"B3\" (\"b_id_1\" int, \"b_id_2\" int, \"b_id_3\" int," + + " CONSTRAINT \"B3_PK\" PRIMARY KEY (\"b_id_1\", \"b_id_2\", \"b_id_3\"));" + + "CREATE TABLE \"A1\" (\"b_id\" int, CONSTRAINT \"A1_FK\" FOREIGN KEY (\"b_id\") REFERENCES \"B1\" (\"b_id\"));" + + "CREATE TABLE \"A2\" (\"b_id_1\" int, \"b_id_2\" int," + + " CONSTRAINT \"A2_FK\" FOREIGN KEY (\"b_id_1\", \"b_id_2\")" + + " REFERENCES \"B2\" (\"b_id_1\", \"b_id_2\") ON DELETE SET NULL);" + + "CREATE TABLE \"A3\" (\"A_col1\" int, \"b_id_3\" int, \"b_id_1\" int, \"b_id_2\" int," + + " CONSTRAINT \"A3_FK\" FOREIGN KEY (\"b_id_1\", \"b_id_2\", \"b_id_3\")" + + " REFERENCES \"B3\" (\"b_id_1\", \"b_id_2\", \"b_id_3\") ON DELETE CASCADE);"; } + protected override string GetForeignKeyExtractionCleanUpScript() => + "drop table \"A1\";" + + "\n drop table \"A2\";" + + "\n drop table \"A3\";" + + "\n drop table \"B1\";" + + "\n drop table \"B2\";" + + "\n drop table \"B3\";"; - [Test] - public void TimeStampBasedTypes() + protected override string GetIndexExtractionPrepareScript(string tableName) { - var dropTable = "DROP TABLE TableWithTimeStamps PURGE"; - try { - var dropTableCommand = Connection.CreateCommand(dropTable); - dropTableCommand.ExecuteNonQuery(); - } - catch { + return + $"CREATE TABLE \"{tableName}\" (\"column1\" int, \"column2\" int);" + + $"\n CREATE INDEX \"{tableName}_index1_desc_asc\" on \"{tableName}\" (\"column1\" desc, \"column2\" asc);" + + $"\n CREATE UNIQUE INDEX \"{tableName}_index1_u_asc_desc\" on \"{tableName}\" (\"column1\" asc, \"column2\" desc);"; + } + protected override string GetIndexExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; - } + protected override string GetFulltextIndexExtractionPrepareScript(string tableName) + { + return $"CREATE TABLE {tableName} (Id int NOT NULL," + + "\n Name nvarchar(100) NULL," + + "\n Comments nvarchar(1000) NULL," + + $"\n CONSTRAINT [PK_{tableName}] PRIMARY KEY CLUSTERED (Id) ON [PRIMARY]);" + + $"\n CREATE FULLTEXT INDEX ON {tableName}(Name LANGUAGE 1033, Comments LANGUAGE 1033)" + + $"\n KEY INDEX PK_{tableName} WITH CHANGE_TRACKING AUTO;"; + } + protected override string GetFulltextIndexExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; + + protected override string GetUniqueConstraintExtractionPrepareScript(string tableName) + { + return $"CREATE TABLE \"{tableName}\" (" + + "\n \"col_11\" int, \"col_12\" int, \"col_13\" int," + + "\n \"col_21\" int, \"col_22\" int, \"col_23\" int," + + "\n CONSTRAINT \"A_UNIQUE_1\" UNIQUE(\"col_11\", \"col_12\", \"col_13\"), " + + "\n CONSTRAINT \"A_UNIQUE_2\" UNIQUE(\"col_21\", \"col_22\", \"col_23\"));"; + } + protected override string GetUniqueConstraintExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\""; - var createTable = "CREATE TABLE TableWithTimeStamps" + - "( Id NUMBER (6)," + - ", DateTime TIMESTAMP" + - ", DateTimeOffset TIMESTAMP WITH TIME ZONE)"; - var command = Connection.CreateCommand(createTable); - command.ExecuteNonQuery(); - var schema = ExtractDefaultSchema(); - var table = schema.Tables["TableWithTimeStamps"]; - Assert.That(table, Is.Not.Null); - Assert.That(table.Columns.Count, Is.EqualTo(3)); - - var dateTimeColumn = table.TableColumns["DateTime"]; - Assert.That(dateTimeColumn, Is.Not.Null); - Assert.That(dateTimeColumn.DataType.Type, Is.EqualTo(SqlType.DateTime)); - - var dateTimeOffsetColumn = table.TableColumns["DateTimeOffset"]; - Assert.That(dateTimeOffsetColumn, Is.Not.Null); - Assert.That(dateTimeColumn.DataType.Type, Is.EqualTo(SqlType.DateTimeOffset)); + protected override string GetCheckConstraintExtractionPrepareScript(string tableName) + { + return $"CREATE TABLE \"{tableName}\" (" + + "\n \"col_11\" int, \"col_12\" int, \"col_13\" int," + + "\n \"col_21\" int, \"col_22\" int, \"col_23\" int," + + "\n CONSTRAINT \"A_CHECK_1\" CHECK(\"col_11\" > 0 OR \"col_12\" > 10 OR \"col_13\" > 20), " + + "\n CONSTRAINT \"A_CHECK_2\" CHECK(\"col_21\" <0 AND \"col_22\" < 10 AND \"col_23\" < 20));"; } + protected override string GetCheckConstraintExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\""; + + protected override string GetCheckSequenceExtractionPrepareScript() + { + return "CREATE SEQUENCE \"seq1\" START WITH 11 INCREMENT BY 100 MINVALUE 10 MAXVALUE 10000 NOCYCLE;" + + "CREATE SEQUENCE \"seq2\" START WITH 110 INCREMENT BY 10 MINVALUE 10 MAXVALUE 100000 CYCLE;"; + } + + protected override string GetCheckSequenceExtractionCleanupScript() => "DROP SEQUENCE \"seq1\"; DROP SEQUENCE \"seq2\""; + #endregion } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/ExtractorTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/ExtractorTest.cs index a00e1d2d5b..c6540638e9 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/ExtractorTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/ExtractorTest.cs @@ -1,55 +1,221 @@ -// Copyright (C) 2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2010-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: Alexey Gamzov // Created: 2010.01.23 -using System; -using System.Diagnostics; +using System.Text; using NUnit.Framework; -using Xtensive.Sql.Model; +using Xtensive.Sql; +using Xtensive.Sql.Dml; +using Xtensive.Sql.Drivers.PostgreSql.v8_0; namespace Xtensive.Orm.Tests.Sql.PostgreSql { - [TestFixture, Explicit] - public class ExtractorTest : SqlTest + public class ExtractorTest: ExtractorTestBase { - protected override void CheckRequirements() + protected override bool CheckContstraintExtracted => true; + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); + + protected override void PopulateCustomTypeToColumnName() + { + TypeToColumnName[CustomSqlType.Point] = "pgpoint_column"; + TypeToColumnName[CustomSqlType.LSeg] = "pglsegar_column"; + TypeToColumnName[CustomSqlType.Box] = "pgbox_column"; + TypeToColumnName[CustomSqlType.Path] = "pgpath_column"; + TypeToColumnName[CustomSqlType.Polygon] = "pgpolygon_column"; + TypeToColumnName[CustomSqlType.Circle] = "pgcirgcle_column"; + } + + #region Base test class members + protected override string GetTypesExtractionPrepareScript(string tableName) + { + var dataTypes = Driver.ServerInfo.DataTypes; + var sb = new StringBuilder(); + _ = sb.Append($"CREATE TABLE \"{tableName}\" ("); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Boolean]}\" boolean NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Int16]}\" smallint NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Int32]}\" integer NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Int64]}\" bigint NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Decimal]}\" decimal({DecimalPrecision}, {DecimalScale}) NULL,"); + + sb.AppendLine($"\"{TypeToColumnName[SqlType.Float]}\" real NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Double]}\" double precision NULL,"); + + sb.AppendLine($"\"{TypeToColumnName[SqlType.Interval]}\" interval NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.DateTime]}\" timestamp NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.DateTimeOffset]}\" timestamptz NULL,"); +#if NET6_0_OR_GREATER + sb.AppendLine($"\"{TypeToColumnName[SqlType.Date]}\" date NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.Time]}\" time NULL,"); +#endif + + sb.AppendLine($"\"{TypeToColumnName[SqlType.Char]}\" char ({CharLength}) NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.VarChar]}\" varchar({VarCharLength}) NULL,"); + sb.AppendLine($"\"{TypeToColumnName[SqlType.VarCharMax]}\" text NULL,"); + + sb.AppendLine($"\"{TypeToColumnName[SqlType.VarBinaryMax]}\" bytea NULL,"); + + sb.AppendLine($"\"{TypeToColumnName[CustomSqlType.Point]}\" point NULL,"); + sb.AppendLine($"\"{TypeToColumnName[CustomSqlType.LSeg]}\" lseg NULL,"); + sb.AppendLine($"\"{TypeToColumnName[CustomSqlType.Box]}\" box NULL,"); + sb.AppendLine($"\"{TypeToColumnName[CustomSqlType.Path]}\" path NULL,"); + sb.AppendLine($"\"{TypeToColumnName[CustomSqlType.Polygon]}\" polygon NULL,"); + sb.AppendLine($"\"{TypeToColumnName[CustomSqlType.Circle]}\" circle NULL"); + sb.AppendLine(");"); + + return sb.ToString(); + } + protected override string GetTypesExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; + + protected override string GetForeignKeyExtractionPrepareScript() + { + return "CREATE TABLE \"B1\" (\"b_id\" int primary key);" + + "CREATE TABLE \"A1\" (\"b_id\" int, CONSTRAINT \"A1_FK\" FOREIGN KEY (\"b_id\") REFERENCES \"B1\" (\"b_id\"));" + + "CREATE TABLE \"B2\" (\"b_id_1\" int, \"b_id_2\" int, " + + " CONSTRAINT \"B2_PK\" PRIMARY KEY (\"b_id_1\", \"b_id_2\"));" + + "CREATE TABLE \"A2\" (\"b_id_1\" int, \"b_id_2\" int," + + " CONSTRAINT \"A2_FK\" FOREIGN KEY (\"b_id_1\", \"b_id_2\")" + + " REFERENCES \"B2\" (\"b_id_1\", \"b_id_2\") ON DELETE CASCADE ON UPDATE NO ACTION);" + + "CREATE TABLE \"B3\" (\"b_id_1\" int, \"b_id_2\" int, \"b_id_3\" int," + + " CONSTRAINT \"B3_PK\" PRIMARY KEY (\"b_id_1\", \"b_id_2\", \"b_id_3\"));" + + "CREATE TABLE \"A3\" (\"A_col1\" int, \"b_id_3\" int, \"b_id_1\" int, \"b_id_2\" int," + + " CONSTRAINT \"A3_FK\" FOREIGN KEY (\"b_id_1\", \"b_id_2\", \"b_id_3\")" + + " REFERENCES \"B3\" (\"b_id_1\", \"b_id_2\", \"b_id_3\") ON DELETE NO ACTION ON UPDATE CASCADE);"; + } + protected override string GetForeignKeyExtractionCleanUpScript() => + "drop table \"A1\";" + + "\n drop table \"A2\";" + + "\n drop table \"A3\";" + + "\n drop table \"B1\";" + + "\n drop table \"B2\";" + + "\n drop table \"B3\";"; + + protected override string GetIndexExtractionPrepareScript(string tableName) + { + return + $"CREATE TABLE \"{tableName}\" (\"column1\" int, \"column2\" int);" + + $"\n CREATE INDEX \"{tableName}_index1_desc_asc\" on \"{tableName}\" (\"column1\" desc, \"column2\" asc);" + + $"\n CREATE UNIQUE INDEX \"{tableName}_index1_u_asc_desc\" on \"{tableName}\" (\"column1\" asc, \"column2\" desc);"; + } + protected override string GetIndexExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; + + protected override string GetPartialIndexExtractionPrepareScript(string tableName) + { + return + $"CREATE TABLE \"{tableName}\" (\"column1\" int, \"column2\" int);" + + $"\n CREATE INDEX \"{tableName}_index1_filtered\" on \"{tableName}\" (\"column1\", \"column2\") WHERE \"column1\" > 10;"; + } + protected override string GetPartialIndexExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; + + protected override string GetFulltextIndexExtractionPrepareScript(string tableName) + { + return $"CREATE TABLE \"{tableName}\" (\"Id\" integer NOT NULL, \"Name\" varchar(100), \"Comments\" varchar(1000)," + + $" CONSTRAINT \"PK_{tableName}\" PRIMARY KEY (\"Id\"));" + + $"CREATE INDEX \"FT_{tableName}\" ON \"{tableName}\" USING gin ((to_tsvector('English'::regconfig, (\"Name\")::text) || to_tsvector('English'::regconfig, (\"Comments\")::text)))"; + } + protected override string GetFulltextIndexExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; + + protected override string GetUniqueConstraintExtractionPrepareScript(string tableName) + { + return $"CREATE TABLE \"{tableName}\" (" + + "\n \"col_11\" int, \"col_12\" int, \"col_13\" int," + + "\n \"col_21\" int, \"col_22\" int, \"col_23\" int," + + "\n CONSTRAINT A_UNIQUE_1 UNIQUE(\"col_11\", \"col_12\", \"col_13\"), " + + "\n CONSTRAINT A_UNIQUE_2 UNIQUE(\"col_21\", \"col_22\", \"col_23\"));"; + } + protected override string GetUniqueConstraintExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; + + protected override string GetCheckConstraintExtractionPrepareScript(string tableName) { - Require.ProviderIs(StorageProvider.PostgreSql); + return $"CREATE TABLE \"{tableName}\" (" + + "\n \"col_11\" int, \"col_12\" int, \"col_13\" int," + + "\n \"col_21\" int, \"col_22\" int, \"col_23\" int," + + "\n CONSTRAINT \"A_CHECK_1\" CHECK(\"col_11\" > 0 OR \"col_12\" > 10 OR \"col_13\" > 20), " + + "\n CONSTRAINT \"A_CHECK_2\" CHECK(\"col_21\" < 0 AND \"col_22\" < 10 AND \"col_23\" < 20));"; + } + protected override string GetCheckConstraintExtractionCleanUpScript(string tableName) => $"drop table \"{tableName}\";"; + #endregion + + #region Provider-related test cases + protected virtual string GetExtractDateTimeOffsetFieldsPrepareScript(string tableName) + { + return $"CREATE TABLE \"{tableName}\" (" + + "\"ID\" bigint PRIMARY KEY NOT NULL," + + "\"DateTimeOffset0\" timestamp(0) with time zone DEFAULT '0001-01-01 00:00:00+00:00'::timestamp(0) with time zone NOT NULL," + + "\"DateTimeOffset1\" timestamp(1) with time zone DEFAULT '0001-01-01 00:00:00.0+00:00'::timestamp(1) with time zone NOT NULL," + + "\"DateTimeOffset2\" timestamp(2) with time zone DEFAULT '0001-01-01 00:00:00.00+00:00'::timestamp(2) with time zone NOT NULL," + + "\"DateTimeOffset3\" timestamp(3) with time zone DEFAULT '0001-01-01 00:00:00.000+00:00'::timestamp(3) with time zone NOT NULL" + + ");"; + } + + protected virtual string GetExtractDateTimeOffsetFieldsCleanupScript(string tableName) + { + return $"DROP TABLE IF EXISTS \"{tableName}\""; } [Test] - public void FullTextIndexExtractorTest() + public void ExtractDateTimeOffsetFields() + { + var createTableQuery = GetExtractDateTimeOffsetFieldsPrepareScript("InteractionLog"); + RegisterCleanupScript(GetExtractDateTimeOffsetFieldsCleanupScript, "InteractionLog"); + + ExecuteQueryLineByLine(createTableQuery); + + var defaultSchema = ExtractDefaultSchema(); + + var testTable = defaultSchema.Tables["InteractionLog"]; + var tableColumn = testTable.TableColumns["DateTimeOffset0"]; + Assert.That(tableColumn.DataType.Type, Is.EqualTo(SqlType.DateTimeOffset)); + Assert.That(tableColumn.DefaultValue, Is.InstanceOf()); + var defaultExpression = (SqlNative) tableColumn.DefaultValue; + Assert.That(defaultExpression.Value, Is.EqualTo("'0001-01-01 04:02:33+04:02:33'::timestamp(0) with time zone")); + + tableColumn = testTable.TableColumns["DateTimeOffset1"]; + Assert.That(tableColumn.DataType.Type, Is.EqualTo(SqlType.DateTimeOffset)); + Assert.That(tableColumn.DefaultValue, Is.InstanceOf()); + defaultExpression = (SqlNative) tableColumn.DefaultValue; + Assert.That(defaultExpression.Value, Is.EqualTo("'0001-01-01 04:02:33+04:02:33'::timestamp(1) with time zone")); + + tableColumn = testTable.TableColumns["DateTimeOffset2"]; + Assert.That(tableColumn.DataType.Type, Is.EqualTo(SqlType.DateTimeOffset)); + Assert.That(tableColumn.DefaultValue, Is.InstanceOf()); + defaultExpression = (SqlNative) tableColumn.DefaultValue; + Assert.That(defaultExpression.Value, Is.EqualTo("'0001-01-01 04:02:33+04:02:33'::timestamp(2) with time zone")); + + tableColumn = testTable.TableColumns["DateTimeOffset3"]; + Assert.That(tableColumn.DataType.Type, Is.EqualTo(SqlType.DateTimeOffset)); + Assert.That(tableColumn.DefaultValue, Is.InstanceOf()); + defaultExpression = (SqlNative) tableColumn.DefaultValue; + Assert.That(defaultExpression.Value, Is.EqualTo("'0001-01-01 04:02:33+04:02:33'::timestamp(3) with time zone")); + } + + + protected virtual string GetExpressionIndexExtractorPrepareScript(string tableName) { - var schema = Driver.ExtractDefaultSchema(Connection); + return $"CREATE TABLE \"{tableName}\"(col1 text, col2 text);" + + $"CREATE INDEX \"{tableName}_indx\" ON \"" + tableName + "\"(col1,col2,(col1||col2));"; + } + + protected virtual string GetExpressionIndexExtractorCleanupScript(string tableName) + { + return $"DROP TABLE IF EXISTS \"{tableName}\""; } [Test] public void ExpressionIndexExtractorTest() { - string guid = Guid.NewGuid().ToString(); - string tableName = "tbl" + guid; - string indexName = "ix" + guid; - string tableCreation = "CREATE TABLE \"" + tableName + "\"(col1 text, col2 text)"; - string indexCreation = "CREATE INDEX \"" + indexName + "\" ON \"" + tableName + "\"(col1,col2,(col1||col2))"; - - Schema schema = null; - try { - Connection.BeginTransaction(); - - using (var cmd = Connection.CreateCommand(tableCreation + ";" + indexCreation)) { - cmd.ExecuteNonQuery(); - } - schema = Driver.ExtractDefaultSchema(Connection); - } - finally { - Connection.Rollback(); - } - - var table = schema.Tables[tableName]; + var createTableQuery = GetExpressionIndexExtractorPrepareScript("tableWithIndx"); + RegisterCleanupScript(GetExpressionIndexExtractorCleanupScript, "tableWithIndx"); + + ExecuteQueryLineByLine(createTableQuery); + + var schema = ExtractDefaultSchema(); + + var table = schema.Tables["tableWithIndx"]; Assert.AreEqual(1, table.Indexes.Count); - var index = table.Indexes[indexName]; + var index = table.Indexes["tableWithIndx_indx"]; Assert.AreEqual(3, index.Columns.Count); Assert.AreSame(table.Columns[0], index.Columns[0].Column); Assert.AreSame(table.Columns[1], index.Columns[1].Column); @@ -58,39 +224,6 @@ public void ExpressionIndexExtractorTest() Assert.IsNull(index.Columns[2].Column); Assert.IsNotNull(index.Columns[2].Expression); } - - [Test] - public void ExtractDateTimeOffsetFields() - { - var dropTableScript = "DROP TABLE IF EXISTS \"InteractionLog\""; - var createTableScript = "CREATE TABLE \"InteractionLog\" (" + - "\"ID\" bigint PRIMARY KEY NOT NULL," + - "\"PacketId\" character varying(10485760)," + - "\"SenderId\" character varying(10485760)," + - "\"ReceiverId\" character varying(10485760)," + - "\"IsTest\" boolean DEFAULT false NOT NULL," + - "\"ResultCode\" integer," + - "\"ErrorMessage\" character varying(10485760)," + - "\"ErrorDescription\" character varying(10485760)," + - "\"Description\" character varying(10485760)," + - "\"Type\" integer DEFAULT 0 NOT NULL," + - "\"FormatId\" character varying(10485760)," + - "\"DateTimeOffset0\" timestamp(0) with time zone DEFAULT '0001-01-01 00:00:00+00:00'::timestamp(0) with time zone NOT NULL," + - "\"DateTimeOffset1\" timestamp(1) with time zone DEFAULT '0001-01-01 00:00:00.0+00:00'::timestamp(1) with time zone NOT NULL," + - "\"DateTimeOffset2\" timestamp(2) with time zone DEFAULT '0001-01-01 00:00:00.00+00:00'::timestamp(2) with time zone NOT NULL," + - "\"DateTimeOffset3\" timestamp(3) with time zone DEFAULT '0001-01-01 00:00:00.000+00:00'::timestamp(3) with time zone NOT NULL," + - "\"VersionID\" integer DEFAULT 0 NOT NULL" + - ");"; - - using (var command = Connection.CreateCommand()) { - command.CommandText = dropTableScript; - command.ExecuteNonQuery(); - - command.CommandText = createTableScript; - command.ExecuteNonQuery(); - } - - var catalog = Driver.ExtractCatalog(Connection); - } + #endregion } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/SqlDateTimePartsTest.cs b/Orm/Xtensive.Orm.Tests.Sql/SqlDateTimePartsTest.cs new file mode 100644 index 0000000000..be0bc899cb --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/SqlDateTimePartsTest.cs @@ -0,0 +1,227 @@ +// Copyright (C) 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; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using Xtensive.Sql.Dml; + +namespace Xtensive.Orm.Sql.Tests +{ + [TestFixture] + public class SqlDateTimePartsTest + { + // Year = 0, + // Month = 1, + // Day = 2, + // Hour = 3, + // Minute = 4, + // Second = 5, + // Millisecond = 6, + // Nanosecond = 7, + // TimeZoneHour = 8, + // TimeZoneMinute = 9, + // DayOfYear = 10, + // DayOfWeek = 11, + // Date = 12, + // DateTime = 13, + // LocalDateTime = 14, + // UtcDateTime = 15, + // Offset = 16, + // Nothing = 25, + + [Test] + public void MainTest() + { + var validNames = new HashSet() { + "Year", "Month", "Day", "Hour", + "Minute", "Second", "Millisecond", "Nanosecond", + "TimeZoneHour", "TimeZoneMinute", + "DayOfYear", "DayOfWeek", + "Date", "DateTime", + "LocalDateTime","UtcDateTime", "Offset", + "Nothing", + }; + + var enums = typeof(SqlDateTimeOffsetPart).Assembly.GetTypes() + .Where(t => t.Namespace == "Xtensive.Sql.Dml" && t.IsEnum && (t.Name.StartsWith("Sql") && t.Name.EndsWith("Part"))) + .ToList(); + foreach (var @enum in enums) { + foreach(var name in Enum.GetNames(@enum)) { + Assert.That(validNames.Contains(name), $"Does the enum {@enum.Name} have new item?"); + } + } + } + + [Test] + public void DateTimeOffsetPartsValueTest() + { + Assert.That((int) SqlDateTimeOffsetPart.Year, Is.EqualTo(0)); + Assert.That((int) SqlDateTimeOffsetPart.Month, Is.EqualTo(1)); + Assert.That((int) SqlDateTimeOffsetPart.Day, Is.EqualTo(2)); + Assert.That((int) SqlDateTimeOffsetPart.Hour, Is.EqualTo(3)); + Assert.That((int) SqlDateTimeOffsetPart.Minute, Is.EqualTo(4)); + Assert.That((int) SqlDateTimeOffsetPart.Second, Is.EqualTo(5)); + Assert.That((int) SqlDateTimeOffsetPart.Millisecond, Is.EqualTo(6)); + Assert.That((int) SqlDateTimeOffsetPart.Nanosecond, Is.EqualTo(7)); + Assert.That((int) SqlDateTimeOffsetPart.TimeZoneHour, Is.EqualTo(8)); + Assert.That((int) SqlDateTimeOffsetPart.TimeZoneMinute, Is.EqualTo(9)); + Assert.That((int) SqlDateTimeOffsetPart.DayOfYear, Is.EqualTo(10)); + Assert.That((int) SqlDateTimeOffsetPart.DayOfWeek, Is.EqualTo(11)); + Assert.That((int) SqlDateTimeOffsetPart.Date, Is.EqualTo(12)); + Assert.That((int) SqlDateTimeOffsetPart.DateTime, Is.EqualTo(13)); + Assert.That((int) SqlDateTimeOffsetPart.LocalDateTime, Is.EqualTo(14)); + Assert.That((int) SqlDateTimeOffsetPart.UtcDateTime, Is.EqualTo(15)); + Assert.That((int) SqlDateTimeOffsetPart.Offset, Is.EqualTo(16)); + Assert.That((int) SqlDateTimeOffsetPart.Nothing, Is.EqualTo(25)); + } + + [Test] + public void DateTimePartsValueTest() + { + Assert.That((int) SqlDateTimePart.Year, Is.EqualTo(0)); + Assert.That((int) SqlDateTimePart.Month, Is.EqualTo(1)); + Assert.That((int) SqlDateTimePart.Day, Is.EqualTo(2)); + Assert.That((int) SqlDateTimePart.Hour, Is.EqualTo(3)); + Assert.That((int) SqlDateTimePart.Minute, Is.EqualTo(4)); + Assert.That((int) SqlDateTimePart.Second, Is.EqualTo(5)); + Assert.That((int) SqlDateTimePart.Millisecond, Is.EqualTo(6)); + Assert.That((int) SqlDateTimePart.Nanosecond, Is.EqualTo(7)); + Assert.That((int) SqlDateTimePart.TimeZoneHour, Is.EqualTo(8)); + Assert.That((int) SqlDateTimePart.TimeZoneMinute, Is.EqualTo(9)); + Assert.That((int) SqlDateTimePart.DayOfYear, Is.EqualTo(10)); + Assert.That((int) SqlDateTimePart.DayOfWeek, Is.EqualTo(11)); + Assert.That((int) SqlDateTimePart.Nothing, Is.EqualTo(25)); + } +#if NET6_0_OR_GREATER + + [Test] + public void DatePartsValueTest() + { + Assert.That((int) SqlDatePart.Year, Is.EqualTo(0)); + Assert.That((int) SqlDatePart.Month, Is.EqualTo(1)); + Assert.That((int) SqlDatePart.Day, Is.EqualTo(2)); + Assert.That((int) SqlDatePart.DayOfYear, Is.EqualTo(10)); + Assert.That((int) SqlDatePart.DayOfWeek, Is.EqualTo(11)); + Assert.That((int) SqlDatePart.Nothing, Is.EqualTo(25)); + } + + [Test] + public void TimePartsValueTest() + { + Assert.That((int) SqlTimePart.Hour, Is.EqualTo(3)); + Assert.That((int) SqlTimePart.Minute, Is.EqualTo(4)); + Assert.That((int) SqlTimePart.Second, Is.EqualTo(5)); + Assert.That((int) SqlTimePart.Millisecond, Is.EqualTo(6)); + Assert.That((int) SqlTimePart.Nanosecond, Is.EqualTo(7)); + Assert.That((int) SqlTimePart.Nothing, Is.EqualTo(25)); + } +#endif + + [Test] + public void IntervalPartsValueTest() + { + Assert.That((int) SqlIntervalPart.Day, Is.EqualTo(2)); + Assert.That((int) SqlIntervalPart.Hour, Is.EqualTo(3)); + Assert.That((int) SqlIntervalPart.Minute, Is.EqualTo(4)); + Assert.That((int) SqlIntervalPart.Second, Is.EqualTo(5)); + Assert.That((int) SqlIntervalPart.Millisecond, Is.EqualTo(6)); + Assert.That((int) SqlIntervalPart.Nanosecond, Is.EqualTo(7)); + + Assert.That((int) SqlIntervalPart.Nothing, Is.EqualTo(25)); + } + + [Test] + public void DateTimePartConversionTest() + { + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Year, Is.EqualTo(SqlDateTimePart.Year)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Month, Is.EqualTo(SqlDateTimePart.Month)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Day, Is.EqualTo(SqlDateTimePart.Day)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Hour, Is.EqualTo(SqlDateTimePart.Hour)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Minute, Is.EqualTo(SqlDateTimePart.Minute)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Second, Is.EqualTo(SqlDateTimePart.Second)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Millisecond, Is.EqualTo(SqlDateTimePart.Millisecond)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Nanosecond, Is.EqualTo(SqlDateTimePart.Nanosecond)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.TimeZoneHour, Is.EqualTo(SqlDateTimePart.TimeZoneHour)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.TimeZoneMinute, Is.EqualTo(SqlDateTimePart.TimeZoneMinute)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.DayOfYear, Is.EqualTo(SqlDateTimePart.DayOfYear)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.DayOfWeek, Is.EqualTo(SqlDateTimePart.DayOfWeek)); + Assert.That((SqlDateTimePart) (int) SqlDateTimeOffsetPart.Nothing, Is.EqualTo(SqlDateTimePart.Nothing)); + + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Year, Is.EqualTo(SqlDateTimeOffsetPart.Year)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Month, Is.EqualTo(SqlDateTimeOffsetPart.Month)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Day, Is.EqualTo(SqlDateTimeOffsetPart.Day)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Hour, Is.EqualTo(SqlDateTimeOffsetPart.Hour)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Minute, Is.EqualTo(SqlDateTimeOffsetPart.Minute)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Second, Is.EqualTo(SqlDateTimeOffsetPart.Second)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Millisecond, Is.EqualTo(SqlDateTimeOffsetPart.Millisecond)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Nanosecond, Is.EqualTo(SqlDateTimeOffsetPart.Nanosecond)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.TimeZoneHour, Is.EqualTo(SqlDateTimeOffsetPart.TimeZoneHour)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.TimeZoneMinute, Is.EqualTo(SqlDateTimeOffsetPart.TimeZoneMinute)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.DayOfYear, Is.EqualTo(SqlDateTimeOffsetPart.DayOfYear)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.DayOfWeek, Is.EqualTo(SqlDateTimeOffsetPart.DayOfWeek)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDateTimePart.Nothing, Is.EqualTo(SqlDateTimeOffsetPart.Nothing)); + } +#if NET6_0_OR_GREATER + + [Test] + public void DatePartConversionTest() + { + Assert.That((SqlDatePart) (int) SqlDateTimeOffsetPart.Year, Is.EqualTo(SqlDatePart.Year)); + Assert.That((SqlDatePart) (int) SqlDateTimeOffsetPart.Month, Is.EqualTo(SqlDatePart.Month)); + Assert.That((SqlDatePart) (int) SqlDateTimeOffsetPart.Day, Is.EqualTo(SqlDatePart.Day)); + Assert.That((SqlDatePart) (int) SqlDateTimeOffsetPart.DayOfYear, Is.EqualTo(SqlDatePart.DayOfYear)); + Assert.That((SqlDatePart) (int) SqlDateTimeOffsetPart.DayOfWeek, Is.EqualTo(SqlDatePart.DayOfWeek)); + Assert.That((SqlDatePart) (int) SqlDateTimeOffsetPart.Nothing, Is.EqualTo(SqlDatePart.Nothing)); + + Assert.That((SqlDateTimeOffsetPart) (int) SqlDatePart.Year, Is.EqualTo(SqlDateTimeOffsetPart.Year)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDatePart.Month, Is.EqualTo(SqlDateTimeOffsetPart.Month)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDatePart.Day, Is.EqualTo(SqlDateTimeOffsetPart.Day)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDatePart.DayOfYear, Is.EqualTo(SqlDateTimeOffsetPart.DayOfYear)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDatePart.DayOfWeek, Is.EqualTo(SqlDateTimeOffsetPart.DayOfWeek)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlDatePart.Nothing, Is.EqualTo(SqlDateTimeOffsetPart.Nothing)); + } + + [Test] + public void TimePartConversionTest() + { + Assert.That((SqlTimePart) (int) SqlDateTimeOffsetPart.Hour, Is.EqualTo(SqlTimePart.Hour)); + Assert.That((SqlTimePart) (int) SqlDateTimeOffsetPart.Minute, Is.EqualTo(SqlTimePart.Minute)); + Assert.That((SqlTimePart) (int) SqlDateTimeOffsetPart.Second, Is.EqualTo(SqlTimePart.Second)); + Assert.That((SqlTimePart) (int) SqlDateTimeOffsetPart.Millisecond, Is.EqualTo(SqlTimePart.Millisecond)); + Assert.That((SqlTimePart) (int) SqlDateTimeOffsetPart.Nanosecond, Is.EqualTo(SqlTimePart.Nanosecond)); + Assert.That((SqlTimePart) (int) SqlDateTimeOffsetPart.Nothing, Is.EqualTo(SqlTimePart.Nothing)); + + Assert.That((SqlDateTimeOffsetPart) (int) SqlTimePart.Hour, Is.EqualTo(SqlDateTimeOffsetPart.Hour)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlTimePart.Minute, Is.EqualTo(SqlDateTimeOffsetPart.Minute)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlTimePart.Second, Is.EqualTo(SqlDateTimeOffsetPart.Second)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlTimePart.Millisecond, Is.EqualTo(SqlDateTimeOffsetPart.Millisecond)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlTimePart.Nanosecond, Is.EqualTo(SqlDateTimeOffsetPart.Nanosecond)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlTimePart.Nothing, Is.EqualTo(SqlDateTimeOffsetPart.Nothing)); + } +#endif + + [Test] + public void IntervalPartConversionTest() + { + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Day, Is.EqualTo(SqlIntervalPart.Day)); + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Hour, Is.EqualTo(SqlIntervalPart.Hour)); + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Minute, Is.EqualTo(SqlIntervalPart.Minute)); + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Second, Is.EqualTo(SqlIntervalPart.Second)); + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Millisecond, Is.EqualTo(SqlIntervalPart.Millisecond)); + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Nanosecond, Is.EqualTo(SqlIntervalPart.Nanosecond)); + Assert.That((SqlIntervalPart) (int) SqlDateTimeOffsetPart.Nothing, Is.EqualTo(SqlIntervalPart.Nothing)); + + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Day, Is.EqualTo(SqlDateTimeOffsetPart.Day)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Hour, Is.EqualTo(SqlDateTimeOffsetPart.Hour)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Minute, Is.EqualTo(SqlDateTimeOffsetPart.Minute)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Second, Is.EqualTo(SqlDateTimeOffsetPart.Second)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Millisecond, Is.EqualTo(SqlDateTimeOffsetPart.Millisecond)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Nanosecond, Is.EqualTo(SqlDateTimeOffsetPart.Nanosecond)); + Assert.That((SqlDateTimeOffsetPart) (int) SqlIntervalPart.Nothing, Is.EqualTo(SqlDateTimeOffsetPart.Nothing)); + } + } +} diff --git a/Orm/Xtensive.Orm.Tests.Sql/SqlServer/MSSQLExtractorTests.cs b/Orm/Xtensive.Orm.Tests.Sql/SqlServer/MSSQLExtractorTests.cs index 5f6335c3a4..1924be18a7 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/SqlServer/MSSQLExtractorTests.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/SqlServer/MSSQLExtractorTests.cs @@ -1,155 +1,139 @@ -// 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. using System; -using System.Collections; -using System.Data.Common; +using System.Text; using NUnit.Framework; using Xtensive.Sql; -using Xtensive.Sql.Model; namespace Xtensive.Orm.Tests.Sql.SqlServer { - public class AssertUtility + public class MSSQLExtractorTest : ExtractorTestBase { - public static void AssertArraysAreEqual(Array a1, Array a2) - { - if (a1==a2) - return; - if (a1==null || a2==null) - throw new AssertionException("One of arrays is null."); - if (a1.Length!=a2.Length) - throw new AssertionException("Lengths are different."); - for (int i = 0; i < a1.Length; i++) - Assert.AreEqual(a1.GetValue(i), a2.GetValue(i)); - } + protected override bool CheckContstraintExtracted => false; - public static void AssertCollectionsAreEqual(IEnumerable col1, IEnumerable col2) - { - if (col1==col2) - return; - if (col2==null || col1==null) - throw new AssertionException("One of arrays is null."); - IEnumerator enumerator1 = col1.GetEnumerator(); - IEnumerator enumerator2 = col2.GetEnumerator(); - enumerator1.Reset(); - enumerator2.Reset(); - while (enumerator1.MoveNext()) { - if (!enumerator2.MoveNext()) - throw new AssertionException("Different count."); - Assert.AreEqual(enumerator1.Current, enumerator2.Current); - } - if (enumerator2.MoveNext()) - throw new AssertionException("Different count."); - } - } + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.SqlServer); - public abstract class MSSQLExtractorTestBase : SqlTest - { - private bool isTestsIgnored = true; - - public virtual string CleanUpScript + #region Base test class members + protected override string GetTypesExtractionPrepareScript(string tableName) { - get { return null; } + var dataTypes = Driver.ServerInfo.DataTypes; + var sb = new StringBuilder(); + _ = sb.Append($"CREATE TABLE {tableName} ("); + sb.AppendLine($"{TypeToColumnName[SqlType.Boolean]} [bit] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int16]} [smallint] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int32]} [int] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int64]} [bigint] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.UInt8]} [tinyint] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Decimal]} [decimal]({DecimalPrecision}, {DecimalScale}) NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Float]} [real] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Double]} [float] NULL,"); + + if (Driver.CoreServerInfo.ServerVersion.Major > 10) + sb.AppendLine($"{TypeToColumnName[SqlType.DateTime]} [datetime2] NULL,"); + else + sb.AppendLine($"{TypeToColumnName[SqlType.DateTime]} [datetime] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.DateTimeOffset]} [datetimeoffset] NULL,"); +#if NET6_0_OR_GREATER + sb.AppendLine($"{TypeToColumnName[SqlType.Date]} [date] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Time]} [time] NULL,"); +#endif + + sb.AppendLine($"{TypeToColumnName[SqlType.Char]} [nchar] ({CharLength}) COLLATE Cyrillic_General_CI_AS NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarChar]} [nvarchar]({VarCharLength}) COLLATE Cyrillic_General_CI_AS NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarCharMax]} [nvarchar](max) COLLATE Cyrillic_General_CI_AS NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Binary]} [binary]({BinaryLength}) NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarBinary]} [varbinary]({VarBinaryLength}) NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.VarBinaryMax]} [varbinary] (max) NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Guid]} [uniqueidentifier] NULL"); // = "guid_column"; + sb.AppendLine(");"); + + return sb.ToString(); } + protected override string GetTypesExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; - protected void ExecuteQuery(string sqlQuery) + protected override string GetForeignKeyExtractionPrepareScript() { - if (string.IsNullOrEmpty(sqlQuery)) - return; - ExecuteNonQuery(sqlQuery); + return "CREATE TABLE B1 (b_id int primary key);" + + "CREATE TABLE A1 (b_id int references B1(b_id));" + + "CREATE TABLE B2 (b_id_1 int, b_id_2 int, " + + " CONSTRAINT [B2_PK] PRIMARY KEY CLUSTERED (b_id_1, b_id_2) ON [PRIMARY]);" + + "CREATE TABLE A2 (b_id_1 int, b_id_2 int," + + " CONSTRAINT [A2_FK] FOREIGN KEY (b_id_1, b_id_2)" + + " REFERENCES B2 (b_id_1, b_id_2) ON DELETE CASCADE ON UPDATE NO ACTION);" + + "CREATE TABLE B3 (b_id_1 int, b_id_2 int, b_id_3 int," + + " CONSTRAINT [B3_PK] PRIMARY KEY CLUSTERED (b_id_1, b_id_2, b_id_3) ON [PRIMARY]);" + + "CREATE TABLE A3 (A_col1 int, b_id_3 int, b_id_1 int, b_id_2 int," + + " CONSTRAINT [A3_FK] FOREIGN KEY (b_id_1, b_id_2, b_id_3)" + + " REFERENCES B3 (b_id_1, b_id_2, b_id_3) ON DELETE NO ACTION ON UPDATE CASCADE);"; } - - protected override void TestFixtureSetUp() + protected override string GetForeignKeyExtractionCleanUpScript() => + "drop table A1" + + "\n drop table A2" + + "\n drop table A3" + + "\n drop table B1" + + "\n drop table B2" + + "\n drop table B3"; + + protected override string GetIndexExtractionPrepareScript(string tableName) { - base.TestFixtureSetUp(); - isTestsIgnored = false; + return + $"CREATE TABLE {tableName} (column1 int, column2 int);" + + $"\n CREATE INDEX {tableName}_index1_desc_asc on {tableName} (column1 desc, column2 asc);" + + $"\n CREATE UNIQUE INDEX {tableName}_index1_u_asc_desc on {tableName} (column1 asc, column2 desc);" + + $"\n CREATE UNIQUE INDEX {tableName}_index_with_included_columns on {tableName} (column1 asc) include (column2);"; } + protected override string GetIndexExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; - protected override void TestFixtureTearDown() + protected override string GetPartialIndexExtractionPrepareScript(string tableName) { - if (!isTestsIgnored) - ExecuteQuery(CleanUpScript); - base.TestFixtureTearDown(); + return + $"CREATE TABLE {tableName} (column1 int, column2 int);" + + $"\n CREATE INDEX {tableName}_index1_filtered on {tableName} (column1, column2) WHERE column1 > 10;"; } + protected override string GetPartialIndexExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; - protected override void CheckRequirements() + protected override string GetFulltextIndexExtractionPrepareScript(string tableName) { - Require.ProviderIs(StorageProvider.SqlServer); + return $"CREATE TABLE {tableName} (Id int NOT NULL," + + "\n Name nvarchar(100) NULL," + + "\n Comments nvarchar(1000) NULL," + + $"\n CONSTRAINT [PK_{tableName}] PRIMARY KEY CLUSTERED (Id) ON [PRIMARY]);" + + $"\n CREATE FULLTEXT INDEX ON {tableName}(Name LANGUAGE 1033, Comments LANGUAGE 1033)" + + $"\n KEY INDEX PK_{tableName} WITH CHANGE_TRACKING AUTO;"; } - } + protected override string GetFulltextIndexExtractionCleanUpScript(string tableName) => $"drop table {tableName};"; - public class MSSQLExtractor_TestSchemaExtraction : MSSQLExtractorTestBase - { - public override string CleanUpScript + protected override string GetUniqueConstraintExtractionPrepareScript(string tableName) { - get - { - return - "\n drop table role1.table1" + - "\n drop table role2.table2" + - "\n drop table role3.table3" + - "\n drop table role3.table31" + - "\n exec sp_droprole role1" + - "\n exec sp_droprole role2" + - "\n exec sp_droprole role3"; - ; - } + return $"CREATE TABLE {tableName} (" + + "\n col_11 int, col_12 int, col_13 int," + + "\n col_21 int, col_22 int, col_23 int," + + "\n CONSTRAINT A_UNIQUE_1 UNIQUE(col_11,col_12,col_13), " + + "\n CONSTRAINT A_UNIQUE_2 UNIQUE(col_21,col_22,col_23));"; } + protected override string GetUniqueConstraintExtractionCleanUpScript(string tableName) => $"drop table {tableName}"; - [Test] - public virtual void Main() + protected override string GetCheckConstraintExtractionPrepareScript(string tableName) { - ExecuteQuery( - " exec sp_addrole 'role1'" + - "\n exec sp_addrole 'role2'" + - "\n exec sp_addrole 'role3'" + - "\n create table role1.table1(test int, test2 int, test3 int)" + - "\n create table role2.table2(test int, test2 int, test3 int)" + - "\n create table role3.table3(test int, test2 int, test3 int)" + - "\n create table role3.table31(test int, test2 int, test3 int)"); - - var model = ExtractCatalog(); - - // Validating. - Assert.IsNotNull(model.Schemas["role1"]); - Assert.IsNotNull(model.Schemas["role1"].Tables["table1"]); - Assert.IsNotNull(model.Schemas["role1"].Tables["table1"].TableColumns["test"]); - Assert.IsNotNull(model.Schemas["role1"].Tables["table1"].TableColumns["test2"]); - Assert.IsNotNull(model.Schemas["role1"].Tables["table1"].TableColumns["test3"]); - Assert.IsTrue(model.Schemas["role1"].Tables.Count==1); - - Assert.IsNotNull(model.Schemas["role2"]); - Assert.IsNotNull(model.Schemas["role2"].Tables["table2"]); - Assert.IsNotNull(model.Schemas["role2"].Tables["table2"].TableColumns["test"]); - Assert.IsNotNull(model.Schemas["role2"].Tables["table2"].TableColumns["test2"]); - Assert.IsNotNull(model.Schemas["role2"].Tables["table2"].TableColumns["test3"]); - Assert.IsTrue(model.Schemas["role2"].Tables.Count==1); - - Assert.IsNotNull(model.Schemas["role3"]); - Assert.IsNotNull(model.Schemas["role3"].Tables["table3"]); - Assert.IsNotNull(model.Schemas["role3"].Tables["table3"].TableColumns["test"]); - Assert.IsNotNull(model.Schemas["role3"].Tables["table3"].TableColumns["test2"]); - Assert.IsNotNull(model.Schemas["role3"].Tables["table3"].TableColumns["test3"]); - Assert.IsTrue(model.Schemas["role3"].Tables.Count==2); + return $"CREATE TABLE {tableName} (" + + "\n col_11 int, col_12 int, col_13 int," + + "\n col_21 int, col_22 int, col_23 int," + + "\n CONSTRAINT A_CHECK_1 CHECK(col_11 > 0 OR col_12 > 10 OR col_13 > 20), " + + "\n CONSTRAINT A_CHECK_2 CHECK(col_21 <0 AND col_22 < 10 AND col_23 < 20));"; } - } + protected override string GetCheckConstraintExtractionCleanUpScript(string tableName) => $"drop table {tableName}"; + #endregion - public class MSSQLExtractor_TestColumnTypeExtraction : MSSQLExtractorTestBase - { - private StringComparer comparer = StringComparer.InvariantCultureIgnoreCase; + #region Additional MS Sql Server specific tests - public override string CleanUpScript + protected virtual string GetMSSqlTypesExtractionPrepareScript(string tableName) { - get { return "drop table dataTypesTestTable"; } - } - - [Test] - public void Main() - { - string createTableQuery = - "CREATE TABLE dataTypesTestTable (" + + return $"CREATE TABLE {tableName} (" + "[int_l4] [int] NULL ," + "[binary_l50] [binary] (50) NULL ," + "[bit_l1] [bit] NULL , " + @@ -175,524 +159,99 @@ public void Main() "[uniqueidentifier_l16] [uniqueidentifier] NULL ," + "[varbinary_l150] [varbinary] (150) NULL ," + "[varchar_l50] [varchar] (50) COLLATE Cyrillic_General_CI_AS NULL)"; - ExecuteQuery(createTableQuery); - var model = ExtractCatalog(); - - Table table = model.DefaultSchema.Tables["dataTypesTestTable"]; - Assert.IsTrue(table.TableColumns["int_l4"].DataType.Type==SqlType.Int32); - Assert.IsTrue(table.TableColumns["binary_l50"].DataType.Length==50); - Assert.IsTrue(table.TableColumns["binary_l50"].DataType.Type==SqlType.Binary); - Assert.IsTrue(table.TableColumns["bit_l1"].DataType.Type==SqlType.Boolean); - Assert.IsTrue(table.TableColumns["char_10"].DataType.Length==5); - Assert.IsTrue(table.TableColumns["char_10"].DataType.Type==SqlType.Char); - Assert.IsTrue(table.TableColumns["datetime_l8"].DataType.Type==SqlType.DateTime); - Assert.IsTrue(table.TableColumns["decimal_p18_s0"].DataType.Type==SqlType.Decimal); - Assert.IsTrue(table.TableColumns["decimal_p18_s0"].DataType.Precision==18); - Assert.IsTrue(table.TableColumns["decimal_p18_s0"].DataType.Scale==0); - Assert.IsTrue(table.TableColumns["decimal_p12_s11_l9"].DataType.Type==SqlType.Decimal); - Assert.IsTrue(table.TableColumns["decimal_p12_s11_l9"].DataType.Precision==12); - Assert.IsTrue(table.TableColumns["decimal_p12_s11_l9"].DataType.Scale==11); - Assert.IsTrue(table.TableColumns["float_p53"].DataType.Type==SqlType.Double); - Assert.IsTrue(table.TableColumns["float_p53"].DataType.Precision==null); - Assert.IsTrue(table.TableColumns["float_p53"].DataType.Scale==null); - Assert.IsTrue(table.TableColumns["image_16"].DataType.Type==SqlType.VarBinaryMax); - Assert.IsTrue(comparer.Compare(table.TableColumns["money_p19_s4_l8"].DataType.TypeName, "money")==0); - Assert.IsTrue(table.TableColumns["money_p19_s4_l8"].DataType.Precision==19); - Assert.IsTrue(table.TableColumns["money_p19_s4_l8"].DataType.Scale==4); - Assert.IsTrue(table.TableColumns["nchar_l100"].DataType.Type==SqlType.Char); - Assert.IsTrue(table.TableColumns["nchar_l100"].DataType.Length==100); - Assert.IsTrue(table.TableColumns["ntext"].DataType.Type==SqlType.VarCharMax); - Assert.IsTrue(table.TableColumns["numeric_p5_s5"].DataType.Type==SqlType.Decimal); - Assert.IsTrue(table.TableColumns["numeric_p5_s5"].DataType.Precision==5); - Assert.IsTrue(table.TableColumns["nvarchar_l50"].DataType.Type==SqlType.VarChar); - Assert.IsTrue(table.TableColumns["nvarchar_l50"].DataType.Length==50); - Assert.IsTrue(table.TableColumns["real_p24_s0_l4"].DataType.Type==SqlType.Float); - Assert.IsTrue(table.TableColumns["real_p24_s0_l4"].DataType.Precision==null); - Assert.IsTrue(table.TableColumns["real_p24_s0_l4"].DataType.Scale==null); - Assert.IsTrue(table.TableColumns["smalldatetime_l4"].DataType.Type==SqlType.DateTime); - Assert.IsTrue(table.TableColumns["smallint_l2"].DataType.Type==SqlType.Int16); - Assert.IsTrue(comparer.Compare(table.TableColumns["small_money_p10_s4_l4"].DataType.TypeName, "smallmoney")==0); - Assert.IsTrue(comparer.Compare(table.TableColumns["sql_variant_"].DataType.TypeName, "sql_variant")==0); - Assert.IsTrue(table.TableColumns["text_16"].DataType.Type==SqlType.VarCharMax); - Assert.IsTrue(comparer.Compare(table.TableColumns["timestamp_l8"].DataType.TypeName, "timestamp")==0); - Assert.IsTrue(table.TableColumns["tinyint_1_p3_s0_l1"].DataType.Type==SqlType.UInt8); - Assert.IsTrue(table.TableColumns["uniqueidentifier_l16"].DataType.Type==SqlType.Guid); - Assert.IsTrue(table.TableColumns["varbinary_l150"].DataType.Type==SqlType.VarBinary); - Assert.IsTrue(table.TableColumns["varbinary_l150"].DataType.Length==150); - Assert.IsTrue(table.TableColumns["varchar_l50"].DataType.Type==SqlType.VarChar); - Assert.IsTrue(table.TableColumns["varchar_l50"].DataType.Length==25); - } - } - - public class MSSQLExtractor_TestExtractingViews : MSSQLExtractorTestBase - { - public override string CleanUpScript - { - get - { - return "\n drop table role1.table1" + - "\n drop view role1.view1" + - "\n drop view role1.view2" + - "\n exec sp_droprole role1"; - } - } - - [Test] - public virtual void Main() - { - ExecuteQuery( - " EXEC sp_addrole 'role1'" + - "\n CREATE TABLE role1.table1(column1 int, column2 int)"); - - ExecuteQuery( - "CREATE VIEW role1.view1 " + - "\n as Select column1 From role1.table1"); - - ExecuteQuery( - "CREATE VIEW role1.view2 " + - "\n as Select column1, column2 From role1.table1"); - - var model = ExtractCatalog(); - Schema schema = model.Schemas["role1"]; - - Assert.IsNotNull(schema); - Assert.IsNotNull(schema.Views["view1"]); - Assert.IsNotNull(schema.Views["view2"]); - Assert.IsNotNull(schema.Views["view1"].ViewColumns["column1"]); - Assert.IsNotNull(schema.Views["view2"].ViewColumns["column1"]); - Assert.IsNotNull(schema.Views["view2"].ViewColumns["column2"]); - } - } - - public class MSSQLExtractor_TestExtractingForeignKeys : MSSQLExtractorTestBase - { - public override string CleanUpScript - { - get - { - return - " drop table A1" + - "\n drop table A2" + - "\n drop table A3" + - "\n drop table B1" + - "\n drop table B2" + - "\n drop table B3"; - } } + protected virtual string GetMSSqlTypesExtractionCleanUpScrypt(string tableName) => $"drop table {tableName}"; [Test] - public void Main() + public void MSSqlTypesExtractionTest() { - string query = "\n create table B1 (b_id int primary key)" + - "\n create table A1 (b_id int references B1(b_id))" + - "\n create table B2 (" + - "\n b_id_1 int, " + - "\n b_id_2 int, " + - "\n CONSTRAINT [B2_PK] PRIMARY KEY CLUSTERED (b_id_1, b_id_2) ON [PRIMARY])" + - "\n create table A2 (" + - "\n b_id_1 int, " + - "\n b_id_2 int, " + - "\n constraint [A2_FK] FOREIGN KEY (b_id_1, b_id_2) " + - "\n REFERENCES B2 (b_id_1, b_id_2) ON DELETE CASCADE ON UPDATE NO ACTION)" + - "\n create table B3 (" + - "\n b_id_1 int," + - "\n b_id_2 int," + - "\n b_id_3 int," + - "\n CONSTRAINT [B3_PK] PRIMARY KEY CLUSTERED (b_id_1, b_id_2, b_id_3) ON [PRIMARY])" + - "\n create table A3 (" + - "\n A_col1 int," + - "\n b_id_3 int," + - "\n b_id_1 int," + - "\n b_id_2 int," + - "\n constraint [A3_FK] FOREIGN KEY (b_id_1, b_id_2, b_id_3) " + - "\n REFERENCES B3 (b_id_1, b_id_2, b_id_3) ON DELETE NO ACTION ON UPDATE CASCADE)"; - ExecuteQuery(query); - - - var model = ExtractCatalog(); - Schema schema = model.DefaultSchema; - - // Validating. - ForeignKey fk1 = (ForeignKey) schema.Tables["A1"].TableConstraints[0]; - Assert.IsNotNull(fk1); - Assert.IsTrue(fk1.Columns[0].Name=="b_id"); - Assert.IsTrue(fk1.ReferencedColumns[0].Name=="b_id"); - Assert.IsTrue(fk1.ReferencedColumns.Count==1); - Assert.IsTrue(fk1.Columns.Count==1); - - ForeignKey fk2 = (ForeignKey) schema.Tables["A2"].TableConstraints[0]; - Assert.IsNotNull(fk1); - Assert.IsTrue(fk2.Name=="A2_FK"); - Assert.IsTrue(fk2.Columns[0].Name=="b_id_1"); - Assert.IsTrue(fk2.ReferencedColumns[0].Name=="b_id_1"); - Assert.IsTrue(fk2.Columns[1].Name=="b_id_2"); - Assert.IsTrue(fk2.ReferencedColumns[1].Name=="b_id_2"); - Assert.IsTrue(fk2.ReferencedColumns.Count==2); - Assert.IsTrue(fk2.Columns.Count==2); - Assert.IsTrue(fk2.OnDelete==ReferentialAction.Cascade); - Assert.IsTrue(fk2.OnUpdate==ReferentialAction.NoAction); + var createTableQuery = GetMSSqlTypesExtractionPrepareScript("mssqlDttTable"); + RegisterCleanupScript(GetMSSqlTypesExtractionCleanUpScrypt, "mssqlDttTable"); - ForeignKey fk3 = (ForeignKey) schema.Tables["A3"].TableConstraints[0]; - Assert.IsNotNull(fk3); - Assert.IsTrue(fk3.Name=="A3_FK"); - Assert.IsTrue(fk3.Columns[0].Name=="b_id_1"); - Assert.IsTrue(fk3.ReferencedColumns[0].Name=="b_id_1"); - Assert.IsTrue(fk3.Columns[1].Name=="b_id_2"); - Assert.IsTrue(fk3.ReferencedColumns[1].Name=="b_id_2"); - Assert.IsTrue(fk3.Columns[2].Name=="b_id_3"); - Assert.IsTrue(fk3.ReferencedColumns[2].Name=="b_id_3"); - Assert.IsTrue(fk3.ReferencedColumns.Count==3); - Assert.IsTrue(fk3.Columns.Count==3); - Assert.IsTrue(fk3.OnDelete==ReferentialAction.NoAction); - Assert.IsTrue(fk3.OnUpdate==ReferentialAction.Cascade); - } - } - - public class MSSQLExtractor_TestExtractingUniqueConstraints : MSSQLExtractorTestBase - { - public override string CleanUpScript - { - get { return "drop table A"; } - } - - [Test] - public void Main() - { - ExecuteQuery( - " Create table A (" + - "\n col_11 int, col_12 int, col_13 int," + - "\n col_21 int, col_22 int, col_23 int," + - "\n CONSTRAINT A_UNIQUE_1 UNIQUE(col_11,col_12,col_13)," + - "\n CONSTRAINT A_UNIQUE_2 UNIQUE(col_21,col_22,col_23))"); - - var model = ExtractCatalog(); - Schema schema = model.DefaultSchema; - - // Validating. - UniqueConstraint A_UNIQUE_1 = (UniqueConstraint) schema.Tables["A"].TableConstraints["A_UNIQUE_1"]; - Assert.IsNotNull(A_UNIQUE_1); - Assert.IsTrue(A_UNIQUE_1.Columns[0].Name=="col_11"); - Assert.IsTrue(A_UNIQUE_1.Columns[1].Name=="col_12"); - Assert.IsTrue(A_UNIQUE_1.Columns[2].Name=="col_13"); - Assert.IsTrue(A_UNIQUE_1.Columns.Count==3); - - UniqueConstraint A_UNIQUE_2 = (UniqueConstraint) schema.Tables["A"].TableConstraints["A_UNIQUE_2"]; - Assert.IsNotNull(A_UNIQUE_2); - Assert.IsTrue(A_UNIQUE_2.Columns[0].Name=="col_21"); - Assert.IsTrue(A_UNIQUE_2.Columns[1].Name=="col_22"); - Assert.IsTrue(A_UNIQUE_2.Columns[2].Name=="col_23"); - Assert.IsTrue(A_UNIQUE_2.Columns.Count==3); - } - } - - public class MSSQLExtractor_TestIndexesExtracted : MSSQLExtractorTestBase - { - public override string CleanUpScript - { - get { return "drop table table1"; } - } - - [Test] - public virtual void Main() - { - ExecuteQuery( - " create table table1 (" + - "\n column1 int, " + - "\n column2 int) " + - "\n create index table1_index1_desc_asc on table1 (column1 desc, column2 asc)" + - "\n create unique index table1_index1_u_asc_desc on table1 (column1 asc, column2 desc)" + - "\n create unique index table1_index_with_included_columns on table1 (column1 asc)" + - "\n include (column2)"); - - var model = ExtractCatalog(); - Schema schema = model.DefaultSchema; - - Assert.IsTrue(schema.Tables["table1"]!=null); - Assert.IsNotNull(schema.Tables["table1"].Indexes["table1_index1_desc_asc"]); - Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns.Count==2); - Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[0].Name=="column1"); - Assert.IsTrue(!schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[0].Ascending); - Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_desc_asc"].Columns[1].Ascending); - - Assert.IsNotNull(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"]); - Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns.Count==2); - Assert.IsTrue(schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[0].Ascending); - Assert.IsTrue(!schema.Tables["table1"].Indexes["table1_index1_u_asc_desc"].Columns[1].Ascending); - - Assert.IsNotNull(schema.Tables["table1"].Indexes["table1_index_with_included_columns"]); - Assert.AreEqual(1, schema.Tables["table1"].Indexes["table1_index_with_included_columns"].Columns.Count, - "Key columns"); - Assert.AreEqual(1, schema.Tables["table1"].Indexes["table1_index_with_included_columns"].NonkeyColumns.Count, - "Included columns"); - } - } - - public class MSSQLExtractor2005_TestSchemaExtraction : MSSQLExtractor_TestSchemaExtraction - { - public override string CleanUpScript - { - get - { - return - "\n drop table schema1.table1" + - "\n drop table schema2.table2" + - "\n drop table schema3.table3" + - "\n drop table schema3.table31" + - "\n drop schema schema1" + - "\n drop schema schema2" + - "\n drop schema schema3"; - ; - } - } - - [Test] - public override void Main() - { - ExecuteQuery(" create schema schema1"); - ExecuteQuery(" create schema schema2"); - ExecuteQuery(" create schema schema3"); - string createTablesSql = - "\n create table schema1.table1(test int, test2 int, test3 int)" + - "\n create table schema2.table2(test int, test2 int, test3 int)" + - "\n create table schema3.table3(test int, test2 int, test3 int)" + - "\n create table schema3.table31(test int, test2 int, test3 int)"; - ExecuteQuery(createTablesSql); - - var model = ExtractCatalog(); - - // Validating. - Assert.IsNotNull(model.Schemas["schema1"]); - Assert.IsNotNull(model.Schemas["schema1"].Tables["table1"]); - Assert.IsNotNull(model.Schemas["schema1"].Tables["table1"].TableColumns["test"]); - Assert.IsNotNull(model.Schemas["schema1"].Tables["table1"].TableColumns["test2"]); - Assert.IsNotNull(model.Schemas["schema1"].Tables["table1"].TableColumns["test3"]); - Assert.IsTrue(model.Schemas["schema1"].Tables.Count==1); - - Assert.IsNotNull(model.Schemas["schema2"]); - Assert.IsNotNull(model.Schemas["schema2"].Tables["table2"]); - Assert.IsNotNull(model.Schemas["schema2"].Tables["table2"].TableColumns["test"]); - Assert.IsNotNull(model.Schemas["schema2"].Tables["table2"].TableColumns["test2"]); - Assert.IsNotNull(model.Schemas["schema2"].Tables["table2"].TableColumns["test3"]); - Assert.IsTrue(model.Schemas["schema2"].Tables.Count==1); + ExecuteQuery(createTableQuery); - Assert.IsNotNull(model.Schemas["schema3"]); - Assert.IsNotNull(model.Schemas["schema3"].Tables["table3"]); - Assert.IsNotNull(model.Schemas["schema3"].Tables["table3"].TableColumns["test"]); - Assert.IsNotNull(model.Schemas["schema3"].Tables["table3"].TableColumns["test2"]); - Assert.IsNotNull(model.Schemas["schema3"].Tables["table3"].TableColumns["test3"]); - Assert.IsTrue(model.Schemas["schema3"].Tables.Count==2); + var table = ExtractDefaultSchema().Tables["mssqlDttTable"]; + + var comparer = StringComparer.InvariantCultureIgnoreCase; + + Assert.IsTrue(table.TableColumns["int_l4"].DataType.Type == SqlType.Int32); + Assert.IsTrue(table.TableColumns["binary_l50"].DataType.Length == 50); + Assert.IsTrue(table.TableColumns["binary_l50"].DataType.Type == SqlType.Binary); + Assert.IsTrue(table.TableColumns["bit_l1"].DataType.Type == SqlType.Boolean); + Assert.IsTrue(table.TableColumns["char_10"].DataType.Length == 5); + Assert.IsTrue(table.TableColumns["char_10"].DataType.Type == SqlType.Char); + Assert.IsTrue(table.TableColumns["datetime_l8"].DataType.Type == SqlType.DateTime); + Assert.IsTrue(table.TableColumns["decimal_p18_s0"].DataType.Type == SqlType.Decimal); + Assert.IsTrue(table.TableColumns["decimal_p18_s0"].DataType.Precision == 18); + Assert.IsTrue(table.TableColumns["decimal_p18_s0"].DataType.Scale == 0); + Assert.IsTrue(table.TableColumns["decimal_p12_s11_l9"].DataType.Type == SqlType.Decimal); + Assert.IsTrue(table.TableColumns["decimal_p12_s11_l9"].DataType.Precision == 12); + Assert.IsTrue(table.TableColumns["decimal_p12_s11_l9"].DataType.Scale == 11); + Assert.IsTrue(table.TableColumns["float_p53"].DataType.Type == SqlType.Double); + Assert.IsTrue(table.TableColumns["float_p53"].DataType.Precision == null); + Assert.IsTrue(table.TableColumns["float_p53"].DataType.Scale == null); + Assert.IsTrue(table.TableColumns["image_16"].DataType.Type == SqlType.VarBinaryMax); + Assert.IsTrue(comparer.Compare(table.TableColumns["money_p19_s4_l8"].DataType.TypeName, "money") == 0); + Assert.IsTrue(table.TableColumns["money_p19_s4_l8"].DataType.Precision == 19); + Assert.IsTrue(table.TableColumns["money_p19_s4_l8"].DataType.Scale == 4); + Assert.IsTrue(table.TableColumns["nchar_l100"].DataType.Type == SqlType.Char); + Assert.IsTrue(table.TableColumns["nchar_l100"].DataType.Length == 100); + Assert.IsTrue(table.TableColumns["ntext"].DataType.Type == SqlType.VarCharMax); + Assert.IsTrue(table.TableColumns["numeric_p5_s5"].DataType.Type == SqlType.Decimal); + Assert.IsTrue(table.TableColumns["numeric_p5_s5"].DataType.Precision == 5); + Assert.IsTrue(table.TableColumns["nvarchar_l50"].DataType.Type == SqlType.VarChar); + Assert.IsTrue(table.TableColumns["nvarchar_l50"].DataType.Length == 50); + Assert.IsTrue(table.TableColumns["real_p24_s0_l4"].DataType.Type == SqlType.Float); + Assert.IsTrue(table.TableColumns["real_p24_s0_l4"].DataType.Precision == null); + Assert.IsTrue(table.TableColumns["real_p24_s0_l4"].DataType.Scale == null); + Assert.IsTrue(table.TableColumns["smalldatetime_l4"].DataType.Type == SqlType.DateTime); + Assert.IsTrue(table.TableColumns["smallint_l2"].DataType.Type == SqlType.Int16); + Assert.IsTrue(comparer.Compare(table.TableColumns["small_money_p10_s4_l4"].DataType.TypeName, "smallmoney") == 0); + Assert.IsTrue(comparer.Compare(table.TableColumns["sql_variant_"].DataType.TypeName, "sql_variant") == 0); + Assert.IsTrue(table.TableColumns["text_16"].DataType.Type == SqlType.VarCharMax); + Assert.IsTrue(comparer.Compare(table.TableColumns["timestamp_l8"].DataType.TypeName, "timestamp") == 0); + Assert.IsTrue(table.TableColumns["tinyint_1_p3_s0_l1"].DataType.Type == SqlType.UInt8); + Assert.IsTrue(table.TableColumns["uniqueidentifier_l16"].DataType.Type == SqlType.Guid); + Assert.IsTrue(table.TableColumns["varbinary_l150"].DataType.Type == SqlType.VarBinary); + Assert.IsTrue(table.TableColumns["varbinary_l150"].DataType.Length == 150); + Assert.IsTrue(table.TableColumns["varchar_l50"].DataType.Type == SqlType.VarChar); + Assert.IsTrue(table.TableColumns["varchar_l50"].DataType.Length == 25); } - } - public class MSSQLExtractor2005_TestColumnTypeExtraction : MSSQLExtractor_TestColumnTypeExtraction - { - /* - public override string CleanUpScript + protected virtual string GetMSSqlViewsExtractionTestPrepareStript() { - get { return base.CleanUpScript + "\n drop table dataTypesTestTable2"; } + return + " EXEC sp_addrole 'role1';" + + "\n CREATE TABLE role1.table1(column1 int, column2 int);" + + "\n CREATE VIEW role1.view1 as Select column1 From role1.table1;" + + "\n CREATE VIEW role1.view2 as Select column1, column2 From role1.table1;"; } - public void Main2() + protected virtual string GetMSSqlViewsExtractionTestCleanUpScript() { - ExecuteQuery( - "create table dataTypesTestTable2(" + - "\n xml_column xml," + - "\n varbinary_max varbinary(max)," + - "\n nvarchar_max nvarchar(max)," + - "\n varchar_max varchar(max))", ConnectionString); - Model model = ExtractCatalog(ConnectionString); - - Schema schema = model.DefaultServer.DefaultCatalog.DefaultSchema; - Assert.IsNotNull(schema.Tables["dataTypesTestTable2"]); - Assert.IsTrue(schema.Tables["dataTypesTestTable2"].TableColumns["varbinary_max"].DataType.DataType == SqlDataType.VarBinaryMax); - Assert.IsTrue(schema.Tables["dataTypesTestTable2"].TableColumns["nvarchar_max"].DataType.DataType == SqlDataType.VarCharMax); - Assert.IsTrue(schema.Tables["dataTypesTestTable2"].TableColumns["varchar_max"].DataType.DataType == SqlDataType.VarCharMax); - Assert.IsTrue(schema.Tables["dataTypesTestTable2"].TableColumns["xml_column"].DataType.DataType == SqlDataType.Xml); - } - */ - } - - public class MSSQLExtractor2005_TestExtractingViews : MSSQLExtractor_TestExtractingViews - { - } - - public class MSSQLExtractor2005_TestExtractingForeignKeys : MSSQLExtractor_TestExtractingForeignKeys - { - } - - public class MSSQLExtractor2005_TestExtractingForeignKeys2 : MSSQLExtractorTestBase - { - public override string CleanUpScript - { - get - { - return - "\n Drop Table A" + - "\n Drop Table B" + - "\n Drop Table B2"; - } + return "\n drop table role1.table1;" + + "\n drop view role1.view1;" + + "\n drop view role1.view2;" + + "\n exec sp_droprole role1"; } [Test] - public void Main() - { - ExecuteQuery( - " Create Table B (b_id int primary key)" + - "\n Create Table B2(b_id_1 int primary key)" + - "\n Create Table A(" + - "\n b_id int ," + - "\n b_id_1 int ," + - "\n Constraint [A_FK_1] Foreign key(b_id) references B(b_id) ON DELETE SET NULL ," + - "\n Constraint [A_FK_2] Foreign key(b_id_1) references B2(b_id_1) ON DELETE SET DEFAULT" + - "\n )"); - var model = ExtractCatalog(); - Schema schema = model.DefaultSchema; - Assert.IsTrue(((ForeignKey) schema.Tables["A"].TableConstraints["A_FK_1"]).OnDelete==ReferentialAction.SetNull); - Assert.IsTrue(((ForeignKey) schema.Tables["A"].TableConstraints["A_FK_2"]).OnDelete==ReferentialAction.SetDefault); - } - } - - public class MSSQLExtractor2005_TestExtractingUniqueConstraints : MSSQLExtractor_TestExtractingUniqueConstraints - { - } - - public class MSSQLExtractor2005_TestIndexesExtracted : MSSQLExtractor_TestIndexesExtracted - { - } - - public class MSSQLExtractor2005_TestPartitionsExtracted : MSSQLExtractorTestBase - { - public override string CleanUpScript - { - get - { - return "IF DB_ID (N'MSSQL2005Extr_PartitionsTest') IS NOT NULL" + - "\n DROP DATABASE MSSQL2005Extr_PartitionsTest;"; - } - } - - public void Main() + public virtual void MSSqlViewsExtractionTest() { - ExecuteQuery("USE master;"); - string createTestDatabaseSql = @"-- Get the SQL Server data path - DECLARE @data_path nvarchar(256); - SET @data_path = (SELECT SUBSTRING(physical_name, 1, CHARINDEX(N'master.mdf', LOWER(physical_name)) - 1) - FROM master.sys.master_files - WHERE database_id = 1 AND file_id = 1); - - -- execute the CREATE DATABASE statement - EXECUTE ('CREATE DATABASE MSSQL2005Extr_PartitionsTest - ON PRIMARY - ( NAME = SPri1_dat, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_P.mdf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 15% ), - ( NAME = SPri2_dat, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_S.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 15% ), - FILEGROUP MSSQL2005Extr_PartitionsTest_FG1 - ( NAME = MSSQL2005Extr_PartitionsTest_D11, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_D11.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ), - ( NAME = MSSQL2005Extr_PartitionsTest_D12, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_D12.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ), - FILEGROUP MSSQL2005Extr_PartitionsTest_FG2 - ( NAME = MSSQL2005Extr_PartitionsTest_D21, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_D21.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ), - ( NAME = MSSQL2005Extr_PartitionsTest_D22, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_D22.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ), - FILEGROUP MSSQL2005Extr_PartitionsTest_FG3 - ( NAME = MSSQL2005Extr_PartitionsTest_D31, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_D31.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ), - ( NAME = MSSQL2005Extr_PartitionsTest_D32, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_D32.ndf'', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ) - LOG ON - ( NAME = Sales_log, - FILENAME = '''+ @data_path + 'MSSQL2005Extr_PartitionsTest_Log.ldf'', - SIZE = 5MB, - MAXSIZE = 25MB, - FILEGROWTH = 5MB )' - );"; - ExecuteQuery(createTestDatabaseSql); - ExecuteQuery("use MSSQL2005Extr_PartitionsTest"); - - // Create partition function. - ExecuteQuery( - "CREATE PARTITION FUNCTION MSSQL2005Extr_PartitionsTest_PFA_LEFT_1_20_30_40 (int)" + - "\n AS RANGE LEFT FOR VALUES (500);"); - - // Create partition scheme. - ExecuteQuery( - "CREATE PARTITION SCHEME MSSQL2005Extr_PartitionsTest_PFA_Schema" + - "\n AS PARTITION MSSQL2005Extr_PartitionsTest_PFA_LEFT_1_20_30_40" + - "\n TO ( " + - "\n MSSQL2005Extr_PartitionsTest_FG1, " + - "\n MSSQL2005Extr_PartitionsTest_FG2);"); - - // Create partitioned tables - ExecuteQuery( - "CREATE TABLE MSSQL2005Extr_PartitionsTest_Table (col1 int, col2 char(10))" + - "\n ON MSSQL2005Extr_PartitionsTest_PFA_Schema (col1)"); - - ExecuteQuery( - "CREATE TABLE MSSQL2005Extr_PartitionsTest_Table2 (col1 int, col2 int)" + - "ON MSSQL2005Extr_PartitionsTest_PFA_Schema (col2) ;"); - - var model = ExtractCatalog(); - Schema schema = model.DefaultSchema; - - Assert.IsNotNull(schema.Tables["MSSQL2005Extr_PartitionsTest_Table"].PartitionDescriptor); + var createViewsQuery = GetMSSqlViewsExtractionTestPrepareStript(); + RegisterCleanupScript(GetMSSqlViewsExtractionTestCleanUpScript); - Assert.IsTrue( - schema.Tables["MSSQL2005Extr_PartitionsTest_Table"] - .PartitionDescriptor - .PartitionSchema.Name=="MSSQL2005Extr_PartitionsTest_PFA_Schema"); + ExecuteQueryLineByLine(createViewsQuery); - Assert.IsTrue( - schema.Tables["MSSQL2005Extr_PartitionsTest_Table"] - .PartitionDescriptor - .PartitionSchema - .PartitionFunction - .BoundaryType==BoundaryType.Left); + var schema = ExtractCatalog().Schemas["role1"]; - AssertUtility.AssertArraysAreEqual( - schema.Tables["MSSQL2005Extr_PartitionsTest_Table"] - .PartitionDescriptor - .PartitionSchema - .PartitionFunction - .BoundaryValues, - new string[] {"0", "500"}); - - AssertUtility.AssertCollectionsAreEqual( - schema.Tables["MSSQL2005Extr_PartitionsTest_Table"] - .PartitionDescriptor - .PartitionSchema - .Filegroups, - new string[] {"MSSQL2005Extr_PartitionsTest_FG1", "MSSQL2005Extr_PartitionsTest_FG2"}); - - Assert.IsTrue(schema.Tables["MSSQL2005Extr_PartitionsTest_Table"] - .PartitionDescriptor - .PartitionMethod==PartitionMethod.Range); + Assert.IsNotNull(schema); + Assert.IsNotNull(schema.Views["view1"]); + Assert.IsNotNull(schema.Views["view2"]); + Assert.IsNotNull(schema.Views["view1"].ViewColumns["column1"]); + Assert.IsNotNull(schema.Views["view2"].ViewColumns["column1"]); + Assert.IsNotNull(schema.Views["view2"].ViewColumns["column2"]); } + #endregion } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/Sqlite/ExtractorTest.cs b/Orm/Xtensive.Orm.Tests.Sql/Sqlite/ExtractorTest.cs index 243788cbdd..4e914df9b2 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/Sqlite/ExtractorTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/Sqlite/ExtractorTest.cs @@ -1,51 +1,89 @@ -using System; +// Copyright (C) 2011-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: Malisa Ncube +// Created: 2011.03.16 + +using System; +using System.Text; using NUnit.Framework; -using Xtensive.Sql.Model; +using Xtensive.Sql; namespace Xtensive.Orm.Tests.Sql.Sqlite { - [TestFixture] - [Explicit] - public class ExtractorTest : SqlTest + public class ExtractorTest: ExtractorTestBase { - [Test] - public void BaseTest() + protected override bool CheckContstraintExtracted => false; + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.Sqlite); + + #region Base test class members + protected override string GetTypesExtractionPrepareScript(string tableName) + { + var dataTypes = Driver.ServerInfo.DataTypes; + var sb = new StringBuilder(); + _ = sb.Append($"CREATE TABLE {tableName} ("); + sb.AppendLine($"{TypeToColumnName[SqlType.Boolean]} [bit] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int16]} [smallint] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int32]} [integer] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Int64]} [bigint] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.UInt8]} [tinyint] NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Decimal]} [decimal]({DecimalPrecision}, {DecimalScale}) NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Float]} [real] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Double]} [float] NULL,"); + + + sb.AppendLine($"{TypeToColumnName[SqlType.DateTime]} [datetime] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.DateTimeOffset]} [datetimeoffset] NULL,"); +#if NET6_0_OR_GREATER + sb.AppendLine($"{TypeToColumnName[SqlType.Date]} [date] NULL,"); + sb.AppendLine($"{TypeToColumnName[SqlType.Time]} [time] NULL,"); +#endif + + sb.AppendLine($"{TypeToColumnName[SqlType.VarCharMax]} [nvarchar] NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.VarBinaryMax]} blob NULL,"); + + sb.AppendLine($"{TypeToColumnName[SqlType.Guid]} uniqueidentifier NULL"); + sb.AppendLine(");"); + + return sb.ToString(); + } + protected override string GetTypesExtractionCleanUpScript(string tableName) => $"drop table if exists {tableName};"; + + protected override string GetForeignKeyExtractionPrepareScript() { - var schema = ExtractDefaultSchema(); - foreach (var table in schema.Tables) { - Console.WriteLine("Table: " + table.Name); - Console.WriteLine("Columns"); - foreach (var column in table.TableColumns) - Console.WriteLine(string.Format(" {0}, {1}, {2}, {3}", column.Name, column.DataType, column.IsNullable, column.DefaultValue)); - Console.WriteLine("Indexes"); - foreach (var index in table.Indexes) { - Console.WriteLine(string.Format(" Name: {0}, Unique : {1}", index.Name, index.IsUnique)); - foreach (var column in index.Columns) - Console.WriteLine(" " + column.Name); - } - Console.WriteLine("Constraints"); - foreach (var constraint in table.TableConstraints) { - var uniqueConstraint = constraint as UniqueConstraint; - if (uniqueConstraint!=null) { - Console.WriteLine(string.Format(" {0}, Primary Key", constraint.Name)); - foreach (var column in uniqueConstraint.Columns) - Console.WriteLine(" " + column.Name); - continue; - } - var foreignKey = constraint as ForeignKey; - if (foreignKey!=null) { - Console.WriteLine(string.Format(" {0}, Foreign Key, {1}, On Update = {2}, On Delete = {3}", constraint.Name, foreignKey.ReferencedTable.Name, foreignKey.OnUpdate, foreignKey.OnDelete)); - foreach (var column in foreignKey.Columns) - Console.WriteLine(" " + column.Name); - continue; - } - } - } + return "CREATE TABLE B1 (b_id int primary key);" + + "CREATE TABLE A1 (b_id int, CONSTRAINT [A1_FK] FOREIGN KEY (b_id) REFERENCES B1(b_id));" + + "CREATE TABLE B2 (b_id_1 int, b_id_2 int, " + + " CONSTRAINT [B2_PK] PRIMARY KEY (b_id_1, b_id_2));" + + "CREATE TABLE A2 (b_id_1 int, b_id_2 int," + + " CONSTRAINT [A2_FK] FOREIGN KEY (b_id_1, b_id_2)" + + " REFERENCES B2 (b_id_1, b_id_2) ON DELETE CASCADE ON UPDATE NO ACTION);" + + "CREATE TABLE B3 (b_id_1 int, b_id_2 int, b_id_3 int," + + " CONSTRAINT [B3_PK] PRIMARY KEY (b_id_1, b_id_2, b_id_3));" + + "CREATE TABLE A3 (A_col1 int, b_id_3 int, b_id_1 int, b_id_2 int," + + " CONSTRAINT [A3_FK] FOREIGN KEY (b_id_1, b_id_2, b_id_3)" + + " REFERENCES B3 (b_id_1, b_id_2, b_id_3) ON DELETE NO ACTION ON UPDATE CASCADE);"; } + protected override string GetForeignKeyExtractionCleanUpScript() => + "drop table if exists A1;" + + "\n drop table if exists A2;" + + "\n drop table if exists A3;" + + "\n drop table if exists B1;" + + "\n drop table if exists B2;" + + "\n drop table if exists B3;"; - protected override void CheckRequirements() + protected override string GetIndexExtractionPrepareScript(string tableName) { - Require.ProviderIs(StorageProvider.Sqlite); + return + $"CREATE TABLE {tableName} (column1 int, column2 int);" + + $"\n CREATE INDEX {tableName}_index1_desc_asc on {tableName} (column1 desc, column2 asc);" + + $"\n CREATE UNIQUE INDEX {tableName}_index1_u_asc_desc on {tableName} (column1 asc, column2 desc);"; } + protected override string GetIndexExtractionCleanUpScript(string tableName) => $"drop table if exists {tableName};"; + #endregion } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/TypeMappingTest.cs b/Orm/Xtensive.Orm.Tests.Sql/TypeMappingTest.cs index 93eea89ad5..f278df1a8b 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/TypeMappingTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/TypeMappingTest.cs @@ -258,6 +258,24 @@ private static object[] GetTestValues(Type type) new DateTimeOffset(2001, 1, 1, 1, 1, 1, 1, new TimeSpan(3, 10, 0)), null }; +#if NET6_0_OR_GREATER + if (type == typeof(DateOnly)) { + return new object[] { + new DateOnly(2005, 5, 5), + new DateOnly(1998, 8, 8), + new DateOnly(1856, 4, 1), + null + }; + } + if (type == typeof(TimeOnly)) { + return new object[] { + new TimeOnly(5, 5, 5), + new TimeOnly(8, 8, 8), + new TimeOnly(5, 6, 7), + null + }; + } +#endif throw new ArgumentOutOfRangeException(); } diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueJira0593_AggregateForSingleColumnTest.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueJira0593_AggregateForSingleColumnTest.cs index ab8b162010..4a194139c4 100644 --- a/Orm/Xtensive.Orm.Tests/Issues/IssueJira0593_AggregateForSingleColumnTest.cs +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueJira0593_AggregateForSingleColumnTest.cs @@ -700,10 +700,10 @@ private static void CheckQueryable(IQueryable query) private static void CheckQueryable(IQueryable query) { var localArray = query.ToArray(); - Assert.AreEqual(localArray.Min().FixDateTimeForProvider(StorageProviderInfo.Instance), query.Min(c => c)); - Assert.AreEqual(localArray.Min().FixDateTimeForProvider(StorageProviderInfo.Instance), query.Min()); - Assert.AreEqual(localArray.Max().FixDateTimeForProvider(StorageProviderInfo.Instance), query.Max(c => c)); - Assert.AreEqual(localArray.Max().FixDateTimeForProvider(StorageProviderInfo.Instance), query.Max()); + Assert.AreEqual(localArray.Min().AdjustDateTimeForCurrentProvider(), query.Min(c => c)); + Assert.AreEqual(localArray.Min().AdjustDateTimeForCurrentProvider(), query.Min()); + Assert.AreEqual(localArray.Max().AdjustDateTimeForCurrentProvider(), query.Max(c => c)); + Assert.AreEqual(localArray.Max().AdjustDateTimeForCurrentProvider(), query.Max()); Assert.AreEqual(localArray.Count(), query.Count()); } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs index c041fcbd64..dbb8671b46 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2016 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2016-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: Alexey Kulakov // Created: 2016.09.15 @@ -36,6 +36,14 @@ protected void ExecuteInsideSession(Action action) } } + protected void ExecuteInsideSession(Action action) + { + using (var session = Domain.OpenSession()) + using (var transaction = session.OpenTransaction()) { + action(session); + } + } + protected override void PopulateData() { PopulateNonPersistentData(); @@ -64,10 +72,23 @@ protected void RunTest(Expression> filter, int rightCount = 1) Assert.AreEqual(rightCount, count); } + protected void RunTest(Session session, Expression> filter, int rightCount = 1) + where T : Entity + { + var count = session.Query.All().Count(filter); + Assert.AreEqual(rightCount, count); + } + protected void RunWrongTest(Expression> filter) where T : Entity { RunTest(filter, 0); } + + protected void RunWrongTest(Session session, Expression> filter) + where T : Entity + { + RunTest(session, filter, 0); + } } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/ComparisonTestDateOnly.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/ComparisonTestDateOnly.cs new file mode 100644 index 0000000000..af6e8e5340 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/ComparisonTestDateOnly.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + + public class ComparisonTestDateOnly : DateTimeBaseTest + { + [Test] + public void EqualsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly == FirstDateOnly); + RunTest(s, c => c.NullableDateOnly == NullableDateOnly); + + RunWrongTest(s, c => c.DateOnly == WrongDateOnly); + RunWrongTest(s, c => c.NullableDateOnly == WrongDateOnly); + RunWrongTest(s, c => c.NullableDateOnly == null); + }); + } + + [Test] + public void NotEqualTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly != FirstDateOnly.AddYears(1)); + RunTest(s, c => c.NullableDateOnly != NullableDateOnly.AddYears(1)); + }); + } + + [Test] + public void CompareTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly > FirstDateOnly.AddDays(-1)); + RunTest(s, c => c.DateOnly < FirstDateOnly.AddDays(1)); + + RunWrongTest(s, c => c.DateOnly > FirstDateOnly); + RunWrongTest(s, c => c.DateOnly < FirstDateOnly); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/ConstructorTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/ConstructorTest.cs new file mode 100644 index 0000000000..d26665b263 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/ConstructorTest.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class ConstructorTest : DateTimeBaseTest + { + [Test] + public void CtorYMD() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { Entity = e, ConstructedDate = new DateOnly(e.Year, e.Month, e.Day) }) + .Where(a => a.ConstructedDate == FirstDateOnly).OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs new file mode 100644 index 0000000000..67265a1d02 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class DateOnlyToStringTest : DateTimeBaseTest + { + [Test] + public void ToStringTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.ToString("o") == FirstDateOnly.ToString("o")); + RunWrongTest(s, c => c.DateOnly.ToString("o") == FirstDateOnly.AddDays(1).ToString("o")); + }); + } + } +} +#endif diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DistinctTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DistinctTest.cs new file mode 100644 index 0000000000..deee0f4fe4 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DistinctTest.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class DistinctTest : DateTimeBaseTest + { + [Test] + public void DistinctByDateOnlyTest() => + ExecuteInsideSession(static (s) => DistinctPrivate(s, c => c.DateOnly)); + + [Test] + public void DistinctByNullableDateOnlyTest() => + ExecuteInsideSession(static (s) => DistinctPrivate(s, c => c.NullableDateOnly)); + + private static void DistinctPrivate(Session session, Expression> selectExpression) + where T : Entity + { + var compiledSelectExpression = selectExpression.Compile(); + var distinctLocal = session.Query.All().ToArray().Select(compiledSelectExpression).Distinct().OrderBy(c => c); + var distinctByServer = session.Query.All().Select(selectExpression).Distinct().OrderBy(c => c); + Assert.IsTrue(distinctLocal.SequenceEqual(distinctByServer)); + + distinctByServer = session.Query.All().Select(selectExpression).Distinct().OrderByDescending(c => c); + Assert.IsFalse(distinctLocal.SequenceEqual(distinctByServer)); + } + } +} +#endif diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/GroupByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/GroupByTest.cs new file mode 100644 index 0000000000..5c34ce186f --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/GroupByTest.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class GroupByTest : DateTimeBaseTest + { + [Test] + public void DateOnlyGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.DateOnly, c => c.Id)); + + [Test] + public void NullableDateOnlyGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.NullableDateOnly, c => c.Id)); + + private void GroupByPrivate(Session session, Expression> groupByExpression, Expression> orderByExpression) + where T : Entity + { + var compiledGroupByExpression = groupByExpression.Compile(); + var compiledOrderByExpression = orderByExpression.Compile(); + var groupByLocal = session.Query.All().ToArray().GroupBy(compiledGroupByExpression).ToArray(); + var groupByServer = session.Query.All().GroupBy(groupByExpression); + foreach (var group in groupByServer) { + Assert.Contains(group, groupByLocal); + var localGroup = groupByLocal.Single(c => c.Key.Equals(group.Key)); + Assert.IsTrue(group.OrderBy(compiledOrderByExpression).SequenceEqual(localGroup.OrderBy(compiledOrderByExpression))); + } + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/JoinTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/JoinTest.cs new file mode 100644 index 0000000000..3730f82f61 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/JoinTest.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class JoinTest : DateTimeBaseTest + { + [Test] + public void DateOnlyJoinTest() + { + ExecuteInsideSession((s) => JoinPrivate, DateOnly, long>(s, + left => left.DateOnly, + right => right.DateOnly, + (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.DateOnly, RightDateTime = right.DateOnly }, + c => c.LeftId, + c => c.RightId)); + } + + [Test] + public void NullableDateOnlyJoinTest() + { + ExecuteInsideSession((s) => JoinPrivate, DateOnly?, long>(s, + left => left.NullableDateOnly, + right => right.NullableDateOnly, + (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.NullableDateOnly, RightDateTime = right.NullableDateOnly }, + c => c.LeftId, + c => c.RightId)); + } + + private static void JoinPrivate(Session session, + Expression> leftJoinExpression, Expression> rightJoinExpression, + Expression> joinResultExpression, Expression> orderByExpression, Expression> thenByExpression) + where T1 : Entity + where T2 : Entity + { + var compiledLeftJoinExpression = leftJoinExpression.Compile(); + var compiledRightJoinExpression = rightJoinExpression.Compile(); + var compiledJoinResultExpression = joinResultExpression.Compile(); + var compiledOrderByExpression = orderByExpression.Compile(); + var compiledThenByExpression = thenByExpression.Compile(); + var joinLocal = session.Query.All().ToArray() + .Join(session.Query.All().ToArray(), compiledLeftJoinExpression, compiledRightJoinExpression, compiledJoinResultExpression) + .OrderBy(compiledOrderByExpression) + .ThenBy(compiledThenByExpression); + + var joinServer = session.Query.All() + .Join(session.Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) + .OrderBy(orderByExpression) + .ThenBy(thenByExpression); + + Assert.IsTrue(joinLocal.SequenceEqual(joinServer)); + + joinServer = session.Query.All() + .Join(session.Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) + .OrderByDescending(orderByExpression) + .ThenBy(thenByExpression); + Assert.IsFalse(joinLocal.SequenceEqual(joinServer)); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/MinMaxTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/MinMaxTest.cs new file mode 100644 index 0000000000..c16b7406d6 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/MinMaxTest.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class MinMaxTest : DateTimeBaseTest + { + [Test] + public void DateOnlyMinMaxTest() + { + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.DateOnly)); + } + + [Test] + public void NullableDateOnlyMinMaxTest() + { + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.NullableDateOnly)); + } + + private static void MinMaxPrivate(Session session, Expression> selectExpression) + where T : Entity + { + var compiledSelectExpression = selectExpression.Compile(); + var minLocal = session.Query.All().ToArray().Min(compiledSelectExpression); + var maxLocal = session.Query.All().ToArray().Max(compiledSelectExpression); + var minServer = session.Query.All().Min(selectExpression); + var maxServer = session.Query.All().Max(selectExpression); + + Assert.AreEqual(minLocal, minServer); + Assert.AreEqual(maxLocal, maxServer); + Assert.AreNotEqual(minLocal, maxServer); + Assert.AreNotEqual(maxLocal, minServer); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs new file mode 100644 index 0000000000..3e7820bae5 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class OperationsTest : DateTimeBaseTest + { + [Test] + public void AddYearsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.AddYears(1) == FirstDateOnly.AddYears(1)); + RunTest(s, c => c.NullableDateOnly.Value.AddYears(33) == NullableDateOnly.AddYears(33)); + + RunWrongTest(s, c => c.DateOnly.AddYears(1) == FirstDateOnly.AddYears(2)); + RunWrongTest(s, c => c.NullableDateOnly.Value.AddYears(33) == NullableDateOnly.AddYears(44)); + }); + } + + [Test] + public void AddMonthsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.AddMonths(1) == FirstDateOnly.AddMonths(1)); + RunTest(s, c => c.NullableDateOnly.Value.AddMonths(33) == NullableDateOnly.AddMonths(33)); + + RunWrongTest(s, c => c.DateOnly.AddMonths(1) == FirstDateOnly.AddMonths(2)); + RunWrongTest(s, c => c.NullableDateOnly.Value.AddMonths(33) == NullableDateOnly.AddMonths(44)); + }); + } + + [Test] + public void AddDaysTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.AddDays(1) == FirstDateOnly.AddDays(1)); + RunTest(s, c => c.NullableDateOnly.Value.AddDays(33) == NullableDateOnly.AddDays(33)); + + RunWrongTest(s, c => c.DateOnly.AddDays(1) == FirstDateOnly.AddDays(2)); + RunWrongTest(s, c => c.NullableDateOnly.Value.AddDays(33) == NullableDateOnly.AddDays(44)); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OrderByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OrderByTest.cs new file mode 100644 index 0000000000..070b4e7051 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OrderByTest.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class OrderByTest : DateTimeBaseTest + { + [Test] + public void DateOnlyOrderByTest() + { + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.DateOnly, c => c.Id); + OrderByPrivate(s, c => c.DateOnly, c => c); + }); + } + + [Test] + public void NullableDateOnlyOrderByTest() + { + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.NullableDateOnly, c => c.Id); + OrderByPrivate(s, c => c.NullableDateOnly, c => c); + }); + } + + private static void OrderByPrivate(Session session, Expression> orderByExpression, Expression> thenByExpression) + where T : Entity + { + var compiledOrderByExpression = orderByExpression.Compile(); + var compiledThenByExpression = thenByExpression.Compile(); + var notOrderedLocal = session.Query.All().ToArray(); + var orderedLocal = notOrderedLocal.OrderBy(compiledOrderByExpression).ThenBy(compiledThenByExpression); + var orderedLocalDescending = notOrderedLocal.OrderByDescending(compiledOrderByExpression).ThenBy(compiledThenByExpression); + var orderedByServer = session.Query.All().OrderBy(orderByExpression).ThenBy(thenByExpression); + var orderedByServerDescending = session.Query.All().OrderByDescending(orderByExpression).ThenBy(thenByExpression); + + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedLocal)); + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocalDescending.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocal.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocalDescending.SequenceEqual(orderedByServer)); + } + + protected static void OrderByPrivate(Session session, Expression> selectorExpression, Expression> orderByExpression) + where T1 : Entity + { + var compiledOrderByExpression = orderByExpression.Compile(); + + var notOrderedLocal = session.Query.All().Select(selectorExpression).ToArray(); + var orderedLocal = notOrderedLocal.OrderBy(compiledOrderByExpression); + var orderedLocalDescending = notOrderedLocal.OrderByDescending(compiledOrderByExpression); + var orderedByServer = session.Query.All().Select(selectorExpression).OrderBy(orderByExpression); + var orderedByServerDescending = session.Query.All().Select(selectorExpression).OrderByDescending(orderByExpression); + + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedLocal)); + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocalDescending.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocal.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocalDescending.SequenceEqual(orderedByServer)); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs new file mode 100644 index 0000000000..2a2031a374 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs @@ -0,0 +1,83 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class PartsExtractionTest : DateTimeBaseTest + { + [Test] + public void ExtractYearTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.Year == FirstDateOnly.Year); + RunTest(s, c => c.NullableDateOnly.Value.Year == NullableDateOnly.Year); + + RunWrongTest(s, c => c.DateOnly.Year == WrongDateOnly.Year); + RunWrongTest(s, c => c.NullableDateOnly.Value.Year == WrongDateOnly.Year); + }); + } + + [Test] + public void ExtractMonthTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.Month == FirstDateOnly.Month); + RunTest(s, c => c.NullableDateOnly.Value.Month == NullableDateOnly.Month); + + RunWrongTest(s, c => c.DateOnly.Month == WrongDateOnly.Month); + RunWrongTest(s, c => c.NullableDateOnly.Value.Month == WrongDateOnly.Month); + + RunTest(s, c => c.DateOnly.Month == FirstDateOnly.Month); + RunTest(s, c => c.NullableDateOnly.Value.Month == NullableDateOnly.Month); + + RunWrongTest(s, c => c.DateOnly.Month == WrongDateOnly.Month); + RunWrongTest(s, c => c.NullableDateOnly.Value.Month == WrongDateOnly.Month); + + }); + } + + [Test] + public void ExtractDayTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.Day == FirstDateOnly.Day); + RunTest(s, c => c.NullableDateOnly.Value.Day == NullableDateOnly.Day); + + RunWrongTest(s, c => c.DateOnly.Day == WrongDateOnly.Day); + RunWrongTest(s, c => c.NullableDateOnly.Value.Day == WrongDateOnly.Day); + }); + } + + [Test] + public void ExtractDayOfYearTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.DayOfYear == FirstDateOnly.DayOfYear); + RunTest(s, c => c.NullableDateOnly.Value.DayOfYear == NullableDateOnly.DayOfYear); + + RunWrongTest(s, c => c.DateOnly.DayOfYear == WrongDateOnly.DayOfYear); + RunWrongTest(s, c => c.NullableDateOnly.Value.DayOfYear == WrongDateOnly.DayOfYear); + }); + } + + [Test] + public void ExtractDayOfWeekTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateOnly.DayOfWeek == FirstDateOnly.DayOfWeek); + RunTest(s, c => c.NullableDateOnly.Value.DayOfWeek == NullableDateOnly.DayOfWeek); + + RunWrongTest(s, c => c.DateOnly.DayOfWeek == WrongDateOnly.DayOfWeek); + RunWrongTest(s, c => c.NullableDateOnly.Value.DayOfWeek == WrongDateOnly.DayOfWeek); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/WhereTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/WhereTest.cs new file mode 100644 index 0000000000..30115b9c0c --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/WhereTest.cs @@ -0,0 +1,53 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateOnlys +{ + public class WhereTest : DateTimeBaseTest + { + [Test] + public void DateOnlyWhereTest() + { + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.DateOnly == FirstDateOnly, c => c.Id); + WherePrivate(s, c => c.DateOnly.Day == FirstDateOnly.Day, c => c.Id); + WherePrivate(s, c => c.DateOnly.Month == FirstDateOnly.Month, c => c.Id); + }); + } + + [Test] + public void NullableDateOnlyWhereTest() + { + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.NullableDateOnly == FirstDateOnly, c => c.Id); + WherePrivate(s, c => c.NullableDateOnly == null, c => c.Id); + WherePrivate(s, c => c.NullableDateOnly.HasValue && c.NullableDateOnly.Value.Day == FirstDateOnly.Day, c => c.Id); + WherePrivate(s, c => c.NullableDateOnly.HasValue && c.NullableDateOnly.Value.Month == FirstDateOnly.Month, c => c.Id); + }); + } + + private static void WherePrivate(Session session, Expression> whereExpression, Expression> orderByExpression) + where T : Entity + { + var compiledWhereExpression = whereExpression.Compile(); + var compiledOrderByExpression = orderByExpression.Compile(); + + var whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression); + var whereByServer = session.Query.All().Where(whereExpression).OrderBy(orderByExpression).ToList(); + Assert.IsTrue(whereLocal.SequenceEqual(whereByServer)); + + whereByServer = session.Query.All().Where(whereExpression).OrderByDescending(orderByExpression).ToList(); + Assert.IsFalse(whereLocal.SequenceEqual(whereByServer)); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ComparisonTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ComparisonTest.cs index e0dd74a9fb..73d7923676 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ComparisonTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ComparisonTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2021 Xtensive LLC. +// Copyright (C) 2016-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: Alex Groznov @@ -14,7 +14,7 @@ public class ComparisonTest : DateTimeBaseTest [Test] public void EqualsTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { RunTest(c => c.DateTime==FirstDateTime); RunTest(c => c.MillisecondDateTime==FirstMillisecondDateTime); RunTest(c => c.NullableDateTime==NullableDateTime); @@ -29,7 +29,7 @@ public void EqualsTest() [Test] public void NotEqualTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { RunTest(c=>c.DateTime!=FirstDateTime.AddYears(1)); RunTest(c => c.MillisecondDateTime!=FirstMillisecondDateTime.AddYears(1)); RunTest(c=>c.NullableDateTime!=NullableDateTime.AddYears(1)); @@ -40,18 +40,18 @@ public void NotEqualTest() public void CompareTest() { Require.ProviderIsNot(StorageProvider.MySql); - ExecuteInsideSession(() => { - RunTest(c => c.DateTime > FirstDateTime.Date); - RunTest(c => c.DateTime > FirstDateTime.AddSeconds(-1)); - RunTest(c => c.MillisecondDateTime > FirstMillisecondDateTime.AddMilliseconds(-1)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime > FirstDateTime.Date); + RunTest(s, c => c.DateTime > FirstDateTime.AddSeconds(-1)); + RunTest(s, c => c.MillisecondDateTime > FirstMillisecondDateTime.AddMilliseconds(-1)); - RunTest(c => c.DateTime < FirstDateTime.Date.AddDays(1)); - RunTest(c => c.DateTime < FirstDateTime.AddSeconds(1)); - RunTest(c => c.MillisecondDateTime < FirstMillisecondDateTime.AddMilliseconds(1)); + RunTest(s, c => c.DateTime < FirstDateTime.Date.AddDays(1)); + RunTest(s, c => c.DateTime < FirstDateTime.AddSeconds(1)); + RunTest(s, c => c.MillisecondDateTime < FirstMillisecondDateTime.AddMilliseconds(1)); - RunWrongTest(c => c.DateTime > FirstDateTime); - RunWrongTest(c => c.MillisecondDateTime > FirstMillisecondDateTime); - RunWrongTest(c => c.MillisecondDateTime < FirstMillisecondDateTime.Date); + RunWrongTest(s, c => c.DateTime > FirstDateTime); + RunWrongTest(s, c => c.MillisecondDateTime > FirstMillisecondDateTime); + RunWrongTest(s, c => c.MillisecondDateTime < FirstMillisecondDateTime.Date); }); } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ConstructorTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ConstructorTest.cs new file mode 100644 index 0000000000..693037f7c0 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/ConstructorTest.cs @@ -0,0 +1,53 @@ +// Copyright (C) 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.Linq; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateTimes +{ + public class ConstructorTest : DateTimeBaseTest + { + [Test] + public void CtorYMDHMSM() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { + Entity = e, + ConstructedDate = new DateTime(e.Year, e.Month, e.Day, e.Hour, e.Minute, e.Second, e.Millisecond) }) + .Where(a => a.ConstructedDate == FirstMillisecondDateTime).OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void CtorYMDHMS() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { + Entity = e, + ConstructedDate = new DateTime(e.Year, e.Month, e.Day, e.Hour, e.Minute, e.Second) + }) + .Where(a => a.ConstructedDate == FirstDateTime).OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void CtorYMD() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { Entity = e, ConstructedDate = new DateTime(e.Year, e.Month, e.Day) }) + .Where(a => a.ConstructedDate == FirstDateTime.Date).OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + } +} diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs index b58dd50aee..48370ebfa1 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2016 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2016-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: Alex Groznov // Created: 2016.08.01 @@ -14,9 +14,9 @@ public class DateTimeToIsoTest : DateTimeBaseTest [Test] public void ToIsoStringTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.ToString("s")==FirstDateTime.ToString("s")); - RunWrongTest(c => c.DateTime.ToString("s")==FirstDateTime.AddMinutes(1).ToString("s")); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.ToString("s")==FirstDateTime.ToString("s")); + RunWrongTest(s, c => c.DateTime.ToString("s")==FirstDateTime.AddMinutes(1).ToString("s")); }); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DistinctTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DistinctTest.cs index e1c301044d..b4e4e6607e 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DistinctTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DistinctTest.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2016 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2016-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: Alex Groznov // Created: 2016.08.01 @@ -15,32 +15,26 @@ namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateTimes public class DistinctTest : DateTimeBaseTest { [Test] - public void DistinctByDateTimeTest() - { - ExecuteInsideSession(() => DistinctPrivate(c => c.DateTime)); - } + public void DistinctByDateTimeTest() => + ExecuteInsideSession((s) => DistinctPrivate(s,c => c.DateTime)); [Test] - public void DistinctByDateTimeWithMillisecondsTest() - { - ExecuteInsideSession(() => DistinctPrivate(c => c.DateTime)); - } + public void DistinctByDateTimeWithMillisecondsTest() => + ExecuteInsideSession((s) => DistinctPrivate(s, c => c.DateTime)); [Test] - public void DistinctByNullableDateTimeTest() - { - ExecuteInsideSession(() => DistinctPrivate(c => c.DateTime)); - } + public void DistinctByNullableDateTimeTest() => + ExecuteInsideSession((s) => DistinctPrivate(s, c => c.DateTime)); - private void DistinctPrivate(Expression> selectExpression) + private static void DistinctPrivate(Session session, Expression> selectExpression) where T : Entity { var compiledSelectExpression = selectExpression.Compile(); - var distinctLocal = Query.All().ToArray().Select(compiledSelectExpression).Distinct().OrderBy(c => c); - var distinctByServer = Query.All().Select(selectExpression).Distinct().OrderBy(c => c); + var distinctLocal = session.Query.All().ToArray().Select(compiledSelectExpression).Distinct().OrderBy(c => c); + var distinctByServer = session.Query.All().Select(selectExpression).Distinct().OrderBy(c => c); Assert.IsTrue(distinctLocal.SequenceEqual(distinctByServer)); - distinctByServer = Query.All().Select(selectExpression).Distinct().OrderByDescending(c => c); + distinctByServer = session.Query.All().Select(selectExpression).Distinct().OrderByDescending(c => c); Assert.IsFalse(distinctLocal.SequenceEqual(distinctByServer)); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/GroupByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/GroupByTest.cs index ccae27229b..b55afd4ba1 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/GroupByTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/GroupByTest.cs @@ -1,3 +1,7 @@ +// Copyright (C) 2016-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.Linq; using System.Linq.Expressions; @@ -9,30 +13,24 @@ namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateTimes public class GroupByTest : DateTimeBaseTest { [Test] - public void DateTimeGroupByTest() - { - ExecuteInsideSession(() => GroupByPrivate(c => c.DateTime, c => c.Id)); - } + public void DateTimeGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.DateTime, c => c.Id)); [Test] - public void MillisecondDateTimeGroupByTest() - { - ExecuteInsideSession(() => GroupByPrivate(c => c.DateTime, c => c.Id)); - } + public void MillisecondDateTimeGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.DateTime, c => c.Id)); [Test] - public void NullableDateTimeGroupByTest() - { - ExecuteInsideSession(() => GroupByPrivate(c => c.DateTime, c => c.Id)); - } + public void NullableDateTimeGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.DateTime, c => c.Id)); - private void GroupByPrivate(Expression> groupByExpression, Expression> orderByExpression) + private static void GroupByPrivate(Session session, Expression> groupByExpression, Expression> orderByExpression) where T : Entity { var compiledGroupByExpression = groupByExpression.Compile(); var compiledOrderByExpression = orderByExpression.Compile(); - var groupByLocal = Query.All().ToArray().GroupBy(compiledGroupByExpression).ToArray(); - var groupByServer = Query.All().GroupBy(groupByExpression); + var groupByLocal = session.Query.All().ToArray().GroupBy(compiledGroupByExpression).ToArray(); + var groupByServer = session.Query.All().GroupBy(groupByExpression); foreach (var group in groupByServer) { Assert.Contains(group, groupByLocal); var localGroup = groupByLocal.Single(c => c.Key.Equals(group.Key)); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/JoinTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/JoinTest.cs index 7eb9f0794d..0c0b34bfac 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/JoinTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/JoinTest.cs @@ -1,3 +1,7 @@ +// Copyright (C) 2016-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.Linq; using System.Linq.Expressions; @@ -11,7 +15,7 @@ public class JoinTest : DateTimeBaseTest [Test] public void DateTimeJoinTest() { - ExecuteInsideSession(() => JoinPrivate, DateTime, long>( + ExecuteInsideSession((s) => JoinPrivate, DateTime, long>(s, left => left.DateTime, right => right.DateTime, (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.DateTime, RightDateTime = right.DateTime }, @@ -22,7 +26,7 @@ public void DateTimeJoinTest() [Test] public void MillisecondDateTimeJoinTest() { - ExecuteInsideSession(() => JoinPrivate, DateTime, long>( + ExecuteInsideSession((s) => JoinPrivate, DateTime, long>(s, left => left.DateTime, right => right.DateTime, (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.DateTime, RightDateTime = right.DateTime }, @@ -33,7 +37,7 @@ public void MillisecondDateTimeJoinTest() [Test] public void NullableDateTimeJoinTest() { - ExecuteInsideSession(() => JoinPrivate, DateTime?, long>( + ExecuteInsideSession((s) => JoinPrivate, DateTime?, long>(s, left => left.DateTime, right => right.DateTime, (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.DateTime, RightDateTime = right.DateTime }, @@ -41,7 +45,8 @@ public void NullableDateTimeJoinTest() c => c.RightId)); } - private void JoinPrivate(Expression> leftJoinExpression, Expression> rightJoinExpression, + private static void JoinPrivate(Session session, + Expression> leftJoinExpression, Expression> rightJoinExpression, Expression> joinResultExpression, Expression> orderByExpression, Expression> thenByExpression) where T1 : Entity where T2 : Entity @@ -51,20 +56,20 @@ private void JoinPrivate(Expression> leftJoi var compiledJoinResultExpression = joinResultExpression.Compile(); var compiledOrderByExpression = orderByExpression.Compile(); var compiledThenByExpression = thenByExpression.Compile(); - var joinLocal = Query.All().ToArray() - .Join(Query.All().ToArray(), compiledLeftJoinExpression, compiledRightJoinExpression, compiledJoinResultExpression) + var joinLocal = session.Query.All().ToArray() + .Join(session.Query.All().ToArray(), compiledLeftJoinExpression, compiledRightJoinExpression, compiledJoinResultExpression) .OrderBy(compiledOrderByExpression) .ThenBy(compiledThenByExpression); - var joinServer = Query.All() - .Join(Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) + var joinServer = session.Query.All() + .Join(session.Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) .OrderBy(orderByExpression) .ThenBy(thenByExpression); Assert.IsTrue(joinLocal.SequenceEqual(joinServer)); - joinServer = Query.All() - .Join(Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) + joinServer = session.Query.All() + .Join(session.Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) .OrderByDescending(orderByExpression) .ThenBy(thenByExpression); Assert.IsFalse(joinLocal.SequenceEqual(joinServer)); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/MinMaxTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/MinMaxTest.cs index 78cc986822..f00d2194b2 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/MinMaxTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/MinMaxTest.cs @@ -1,3 +1,7 @@ +// Copyright (C) 2016-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.Linq; using System.Linq.Expressions; @@ -11,29 +15,29 @@ public class MinMaxTest : DateTimeBaseTest [Test] public void DateTimeMinMaxTest() { - ExecuteInsideSession(() => MinMaxPrivate(c => c.DateTime)); + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.DateTime)); } [Test] public void MillisecondDateTimeMinMaxTest() { - ExecuteInsideSession(() => MinMaxPrivate(c => c.DateTime)); + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.DateTime)); } [Test] public void NullableDateTimeMinMaxTest() { - ExecuteInsideSession(() => MinMaxPrivate(c => c.DateTime)); + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.DateTime)); } - private void MinMaxPrivate(Expression> selectExpression) + private static void MinMaxPrivate(Session session, Expression> selectExpression) where T : Entity { var compiledSelectExpression = selectExpression.Compile(); - var minLocal = Query.All().ToArray().Min(compiledSelectExpression); - var maxLocal = Query.All().ToArray().Max(compiledSelectExpression); - var minServer = Query.All().Min(selectExpression); - var maxServer = Query.All().Max(selectExpression); + var minLocal = session.Query.All().ToArray().Min(compiledSelectExpression); + var maxLocal = session.Query.All().ToArray().Max(compiledSelectExpression); + var minServer = session.Query.All().Min(selectExpression); + var maxServer = session.Query.All().Max(selectExpression); Assert.AreEqual(minLocal, minServer); Assert.AreEqual(maxLocal, maxServer); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs index b0b2bb46d7..3b73872219 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs @@ -192,14 +192,14 @@ public void MinusDateTimeTest() } [Test] - public void MysqlMinisDateTimeTest() + public void MysqlMinusDateTimeTest() { Require.ProviderIs(StorageProvider.MySql); ExecuteInsideSession(() => { - var firstDateTime = FirstDateTime.FixDateTimeForProvider(StorageProviderInfo.Instance); - var firstMillisecondDateTime = FirstMillisecondDateTime.FixDateTimeForProvider(StorageProviderInfo.Instance); - var secondDateTime = SecondDateTime.FixDateTimeForProvider(StorageProviderInfo.Instance); - var nullableDateTime = NullableDateTime.FixDateTimeForProvider(StorageProviderInfo.Instance); + var firstDateTime = FirstDateTime.AdjustDateTime(0); + var firstMillisecondDateTime = FirstMillisecondDateTime.AdjustDateTime(0); + var secondDateTime = SecondDateTime.AdjustDateTime(0); + var nullableDateTime = NullableDateTime.AdjustDateTime(0); RunTest(c => c.DateTime - secondDateTime == firstDateTime - secondDateTime); RunTest(c => c.MillisecondDateTime - secondDateTime == firstMillisecondDateTime - secondDateTime); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OrderByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OrderByTest.cs index 925a678cd6..ece14ffa9c 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OrderByTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OrderByTest.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2016 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2016-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: Alex Groznov // Created: 2016.08.01 @@ -17,40 +17,41 @@ public class OrderByTest : DateTimeBaseTest [Test] public void DateTimeOrderByTest() { - ExecuteInsideSession(() => { - OrderByPrivate(c => c.DateTime, c => c.Id); - OrderByPrivate(c => c.DateTime, c => c); + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.DateTime, c => c.Id); + OrderByPrivate(s, c => c.DateTime, c => c); }); } [Test] public void MillisecondDateTimeOrderByTest() { - ExecuteInsideSession(() => { - OrderByPrivate(c => c.DateTime, c => c.Id); - OrderByPrivate(c => c.DateTime, c => c); + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.DateTime, c => c.Id); + OrderByPrivate(s, c => c.DateTime, c => c); }); } [Test] public void NullableDateTimeOrderByTest() { - ExecuteInsideSession(() => { - OrderByPrivate(c => c.DateTime, c => c.Id); - OrderByPrivate(c => c.DateTime, c => c); + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.DateTime, c => c.Id); + OrderByPrivate(s, c => c.DateTime, c => c); }); } - private void OrderByPrivate(Expression> orderByExpression, Expression> thenByExpression) + private static void OrderByPrivate(Session session, + Expression> orderByExpression, Expression> thenByExpression) where T : Entity { var compiledOrderByExpression = orderByExpression.Compile(); var compiledThenByExpression = thenByExpression.Compile(); - var notOrderedLocal = Query.All().ToArray(); + var notOrderedLocal = session.Query.All().ToArray(); var orderedLocal = notOrderedLocal.OrderBy(compiledOrderByExpression).ThenBy(compiledThenByExpression); var orderedLocalDescending = notOrderedLocal.OrderByDescending(compiledOrderByExpression).ThenBy(compiledThenByExpression); - var orderedByServer = Query.All().OrderBy(orderByExpression).ThenBy(thenByExpression); - var orderedByServerDescending = Query.All().OrderByDescending(orderByExpression).ThenBy(thenByExpression); + var orderedByServer = session.Query.All().OrderBy(orderByExpression).ThenBy(thenByExpression); + var orderedByServerDescending = session.Query.All().OrderByDescending(orderByExpression).ThenBy(thenByExpression); Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedLocal)); Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedByServer)); @@ -60,16 +61,17 @@ private void OrderByPrivate(Expression> orderByExpress Assert.IsFalse(orderedLocalDescending.SequenceEqual(orderedByServer)); } - protected void OrderByPrivate(Expression> selectorExpression, Expression> orderByExpression) + protected static void OrderByPrivate(Session session, + Expression> selectorExpression, Expression> orderByExpression) where T1 : Entity { var compiledOrderByExpression = orderByExpression.Compile(); - var notOrderedLocal = Query.All().Select(selectorExpression).ToArray(); + var notOrderedLocal = session.Query.All().Select(selectorExpression).ToArray(); var orderedLocal = notOrderedLocal.OrderBy(compiledOrderByExpression); var orderedLocalDescending = notOrderedLocal.OrderByDescending(compiledOrderByExpression); - var orderedByServer = Query.All().Select(selectorExpression).OrderBy(orderByExpression); - var orderedByServerDescending = Query.All().Select(selectorExpression).OrderByDescending(orderByExpression); + var orderedByServer = session.Query.All().Select(selectorExpression).OrderBy(orderByExpression); + var orderedByServerDescending = session.Query.All().Select(selectorExpression).OrderByDescending(orderByExpression); Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedLocal)); Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedByServer)); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs index fb5e5c8585..1d1b2a574e 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2022 Xtensive LLC. +// Copyright (C) 2016-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: Alex Groznov @@ -15,84 +15,84 @@ public class PartsExtractionTest : DateTimeBaseTest [Test] public void ExtractYearTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Year == FirstDateTime.Year); - RunTest(c => c.MillisecondDateTime.Year == FirstMillisecondDateTime.Year); - RunTest(c => c.NullableDateTime.Value.Year == NullableDateTime.Year); - - RunWrongTest(c => c.DateTime.Year == WrongDateTime.Year); - RunWrongTest(c => c.MillisecondDateTime.Year == WrongMillisecondDateTime.Year); - RunWrongTest(c => c.NullableDateTime.Value.Year == WrongDateTime.Year); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Year == FirstDateTime.Year); + RunTest(s, c => c.MillisecondDateTime.Year == FirstMillisecondDateTime.Year); + RunTest(s, c => c.NullableDateTime.Value.Year == NullableDateTime.Year); + + RunWrongTest(s, c => c.DateTime.Year == WrongDateTime.Year); + RunWrongTest(s, c => c.MillisecondDateTime.Year == WrongMillisecondDateTime.Year); + RunWrongTest(s, c => c.NullableDateTime.Value.Year == WrongDateTime.Year); }); } [Test] public void ExtractMonthTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Month == FirstDateTime.Month); - RunTest(c => c.MillisecondDateTime.Month == FirstMillisecondDateTime.Month); - RunTest(c => c.NullableDateTime.Value.Month == NullableDateTime.Month); - - RunWrongTest(c => c.DateTime.Month == WrongDateTime.Month); - RunWrongTest(c => c.MillisecondDateTime.Month == WrongMillisecondDateTime.Month); - RunWrongTest(c => c.NullableDateTime.Value.Month == WrongDateTime.Month); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Month == FirstDateTime.Month); + RunTest(s, c => c.MillisecondDateTime.Month == FirstMillisecondDateTime.Month); + RunTest(s, c => c.NullableDateTime.Value.Month == NullableDateTime.Month); + + RunWrongTest(s, c => c.DateTime.Month == WrongDateTime.Month); + RunWrongTest(s, c => c.MillisecondDateTime.Month == WrongMillisecondDateTime.Month); + RunWrongTest(s, c => c.NullableDateTime.Value.Month == WrongDateTime.Month); }); } [Test] public void ExtractDayTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Day == FirstDateTime.Day); - RunTest(c => c.MillisecondDateTime.Day == FirstMillisecondDateTime.Day); - RunTest(c => c.NullableDateTime.Value.Day == NullableDateTime.Day); - - RunWrongTest(c => c.DateTime.Day == WrongDateTime.Day); - RunWrongTest(c => c.MillisecondDateTime.Day == WrongMillisecondDateTime.Day); - RunWrongTest(c => c.NullableDateTime.Value.Day == WrongDateTime.Day); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Day == FirstDateTime.Day); + RunTest(s, c => c.MillisecondDateTime.Day == FirstMillisecondDateTime.Day); + RunTest(s, c => c.NullableDateTime.Value.Day == NullableDateTime.Day); + + RunWrongTest(s, c => c.DateTime.Day == WrongDateTime.Day); + RunWrongTest(s, c => c.MillisecondDateTime.Day == WrongMillisecondDateTime.Day); + RunWrongTest(s, c => c.NullableDateTime.Value.Day == WrongDateTime.Day); }); } [Test] public void ExtractHourTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Hour == FirstDateTime.Hour); - RunTest(c => c.MillisecondDateTime.Hour == FirstMillisecondDateTime.Hour); - RunTest(c => c.NullableDateTime.Value.Hour == NullableDateTime.Hour); - - RunWrongTest(c => c.DateTime.Hour == WrongDateTime.Hour); - RunWrongTest(c => c.MillisecondDateTime.Hour == WrongMillisecondDateTime.Hour); - RunWrongTest(c => c.NullableDateTime.Value.Hour == WrongDateTime.Hour); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Hour == FirstDateTime.Hour); + RunTest(s, c => c.MillisecondDateTime.Hour == FirstMillisecondDateTime.Hour); + RunTest(s, c => c.NullableDateTime.Value.Hour == NullableDateTime.Hour); + + RunWrongTest(s, c => c.DateTime.Hour == WrongDateTime.Hour); + RunWrongTest(s, c => c.MillisecondDateTime.Hour == WrongMillisecondDateTime.Hour); + RunWrongTest(s, c => c.NullableDateTime.Value.Hour == WrongDateTime.Hour); }); } [Test] public void ExtractMinuteTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Minute == FirstDateTime.Minute); - RunTest(c => c.MillisecondDateTime.Minute == FirstMillisecondDateTime.Minute); - RunTest(c => c.NullableDateTime.Value.Minute == NullableDateTime.Minute); - - RunWrongTest(c => c.DateTime.Minute == WrongDateTime.Minute); - RunWrongTest(c => c.MillisecondDateTime.Minute == WrongMillisecondDateTime.Minute); - RunWrongTest(c => c.NullableDateTime.Value.Minute == WrongDateTime.Minute); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Minute == FirstDateTime.Minute); + RunTest(s, c => c.MillisecondDateTime.Minute == FirstMillisecondDateTime.Minute); + RunTest(s, c => c.NullableDateTime.Value.Minute == NullableDateTime.Minute); + + RunWrongTest(s, c => c.DateTime.Minute == WrongDateTime.Minute); + RunWrongTest(s, c => c.MillisecondDateTime.Minute == WrongMillisecondDateTime.Minute); + RunWrongTest(s, c => c.NullableDateTime.Value.Minute == WrongDateTime.Minute); }); } [Test] public void ExtractSecondTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Second == FirstDateTime.Second); - RunTest(c => c.MillisecondDateTime.Second == FirstMillisecondDateTime.Second); - RunTest(c => c.NullableDateTime.Value.Second == NullableDateTime.Second); - - RunWrongTest(c => c.DateTime.Second == WrongDateTime.Second); - RunWrongTest(c => c.MillisecondDateTime.Second == WrongMillisecondDateTime.Second); - RunWrongTest(c => c.NullableDateTime.Value.Second == WrongDateTime.Second); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Second == FirstDateTime.Second); + RunTest(s, c => c.MillisecondDateTime.Second == FirstMillisecondDateTime.Second); + RunTest(s, c => c.NullableDateTime.Value.Second == NullableDateTime.Second); + + RunWrongTest(s, c => c.DateTime.Second == WrongDateTime.Second); + RunWrongTest(s, c => c.MillisecondDateTime.Second == WrongMillisecondDateTime.Second); + RunWrongTest(s, c => c.NullableDateTime.Value.Second == WrongDateTime.Second); }); } @@ -100,9 +100,9 @@ public void ExtractSecondTest() public void ExtractMillisecondTest() { Require.ProviderIsNot(StorageProvider.MySql); - ExecuteInsideSession(() => { - RunTest(c => c.MillisecondDateTime.Millisecond == FirstMillisecondDateTime.Millisecond); - RunWrongTest(c => c.MillisecondDateTime.Second == WrongMillisecondDateTime.Millisecond); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MillisecondDateTime.Millisecond == FirstMillisecondDateTime.Millisecond); + RunWrongTest(s, c => c.MillisecondDateTime.Second == WrongMillisecondDateTime.Millisecond); }); } @@ -110,24 +110,24 @@ public void ExtractMillisecondTest() public void MysqlExtractMillisecondTest() { Require.ProviderIs(StorageProvider.MySql); - ExecuteInsideSession(() => { - var firstMillisecondDateTime = FirstMillisecondDateTime.FixDateTimeForProvider(StorageProviderInfo.Instance); - RunTest(c => c.MillisecondDateTime.Millisecond == firstMillisecondDateTime.Millisecond); - RunWrongTest(c => c.MillisecondDateTime.Second == WrongMillisecondDateTime.Millisecond); + ExecuteInsideSession((s) => { + var firstMillisecondDateTime = FirstMillisecondDateTime.AdjustDateTime(0); + RunTest(s, c => c.MillisecondDateTime.Millisecond == firstMillisecondDateTime.Millisecond); + RunWrongTest(s, c => c.MillisecondDateTime.Second == WrongMillisecondDateTime.Millisecond); }); } [Test] public void ExtractDateTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Date == FirstDateTime.Date); - RunTest(c => c.MillisecondDateTime.Date == FirstMillisecondDateTime.Date); - RunTest(c => c.NullableDateTime.Value.Date == NullableDateTime.Date); - - RunWrongTest(c => c.DateTime.Date == WrongDateTime.Date); - RunWrongTest(c => c.MillisecondDateTime.Date == WrongMillisecondDateTime.Date); - RunWrongTest(c => c.NullableDateTime.Value.Date == WrongDateTime.Date); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Date == FirstDateTime.Date); + RunTest(s, c => c.MillisecondDateTime.Date == FirstMillisecondDateTime.Date); + RunTest(s, c => c.NullableDateTime.Value.Date == NullableDateTime.Date); + + RunWrongTest(s, c => c.DateTime.Date == WrongDateTime.Date); + RunWrongTest(s, c => c.MillisecondDateTime.Date == WrongMillisecondDateTime.Date); + RunWrongTest(s, c => c.NullableDateTime.Value.Date == WrongDateTime.Date); }); } @@ -140,52 +140,52 @@ public void ExtractDateTest() public void ExtractDateFromMicrosecondsTest(string testValueString) { Require.ProviderIs(StorageProvider.SqlServer); - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var testDateTime = DateTime.Parse(testValueString); - _ = new SingleDateTimeEntity() { MillisecondDateTime = testDateTime }; - RunTest(c => c.MillisecondDateTime.Date == testDateTime.Date); + _ = new SingleDateTimeEntity(s) { MillisecondDateTime = testDateTime }; + RunTest(s, c => c.MillisecondDateTime.Date == testDateTime.Date); }); } [Test] public void ExtractTimeOfDayTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.TimeOfDay == FirstDateTime.TimeOfDay); - RunTest(c => c.MillisecondDateTime.TimeOfDay == FirstMillisecondDateTime.TimeOfDay); - RunTest(c => c.NullableDateTime.Value.TimeOfDay == NullableDateTime.TimeOfDay); - - RunWrongTest(c => c.DateTime.TimeOfDay == WrongDateTime.TimeOfDay); - RunWrongTest(c => c.MillisecondDateTime.TimeOfDay == WrongMillisecondDateTime.TimeOfDay); - RunWrongTest(c => c.NullableDateTime.Value.TimeOfDay == WrongDateTime.TimeOfDay); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.TimeOfDay == FirstDateTime.TimeOfDay); + RunTest(s, c => c.MillisecondDateTime.TimeOfDay == FirstMillisecondDateTime.TimeOfDay); + RunTest(s, c => c.NullableDateTime.Value.TimeOfDay == NullableDateTime.TimeOfDay); + + RunWrongTest(s, c => c.DateTime.TimeOfDay == WrongDateTime.TimeOfDay); + RunWrongTest(s, c => c.MillisecondDateTime.TimeOfDay == WrongMillisecondDateTime.TimeOfDay); + RunWrongTest(s, c => c.NullableDateTime.Value.TimeOfDay == WrongDateTime.TimeOfDay); }); } [Test] public void ExtractDayOfYearTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.DayOfYear == FirstDateTime.DayOfYear); - RunTest(c => c.MillisecondDateTime.DayOfYear == FirstMillisecondDateTime.DayOfYear); - RunTest(c => c.NullableDateTime.Value.DayOfYear == NullableDateTime.DayOfYear); - - RunWrongTest(c => c.DateTime.DayOfYear == WrongDateTime.DayOfYear); - RunWrongTest(c => c.MillisecondDateTime.DayOfYear == WrongMillisecondDateTime.DayOfYear); - RunWrongTest(c => c.NullableDateTime.Value.DayOfYear == WrongDateTime.DayOfYear); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.DayOfYear == FirstDateTime.DayOfYear); + RunTest(s, c => c.MillisecondDateTime.DayOfYear == FirstMillisecondDateTime.DayOfYear); + RunTest(s, c => c.NullableDateTime.Value.DayOfYear == NullableDateTime.DayOfYear); + + RunWrongTest(s, c => c.DateTime.DayOfYear == WrongDateTime.DayOfYear); + RunWrongTest(s, c => c.MillisecondDateTime.DayOfYear == WrongMillisecondDateTime.DayOfYear); + RunWrongTest(s, c => c.NullableDateTime.Value.DayOfYear == WrongDateTime.DayOfYear); }); } [Test] public void ExtractDayOfWeekTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.DayOfWeek == FirstDateTime.DayOfWeek); - RunTest(c => c.MillisecondDateTime.DayOfWeek == FirstMillisecondDateTime.DayOfWeek); - RunTest(c => c.NullableDateTime.Value.DayOfWeek == NullableDateTime.DayOfWeek); - - RunWrongTest(c => c.DateTime.DayOfWeek == WrongDateTime.DayOfWeek); - RunWrongTest(c => c.MillisecondDateTime.DayOfWeek == WrongMillisecondDateTime.DayOfWeek); - RunWrongTest(c => c.NullableDateTime.Value.DayOfWeek == WrongDateTime.DayOfWeek); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.DayOfWeek == FirstDateTime.DayOfWeek); + RunTest(s, c => c.MillisecondDateTime.DayOfWeek == FirstMillisecondDateTime.DayOfWeek); + RunTest(s, c => c.NullableDateTime.Value.DayOfWeek == NullableDateTime.DayOfWeek); + + RunWrongTest(s, c => c.DateTime.DayOfWeek == WrongDateTime.DayOfWeek); + RunWrongTest(s, c => c.MillisecondDateTime.DayOfWeek == WrongMillisecondDateTime.DayOfWeek); + RunWrongTest(s, c => c.NullableDateTime.Value.DayOfWeek == WrongDateTime.DayOfWeek); }); } @@ -194,9 +194,9 @@ public void ExtractTimeOfDayTicksTest() { Require.ProviderIsNot(StorageProvider.PostgreSql | StorageProvider.Oracle); - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.TimeOfDay.Ticks == FirstDateTime.TimeOfDay.Ticks); - RunWrongTest(c => c.DateTime.TimeOfDay.Ticks < FirstDateTime.TimeOfDay.Ticks); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.TimeOfDay.Ticks == FirstDateTime.TimeOfDay.Ticks); + RunWrongTest(s, c => c.DateTime.TimeOfDay.Ticks < FirstDateTime.TimeOfDay.Ticks); }); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/WhereTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/WhereTest.cs index 56dec921c9..596b0cb879 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/WhereTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/WhereTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2021 Xtensive LLC. +// Copyright (C) 2016-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: Alex Groznov @@ -17,10 +17,10 @@ public class WhereTest : DateTimeBaseTest [Test] public void DateTimeWhereTest() { - ExecuteInsideSession(() => { - WherePrivate(c => c.DateTime == FirstDateTime, c => c.Id); - WherePrivate(c => c.DateTime.Hour == FirstDateTime.Hour, c => c.Id); - WherePrivate(c => c.DateTime.Second == FirstDateTime.Second, c => c.Id); + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.DateTime == FirstDateTime, c => c.Id); + WherePrivate(s, c => c.DateTime.Hour == FirstDateTime.Hour, c => c.Id); + WherePrivate(s, c => c.DateTime.Second == FirstDateTime.Second, c => c.Id); }); } @@ -28,35 +28,35 @@ public void DateTimeWhereTest() public void MillisecondDateTimeWhereTest() { Require.ProviderIsNot(StorageProvider.MySql); - ExecuteInsideSession(() => { - WherePrivate(c => c.DateTime == FirstMillisecondDateTime, c => c.Id); - WherePrivate(c => c.DateTime.Hour == FirstMillisecondDateTime.Hour, c => c.Id); - WherePrivate(c => c.DateTime.Millisecond == FirstMillisecondDateTime.Millisecond, c => c.Id); + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.DateTime == FirstMillisecondDateTime, c => c.Id); + WherePrivate(s, c => c.DateTime.Hour == FirstMillisecondDateTime.Hour, c => c.Id); + WherePrivate(s, c => c.DateTime.Millisecond == FirstMillisecondDateTime.Millisecond, c => c.Id); }); } [Test] public void NullableDateTimeWhereTest() { - ExecuteInsideSession(() => { - WherePrivate(c => c.DateTime == FirstDateTime, c => c.Id); - WherePrivate(c => c.DateTime == null, c => c.Id); - WherePrivate(c => c.DateTime.HasValue && c.DateTime.Value.Hour == FirstDateTime.Hour, c => c.Id); - WherePrivate(c => c.DateTime.HasValue && c.DateTime.Value.Second == FirstDateTime.Second, c => c.Id); + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.DateTime == FirstDateTime, c => c.Id); + WherePrivate(s, c => c.DateTime == null, c => c.Id); + WherePrivate(s, c => c.DateTime.HasValue && c.DateTime.Value.Hour == FirstDateTime.Hour, c => c.Id); + WherePrivate(s, c => c.DateTime.HasValue && c.DateTime.Value.Second == FirstDateTime.Second, c => c.Id); }); } - private void WherePrivate(Expression> whereExpression, Expression> orderByExpression) + private static void WherePrivate(Session session, Expression> whereExpression, Expression> orderByExpression) where T : Entity { var compiledWhereExpression = whereExpression.Compile(); var compiledOrderByExpression = orderByExpression.Compile(); - var whereLocal = Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression); - var whereByServer = Query.All().Where(whereExpression).OrderBy(orderByExpression); + var whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression); + var whereByServer = session.Query.All().Where(whereExpression).OrderBy(orderByExpression); Assert.IsTrue(whereLocal.SequenceEqual(whereByServer)); - whereByServer = Query.All().Where(whereExpression).OrderByDescending(orderByExpression); + whereByServer = session.Query.All().Where(whereExpression).OrderByDescending(orderByExpression); Assert.IsFalse(whereLocal.SequenceEqual(whereByServer)); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs index 91fa766b84..66d42a5ce1 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2016 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2016-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: Alexey Kulakov // Created: 2016.09.15 @@ -24,69 +24,186 @@ public abstract class DateTimeBaseTest : BaseTest protected static readonly DateTime FirstMillisecondDateTime = FirstDateTime.AddMilliseconds(321); protected static readonly DateTime SecondMillisecondDateTime = SecondDateTime.AddMilliseconds(987); protected static readonly DateTime WrongMillisecondDateTime = WrongDateTime.AddMilliseconds(654); +#if NET6_0_OR_GREATER + + protected static readonly DateOnly FirstDateOnly = DateOnly.FromDateTime(FirstDateTime); + protected static readonly DateOnly SecondDateOnly = DateOnly.FromDateTime(SecondDateTime); + protected static readonly DateOnly NullableDateOnly = DateOnly.FromDateTime(SecondDateTime); + protected static readonly DateOnly WrongDateOnly = DateOnly.FromDateTime(WrongDateTime); + + protected static readonly TimeOnly FirstTimeOnly = TimeOnly.FromDateTime(FirstDateTime); + protected static readonly TimeOnly SecondTimeOnly = TimeOnly.FromDateTime(SecondDateTime); + protected static readonly TimeOnly NullableTimeOnly = TimeOnly.FromDateTime(SecondDateTime); + protected static readonly TimeOnly WrongTimeOnly = TimeOnly.FromDateTime(WrongDateTime); + + protected static readonly TimeOnly FirstMillisecondTimeOnly = TimeOnly.FromDateTime(FirstDateTime.AddMilliseconds(321)); + protected static readonly TimeOnly SecondMillisecondTimeOnly = TimeOnly.FromDateTime(SecondDateTime.AddMilliseconds(987)); + protected static readonly TimeOnly WrongMillisecondTimeOnly = TimeOnly.FromDateTime(WrongDateTime.AddMilliseconds(654)); +#endif protected override void RegisterTypes(DomainConfiguration configuration) { - configuration.Types.Register(typeof (SingleDateTimeEntity)); - configuration.Types.Register(typeof (DateTimeEntity)); - configuration.Types.Register(typeof (MillisecondDateTimeEntity)); - configuration.Types.Register(typeof (NullableDateTimeEntity)); + configuration.Types.Register(typeof(SingleDateTimeEntity)); + configuration.Types.Register(typeof(DateTimeEntity)); + configuration.Types.Register(typeof(MillisecondDateTimeEntity)); + configuration.Types.Register(typeof(NullableDateTimeEntity)); + configuration.Types.Register(typeof(AllPossiblePartsEntity)); +#if NET6_0_OR_GREATER + configuration.Types.Register(typeof(DateOnlyEntity)); + configuration.Types.Register(typeof(SingleDateOnlyEntity)); + configuration.Types.Register(typeof(TimeOnlyEntity)); + configuration.Types.Register(typeof(SingleTimeOnlyEntity)); +#endif } protected override void PopulateEntities(Session session) { - new SingleDateTimeEntity { + _ = new SingleDateTimeEntity(session) { DateTime = FirstDateTime, MillisecondDateTime = FirstMillisecondDateTime, NullableDateTime = NullableDateTime }; +#if NET6_0_OR_GREATER + + _ = new SingleDateOnlyEntity(session) { + DateOnly = FirstDateOnly, + NullableDateOnly = NullableDateOnly, + }; + + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly, NullableDateOnly = FirstDateOnly }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly, NullableDateOnly = FirstDateOnly }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly, NullableDateOnly = NullableDateOnly }; + _ = new DateOnlyEntity(session) { DateOnly = SecondDateOnly, NullableDateOnly = NullableDateOnly }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly, NullableDateOnly = null }; + _ = new DateOnlyEntity(session) { DateOnly = SecondDateOnly, NullableDateOnly = null }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly.AddYears(1), NullableDateOnly = FirstDateOnly.AddYears(1) }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly.AddYears(-2), NullableDateOnly = FirstDateOnly.AddYears(-2) }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly.AddMonths(44), NullableDateOnly = FirstDateOnly.AddMonths(44) }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly.AddMonths(-55), NullableDateOnly = FirstDateOnly.AddMonths(-55) }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly.AddDays(444), NullableDateOnly = FirstDateOnly.AddDays(444) }; + _ = new DateOnlyEntity(session) { DateOnly = FirstDateOnly.AddDays(-555), NullableDateOnly = FirstDateOnly.AddDays(-555) }; - new DateTimeEntity { DateTime = FirstDateTime }; - new DateTimeEntity { DateTime = FirstDateTime }; - new DateTimeEntity { DateTime = FirstDateTime.Date }; - new DateTimeEntity { DateTime = SecondDateTime }; - new DateTimeEntity { DateTime = SecondDateTime.Date }; - new DateTimeEntity { DateTime = new DateTime(FirstDateTime.Year, FirstDateTime.Month, FirstDateTime.Day, FirstDateTime.Hour, FirstDateTime.Minute, 0) }; - new DateTimeEntity { DateTime = new DateTime(FirstDateTime.Ticks, DateTimeKind.Local) }; - new DateTimeEntity { DateTime = FirstDateTime.Add(new TimeSpan(987, 23, 34, 45)) }; - new DateTimeEntity { DateTime = FirstDateTime.AddYears(1) }; - new DateTimeEntity { DateTime = FirstDateTime.AddYears(-2) }; - new DateTimeEntity { DateTime = FirstDateTime.AddMonths(44) }; - new DateTimeEntity { DateTime = FirstDateTime.AddMonths(-55) }; - new DateTimeEntity { DateTime = SecondDateTime.AddHours(5) }; - new DateTimeEntity { DateTime = SecondDateTime.AddHours(-15) }; - new DateTimeEntity { DateTime = SecondDateTime.AddMinutes(59) }; - new DateTimeEntity { DateTime = SecondDateTime.AddMinutes(-49) }; - new DateTimeEntity { DateTime = SecondDateTime.AddSeconds(57) }; - new DateTimeEntity { DateTime = SecondDateTime.AddSeconds(-5) }; + _ = new SingleTimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = NullableTimeOnly, + MillisecondTimeOnly = FirstMillisecondTimeOnly + }; + + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = FirstTimeOnly, + MillisecondTimeOnly = FirstTimeOnly + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = FirstTimeOnly, + MillisecondTimeOnly = FirstTimeOnly + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = NullableTimeOnly, + MillisecondTimeOnly = FirstMillisecondTimeOnly + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = NullableTimeOnly, + MillisecondTimeOnly = FirstMillisecondTimeOnly + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = null, + MillisecondTimeOnly = FirstMillisecondTimeOnly + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly, + NullableTimeOnly = null, + MillisecondTimeOnly = FirstMillisecondTimeOnly + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly.AddHours(5), + MillisecondTimeOnly = FirstMillisecondTimeOnly.AddHours(5), + NullableTimeOnly = NullableTimeOnly.AddHours(5) + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly.AddHours(-15), + MillisecondTimeOnly = FirstMillisecondTimeOnly.AddHours(-15), + NullableTimeOnly = NullableTimeOnly.AddHours(-15), + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly.AddMinutes(59), + MillisecondTimeOnly = FirstMillisecondTimeOnly.AddMinutes(59), + NullableTimeOnly = NullableTimeOnly.AddMinutes(59), + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly.AddMinutes(-49), + MillisecondTimeOnly = FirstMillisecondTimeOnly.AddMinutes(-49), + NullableTimeOnly = NullableTimeOnly.AddMinutes(-49), + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly.Add(new TimeSpan(0, 0, 57)), + MillisecondTimeOnly = FirstMillisecondTimeOnly.Add(new TimeSpan(0, 0, 57)), + NullableTimeOnly = NullableTimeOnly.Add(new TimeSpan(0, 0, 57)), + }; + _ = new TimeOnlyEntity(session) { + TimeOnly = FirstTimeOnly.Add(new TimeSpan(0, 0,-5)), + MillisecondTimeOnly = FirstMillisecondTimeOnly.Add(new TimeSpan(0, 0,-5)), + NullableTimeOnly = NullableTimeOnly.Add(new TimeSpan(0, 0,-5)), + }; +#endif + + _ = new DateTimeEntity(session, FirstDateTime); + _ = new DateTimeEntity(session, FirstDateTime); + _ = new DateTimeEntity(session, FirstDateTime.Date); + _ = new DateTimeEntity(session, SecondDateTime); + _ = new DateTimeEntity(session, SecondDateTime.Date); + _ = new DateTimeEntity(session, new DateTime(FirstDateTime.Year, FirstDateTime.Month, FirstDateTime.Day, FirstDateTime.Hour, FirstDateTime.Minute, 0)); + _ = new DateTimeEntity(session, new DateTime(FirstDateTime.Ticks, DateTimeKind.Local)); + _ = new DateTimeEntity(session, FirstDateTime.Add(new TimeSpan(987, 23, 34, 45))); + _ = new DateTimeEntity(session, FirstDateTime.AddYears(1)); + _ = new DateTimeEntity(session, FirstDateTime.AddYears(-2)); + _ = new DateTimeEntity(session, FirstDateTime.AddMonths(44)); + _ = new DateTimeEntity(session, FirstDateTime.AddMonths(-55)); + _ = new DateTimeEntity(session, SecondDateTime.AddHours(5)); + _ = new DateTimeEntity(session, SecondDateTime.AddHours(-15)); + _ = new DateTimeEntity(session, SecondDateTime.AddMinutes(59)); + _ = new DateTimeEntity(session, SecondDateTime.AddMinutes(-49)); + _ = new DateTimeEntity(session, SecondDateTime.AddSeconds(57)); + _ = new DateTimeEntity(session, SecondDateTime.AddSeconds(-5)); var dateTime = FirstDateTime.AddYears(10); - for (var i = 0; i < 60; ++i) - new DateTimeEntity { DateTime = dateTime.AddSeconds(i) }; - - new MillisecondDateTimeEntity { DateTime = FirstMillisecondDateTime }; - new MillisecondDateTimeEntity { DateTime = FirstMillisecondDateTime }; - new MillisecondDateTimeEntity { DateTime = FirstMillisecondDateTime.Date }; - new MillisecondDateTimeEntity { DateTime = SecondMillisecondDateTime }; - new MillisecondDateTimeEntity { DateTime = SecondMillisecondDateTime.Date }; - new MillisecondDateTimeEntity { DateTime = new DateTime(FirstMillisecondDateTime.Year, FirstMillisecondDateTime.Month, FirstMillisecondDateTime.Day, FirstMillisecondDateTime.Hour, FirstMillisecondDateTime.Minute, 0) }; - new MillisecondDateTimeEntity { DateTime = new DateTime(FirstMillisecondDateTime.Year, FirstMillisecondDateTime.Month, FirstMillisecondDateTime.Day, FirstMillisecondDateTime.Hour, FirstMillisecondDateTime.Minute, FirstMillisecondDateTime.Second, 0) }; - new MillisecondDateTimeEntity { DateTime = new DateTime(FirstMillisecondDateTime.Ticks, DateTimeKind.Local) }; - new MillisecondDateTimeEntity { DateTime = FirstMillisecondDateTime.Add(new TimeSpan(987, 23, 34, 45)) }; + for (var i = 0; i < 60; ++i) { + _ = new DateTimeEntity(session, dateTime.AddSeconds(i)); + } + + _ = new MillisecondDateTimeEntity(session) { DateTime = FirstMillisecondDateTime }; + _ = new MillisecondDateTimeEntity(session) { DateTime = FirstMillisecondDateTime }; + _ = new MillisecondDateTimeEntity(session) { DateTime = FirstMillisecondDateTime.Date }; + _ = new MillisecondDateTimeEntity(session) { DateTime = SecondMillisecondDateTime }; + _ = new MillisecondDateTimeEntity(session) { DateTime = SecondMillisecondDateTime.Date }; + _ = new MillisecondDateTimeEntity(session) { DateTime = new DateTime(FirstMillisecondDateTime.Year, FirstMillisecondDateTime.Month, FirstMillisecondDateTime.Day, FirstMillisecondDateTime.Hour, FirstMillisecondDateTime.Minute, 0) }; + _ = new MillisecondDateTimeEntity(session) { DateTime = new DateTime(FirstMillisecondDateTime.Year, FirstMillisecondDateTime.Month, FirstMillisecondDateTime.Day, FirstMillisecondDateTime.Hour, FirstMillisecondDateTime.Minute, FirstMillisecondDateTime.Second, 0) }; + _ = new MillisecondDateTimeEntity(session) { DateTime = new DateTime(FirstMillisecondDateTime.Ticks, DateTimeKind.Local) }; + _ = new MillisecondDateTimeEntity(session) { DateTime = FirstMillisecondDateTime.Add(new TimeSpan(987, 23, 34, 45)) }; var index = 0; - foreach (var dateTimeEntity1 in Query.All()) - new MillisecondDateTimeEntity(dateTimeEntity1, ++index % 3==0 ? FirstMillisecondDateTime.Millisecond : SecondMillisecondDateTime.Millisecond); + foreach (var dateTimeEntity1 in session.Query.All()) { + var dtValue = dateTimeEntity1.DateTime.AddMilliseconds(++index % 3 == 0 ? FirstMillisecondDateTime.Millisecond : SecondMillisecondDateTime.Millisecond); + _ = new MillisecondDateTimeEntity(session, dtValue); + } dateTime = FirstMillisecondDateTime.AddYears(10); - for (var i = 0; i < 1000; ++i) - new MillisecondDateTimeEntity { DateTime = dateTime.AddMilliseconds(i) }; + for (var i = 0; i < 1000; ++i) { + _ = new MillisecondDateTimeEntity(session) { DateTime = dateTime.AddMilliseconds(i) }; + } + + foreach (var dateTimeEntity in session.Query.All()) { + _ = new NullableDateTimeEntity(session) { DateTime = dateTimeEntity.DateTime }; + } - foreach (var dateTimeEntity in Query.All()) - new NullableDateTimeEntity(dateTimeEntity); + _ = new NullableDateTimeEntity(session) { DateTime = null }; + _ = new NullableDateTimeEntity(session) { DateTime = null }; - new NullableDateTimeEntity { DateTime = null }; - new NullableDateTimeEntity { DateTime = null }; + _ = AllPossiblePartsEntity.FromDateTime(session, FirstMillisecondDateTime, 321); } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs index e183016ded..40cea806fd 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs @@ -1,10 +1,12 @@ -// Copyright (C) 2016 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2016-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: Alex Groznov // Created: 2016.08.01 using System; +using System.Net.Sockets; +using Org.BouncyCastle.Crypto.Digests; namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model { @@ -22,6 +24,11 @@ public class SingleDateTimeEntity : Entity [Field] public DateTime? NullableDateTime { get; set; } + + public SingleDateTimeEntity(Session session) + : base(session) + { + } } [HierarchyRoot] @@ -48,6 +55,12 @@ public class DateTimeEntity : Entity [Field] public DateTime DateTime { get; set; } + + public DateTimeEntity(Session session, DateTime dateTime) + : base(session) + { + DateTime = dateTime; + } } [HierarchyRoot] @@ -59,13 +72,13 @@ public class MillisecondDateTimeEntity : Entity [Field] public DateTime DateTime { get; set; } - public MillisecondDateTimeEntity() + public MillisecondDateTimeEntity(Session session) { } - public MillisecondDateTimeEntity(DateTimeEntity dateTimeEntity, int milliseconds) + public MillisecondDateTimeEntity(Session session, DateTime dateTime) { - DateTime = dateTimeEntity.DateTime.AddMilliseconds(milliseconds); + DateTime = dateTime; } } @@ -78,14 +91,10 @@ public class NullableDateTimeEntity : Entity [Field] public DateTime? DateTime { get; set; } - public NullableDateTimeEntity() + public NullableDateTimeEntity(Session session) + : base(session) { } - - public NullableDateTimeEntity(DateTimeEntity dateTimeEntity) - { - DateTime = dateTimeEntity.DateTime; - } } [HierarchyRoot] @@ -144,4 +153,152 @@ public NullableDateTimeOffsetEntity(DateTimeOffsetEntity dateTimeOffsetEntity) DateTimeOffset = dateTimeOffsetEntity.DateTimeOffset; } } + + + [HierarchyRoot] + public class AllPossiblePartsEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 3000)] + public int Year { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 1, Max = 12)] + public int Month { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 1, Max = 31)] + public int Day { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 23)] + public int Hour { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 59)] + public int Minute { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 59)] + public int Second { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 999)] + public int Millisecond { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 999)] + public int Microsecond { get; set; } + + [Field] + public long Ticks { get; set; } + + [Field] + [Validation.RangeConstraint(Min = -23, Max = 23)] + public int OffsetHour { get; set; } + + [Field] + [Validation.RangeConstraint(Min = 0, Max = 59)] + public int OffsetMinute { get; set; } + + public static AllPossiblePartsEntity FromDateTime(Session session, DateTime dateTime, int microsecond) + { + return new AllPossiblePartsEntity(session) { + Year = dateTime.Year, + Month = dateTime.Month, + Day = dateTime.Day, + Hour = dateTime.Hour, + Minute = dateTime.Minute, + Second = dateTime.Second, + Millisecond = dateTime.Millisecond, + Microsecond = microsecond, + OffsetHour = 0, + OffsetMinute = 0, + Ticks = dateTime.Ticks + }; + } + + public static AllPossiblePartsEntity FromDateTimeOffset(Session session, DateTimeOffset dateTimeOffset, int microsecond) + { + return new AllPossiblePartsEntity(session) { + Year = dateTimeOffset.Year, + Month = dateTimeOffset.Month, + Day = dateTimeOffset.Day, + Hour = dateTimeOffset.Hour, + Minute = dateTimeOffset.Minute, + Second = dateTimeOffset.Second, + Millisecond = dateTimeOffset.Millisecond, + Microsecond = microsecond, + OffsetHour = dateTimeOffset.Offset.Hours, + OffsetMinute = dateTimeOffset.Offset.Minutes, + Ticks = dateTimeOffset.Ticks + }; + } + + private AllPossiblePartsEntity(Session session) + : base(session) + { + } + } +#if NET6_0_OR_GREATER + + [HierarchyRoot] + public class DateOnlyEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public DateOnly DateOnly { get; set; } + + [Field] + public DateOnly? NullableDateOnly { get; set; } + + public DateOnlyEntity(Session session) + : base(session) + { + } + } + + public class SingleDateOnlyEntity : DateOnlyEntity + { + public SingleDateOnlyEntity(Session session) + : base(session) + { + + } + } + + [HierarchyRoot] + public class TimeOnlyEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public TimeOnly TimeOnly { get; set; } + + [Field] + public TimeOnly MillisecondTimeOnly { get; set; } + + [Field] + public TimeOnly? NullableTimeOnly { get; set; } + + public TimeOnlyEntity(Session session) + :base(session) + { + } + } + + public class SingleTimeOnlyEntity : TimeOnlyEntity + { + public SingleTimeOnlyEntity(Session session) + : base(session) + { + } + } +#endif } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ComparisonTestTimeOnly.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ComparisonTestTimeOnly.cs new file mode 100644 index 0000000000..1b10a3a4d7 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ComparisonTestTimeOnly.cs @@ -0,0 +1,76 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class ComparisonTestTimeOnly : DateTimeBaseTest + { + [Test] + public void EqualsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly == FirstTimeOnly); + RunTest(s, c => c.MillisecondTimeOnly == FirstMillisecondTimeOnly); + RunTest(s, c => c.NullableTimeOnly == NullableTimeOnly); + + RunWrongTest(s, c => c.TimeOnly == WrongTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly == WrongMillisecondTimeOnly); + RunWrongTest(s, c => c.NullableTimeOnly == WrongTimeOnly); + RunWrongTest(s, c => c.NullableTimeOnly == null); + }); + } + + [Test] + public void NotEqualTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly != FirstTimeOnly.AddHours(1)); + RunTest(s, c => c.MillisecondTimeOnly != FirstMillisecondTimeOnly.AddHours(1)); + RunTest(s, c => c.NullableTimeOnly != NullableTimeOnly.AddHours(1)); + }); + } + + [Test] + public void CompareTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly > FirstTimeOnly.AddHours(-1)); + RunTest(s, c => c.MillisecondTimeOnly > FirstMillisecondTimeOnly.Add(new TimeSpan(0,0,0,0, -1))); + + RunTest(s, c => c.TimeOnly < FirstTimeOnly.AddHours(1)); + RunTest(s, c => c.MillisecondTimeOnly < FirstMillisecondTimeOnly.Add(new TimeSpan(0, 0, 0, 0, 1))); + + RunWrongTest(s, c => c.DateTime > FirstDateTime); + RunWrongTest(s, c => c.MillisecondDateTime > FirstMillisecondDateTime); + RunWrongTest(s, c => c.MillisecondDateTime < FirstMillisecondDateTime.Date); + + RunWrongTest(s, c => c.TimeOnly > FirstTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly > FirstMillisecondTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly < FirstMillisecondTimeOnly.AddMinutes(-3)); + }); + } + + [Test] + public void CompareMysqTest() + { + Require.ProviderIs(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly > FirstTimeOnly.AddMinutes(-1)); + RunTest(s, c => c.TimeOnly < FirstTimeOnly.AddMinutes(1)); + + RunWrongTest(s, c => c.TimeOnly > FirstTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly > FirstMillisecondTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly < FirstMillisecondTimeOnly.AddMinutes(-3)); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs new file mode 100644 index 0000000000..3c9f91931c --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs @@ -0,0 +1,57 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class ConstructorTest : DateTimeBaseTest + { + [Test] + public void CtorHMSM() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .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); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void CtorHMS() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { + Entity = e, + ConstructedTime = new TimeOnly(e.Hour, e.Minute, e.Second) + }) + .Where(a => a.ConstructedTime == FirstTimeOnly).OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void CtorHM() + { + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { Entity = e, ConstructedTime = new TimeOnly(e.Hour, e.Minute) }) + .Where(a => a.ConstructedTime == FirstTimeOnly.Add(TimeSpan.FromSeconds(-5))) + .OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/DistinctTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/DistinctTest.cs new file mode 100644 index 0000000000..5e93720af5 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/DistinctTest.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class DistinctTest : DateTimeBaseTest + { + [Test] + public void DistinctByTimeOnlyTest() => + ExecuteInsideSession((s) => DistinctPrivate(s, c => c.TimeOnly)); + + [Test] + public void DistinctByTimeOnlyWithMillisecondsTest() => + ExecuteInsideSession((s) => DistinctPrivate(s, c => c.MillisecondTimeOnly)); + + [Test] + public void DistinctByNullableTimeOnlyTest() => + ExecuteInsideSession((s) => DistinctPrivate(s, c => c.TimeOnly)); + + private static void DistinctPrivate(Session session, Expression> selectExpression) + where T : Entity + { + var compiledSelectExpression = selectExpression.Compile(); + var distinctLocal = session.Query.All().ToArray().Select(compiledSelectExpression).Distinct().OrderBy(c => c); + var distinctByServer = session.Query.All().Select(selectExpression).Distinct().OrderBy(c => c); + Assert.IsTrue(distinctLocal.SequenceEqual(distinctByServer)); + + distinctByServer = session.Query.All().Select(selectExpression).Distinct().OrderByDescending(c => c); + Assert.IsFalse(distinctLocal.SequenceEqual(distinctByServer)); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/GroupByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/GroupByTest.cs new file mode 100644 index 0000000000..95a39c580d --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/GroupByTest.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class GroupByTest : DateTimeBaseTest + { + [Test] + public void TimeOnlyGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.TimeOnly, c => c.Id)); + + [Test] + public void MillisecondTimeOnlyGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.MillisecondTimeOnly, c => c.Id)); + + [Test] + public void NullableTimeOnlyGroupByTest() => + ExecuteInsideSession((s) => GroupByPrivate(s, c => c.NullableTimeOnly, c => c.Id)); + + private static void GroupByPrivate(Session session, + Expression> groupByExpression, + Expression> orderByExpression) + where T : Entity + { + var compiledGroupByExpression = groupByExpression.Compile(); + var compiledOrderByExpression = orderByExpression.Compile(); + var groupByLocal = session.Query.All().ToArray().GroupBy(compiledGroupByExpression).ToArray(); + var groupByServer = session.Query.All().GroupBy(groupByExpression); + foreach (var group in groupByServer) { + Assert.Contains(group, groupByLocal); + var localGroup = groupByLocal.Single(c => c.Key.Equals(group.Key)); + Assert.IsTrue(group.OrderBy(compiledOrderByExpression).SequenceEqual(localGroup.OrderBy(compiledOrderByExpression))); + } + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/JoinTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/JoinTest.cs new file mode 100644 index 0000000000..56b718d852 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/JoinTest.cs @@ -0,0 +1,81 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class JoinTest : DateTimeBaseTest + { + [Test] + public void TimeOnlyJoinTest() + { + ExecuteInsideSession((s) => JoinPrivate, TimeOnly, long>(s, + left => left.TimeOnly, + right => right.TimeOnly, + (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.TimeOnly, RightDateTime = right.TimeOnly }, + c => c.LeftId, + c => c.RightId)); + } + + [Test] + public void MillisecondTimeOnlyJoinTest() + { + ExecuteInsideSession((s) => JoinPrivate, TimeOnly, long>(s, + left => left.MillisecondTimeOnly, + right => right.MillisecondTimeOnly, + (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.MillisecondTimeOnly, RightDateTime = right.MillisecondTimeOnly }, + c => c.LeftId, + c => c.RightId)); + } + + [Test] + public void NullableTimeOnlyJoinTest() + { + ExecuteInsideSession((s) => JoinPrivate, TimeOnly?, long>(s, + left => left.NullableTimeOnly, + right => right.NullableTimeOnly, + (left, right) => new JoinResult { LeftId = left.Id, RightId = right.Id, LeftDateTime = left.NullableTimeOnly, RightDateTime = right.NullableTimeOnly }, + c => c.LeftId, + c => c.RightId)); + } + + private static void JoinPrivate(Session session, + Expression> leftJoinExpression, Expression> rightJoinExpression, + Expression> joinResultExpression, Expression> orderByExpression, Expression> thenByExpression) + where T1 : Entity + where T2 : Entity + { + var compiledLeftJoinExpression = leftJoinExpression.Compile(); + var compiledRightJoinExpression = rightJoinExpression.Compile(); + var compiledJoinResultExpression = joinResultExpression.Compile(); + var compiledOrderByExpression = orderByExpression.Compile(); + var compiledThenByExpression = thenByExpression.Compile(); + var joinLocal = session.Query.All().ToArray() + .Join(session.Query.All().ToArray(), compiledLeftJoinExpression, compiledRightJoinExpression, compiledJoinResultExpression) + .OrderBy(compiledOrderByExpression) + .ThenBy(compiledThenByExpression); + + var joinServer = session.Query.All() + .Join(session.Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) + .OrderBy(orderByExpression) + .ThenBy(thenByExpression); + + Assert.IsTrue(joinLocal.SequenceEqual(joinServer)); + + joinServer = session.Query.All() + .Join(session.Query.All(), leftJoinExpression, rightJoinExpression, joinResultExpression) + .OrderByDescending(orderByExpression) + .ThenBy(thenByExpression); + Assert.IsFalse(joinLocal.SequenceEqual(joinServer)); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/MinMaxTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/MinMaxTest.cs new file mode 100644 index 0000000000..d57c7cfe9b --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/MinMaxTest.cs @@ -0,0 +1,51 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class MinMaxTest : DateTimeBaseTest + { + [Test] + public void TimeOnlyMinMaxTest() + { + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.TimeOnly)); + } + + [Test] + public void MillisecondTimeOnlyMinMaxTest() + { + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.MillisecondTimeOnly)); + } + + [Test] + public void NullableTimeOnlyMinMaxTest() + { + ExecuteInsideSession((s) => MinMaxPrivate(s, c => c.NullableTimeOnly)); + } + + private static void MinMaxPrivate(Session session, Expression> selectExpression) + where T : Entity + { + var compiledSelectExpression = selectExpression.Compile(); + var minLocal = session.Query.All().ToArray().Min(compiledSelectExpression); + var maxLocal = session.Query.All().ToArray().Max(compiledSelectExpression); + var minServer = session.Query.All().Min(selectExpression); + var maxServer = session.Query.All().Max(selectExpression); + + Assert.AreEqual(minLocal, minServer); + Assert.AreEqual(maxLocal, maxServer); + Assert.AreNotEqual(minLocal, maxServer); + Assert.AreNotEqual(maxLocal, minServer); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/OperationsTest.cs new file mode 100644 index 0000000000..b2d503d660 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/OperationsTest.cs @@ -0,0 +1,111 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class OperationsTest : DateTimeBaseTest + { + [Test] + public void AddHoursTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly.AddHours(1) == FirstTimeOnly.AddHours(1)); + RunTest(s, c => c.NullableTimeOnly.Value.AddHours(33) == NullableTimeOnly.AddHours(33)); + + RunWrongTest(s, c => c.TimeOnly.AddHours(1) == FirstTimeOnly.AddHours(2)); + RunWrongTest(s, c => c.NullableTimeOnly.Value.AddHours(33) == NullableTimeOnly.AddHours(44)); + + if(StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.MySql) + || StorageProviderInfo.Instance.CheckProviderVersionIsAtLeast(new Version(5, 6))) { + RunTest(s, c => c.MillisecondTimeOnly.AddHours(-2) == FirstMillisecondTimeOnly.AddHours(-2)); + RunWrongTest(s, c => c.MillisecondTimeOnly.AddHours(-1) == FirstMillisecondTimeOnly.AddHours(-2)); + } + }); + } + + [Test] + public void AddMinutesTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly.AddMinutes(1) == FirstTimeOnly.AddMinutes(1)); + RunTest(s, c => c.NullableTimeOnly.Value.AddMinutes(33) == NullableTimeOnly.AddMinutes(33)); + + RunWrongTest(s, c => c.TimeOnly.AddMinutes(1) == FirstTimeOnly.AddMinutes(2)); + RunWrongTest(s, c => c.NullableTimeOnly.Value.AddMinutes(33) == NullableTimeOnly.AddMinutes(44)); + + if (StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.MySql) + || StorageProviderInfo.Instance.CheckProviderVersionIsAtLeast(new Version(5, 6))) { + RunTest(s, c => c.MillisecondTimeOnly.AddMinutes(-2) == FirstMillisecondTimeOnly.AddMinutes(-2)); + RunWrongTest(s, c => c.MillisecondTimeOnly.AddMinutes(-1) == FirstMillisecondTimeOnly.AddMinutes(-2)); + } + }); + } + + [Test] + public void AddTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MillisecondTimeOnly.Add(SecondOffset) == FirstMillisecondTimeOnly.Add(SecondOffset)); + RunTest(s, c => c.TimeOnly.Add(FirstOffset) == FirstTimeOnly.Add(FirstOffset)); + RunTest(s, c => c.NullableTimeOnly.Value.Add(FirstOffset) == NullableTimeOnly.Add(FirstOffset)); + + RunWrongTest(s, c => c.TimeOnly.Add(FirstOffset) == FirstTimeOnly.Add(WrongOffset)); + RunWrongTest(s, c => c.NullableTimeOnly.Value.Add(FirstOffset) == NullableTimeOnly.Add(WrongOffset)); + + if (StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.MySql) + || StorageProviderInfo.Instance.CheckProviderVersionIsAtLeast(new Version(5, 6))) { + RunTest(s, c => c.MillisecondTimeOnly.Add(SecondOffset) == FirstMillisecondTimeOnly.Add(SecondOffset)); + RunWrongTest(s, c => c.MillisecondTimeOnly.Add(SecondOffset) == FirstMillisecondTimeOnly.Add(WrongOffset)); + } + }); + } + + [Test] + public void MinusTimeOnlyTest() + { + var inteval = FirstTimeOnly - SecondTimeOnly; + + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly - SecondTimeOnly == FirstTimeOnly - SecondTimeOnly); + RunTest(s, c => c.NullableTimeOnly - SecondTimeOnly == NullableTimeOnly - SecondTimeOnly); + + RunWrongTest(s, c => c.TimeOnly - SecondTimeOnly == FirstTimeOnly - WrongTimeOnly); + RunWrongTest(s, c => c.NullableTimeOnly - SecondTimeOnly == NullableTimeOnly - WrongTimeOnly); + + if (StorageProviderInfo.Instance.CheckProviderIsNot(StorageProvider.MySql) + || StorageProviderInfo.Instance.CheckProviderVersionIsAtLeast(new Version(5, 6))) { + RunTest(s, c => c.MillisecondTimeOnly - SecondTimeOnly == FirstMillisecondTimeOnly - SecondTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly - SecondTimeOnly == FirstMillisecondTimeOnly - WrongTimeOnly); + } + }); + } + + [Test] + public void MysqlMinisTimeOnlyTest() + { + Require.ProviderIs(StorageProvider.MySql); + ExecuteInsideSession((s) => { + var firstTimeOnly = FirstTimeOnly.AdjustTimeOnlyForCurrentProvider(); + var firstMillisecondTimeOnly = FirstMillisecondTimeOnly.AdjustTimeOnlyForCurrentProvider(); + var secondTimeOnly = SecondTimeOnly.AdjustTimeOnlyForCurrentProvider(); + var nullableTimeOnly = NullableTimeOnly.AdjustTimeOnlyForCurrentProvider(); + + RunTest(s, c => c.TimeOnly - secondTimeOnly == firstTimeOnly - secondTimeOnly); + RunTest(s, c => c.MillisecondTimeOnly - secondTimeOnly == firstMillisecondTimeOnly - secondTimeOnly); + RunTest(s, c => c.NullableTimeOnly - secondTimeOnly == NullableTimeOnly - secondTimeOnly); + + RunWrongTest(s, c => c.TimeOnly - secondTimeOnly == secondTimeOnly - WrongTimeOnly); + RunWrongTest(s, c => c.MillisecondTimeOnly - secondTimeOnly == firstMillisecondTimeOnly - WrongTimeOnly); + RunWrongTest(s, c => c.NullableTimeOnly - secondTimeOnly == nullableTimeOnly - WrongTimeOnly); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/OrderByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/OrderByTest.cs new file mode 100644 index 0000000000..0cc6f27a47 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/OrderByTest.cs @@ -0,0 +1,83 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class OrderByTest : DateTimeBaseTest + { + [Test] + public void TimeOnlyOrderByTest() + { + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.TimeOnly, c => c.Id); + OrderByPrivate(s, c => c.TimeOnly, c => c); + }); + } + + [Test] + public void MillisecondTimeOnlyOrderByTest() + { + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.MillisecondTimeOnly, c => c.Id); + OrderByPrivate(s, c => c.MillisecondTimeOnly, c => c); + }); + } + + [Test] + public void NullableTimeOnlyOrderByTest() + { + ExecuteInsideSession((s) => { + OrderByPrivate(s, c => c.NullableTimeOnly, c => c.Id); + OrderByPrivate(s, c => c.NullableTimeOnly, c => c); + }); + } + + private static void OrderByPrivate(Session session, Expression> orderByExpression, Expression> thenByExpression) + where T : Entity + { + var compiledOrderByExpression = orderByExpression.Compile(); + var compiledThenByExpression = thenByExpression.Compile(); + var notOrderedLocal = session.Query.All().ToArray(); + var orderedLocal = notOrderedLocal.OrderBy(compiledOrderByExpression).ThenBy(compiledThenByExpression); + var orderedLocalDescending = notOrderedLocal.OrderByDescending(compiledOrderByExpression).ThenBy(compiledThenByExpression); + var orderedByServer = session.Query.All().OrderBy(orderByExpression).ThenBy(thenByExpression); + var orderedByServerDescending = session.Query.All().OrderByDescending(orderByExpression).ThenBy(thenByExpression); + + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedLocal)); + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocalDescending.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocal.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocalDescending.SequenceEqual(orderedByServer)); + } + + protected static void OrderByPrivate(Session session, Expression> selectorExpression, Expression> orderByExpression) + where T1 : Entity + { + var compiledOrderByExpression = orderByExpression.Compile(); + + var notOrderedLocal = session.Query.All().Select(selectorExpression).ToArray(); + var orderedLocal = notOrderedLocal.OrderBy(compiledOrderByExpression); + var orderedLocalDescending = notOrderedLocal.OrderByDescending(compiledOrderByExpression); + var orderedByServer = session.Query.All().Select(selectorExpression).OrderBy(orderByExpression); + var orderedByServerDescending = session.Query.All().Select(selectorExpression).OrderByDescending(orderByExpression); + + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedLocal)); + Assert.IsFalse(notOrderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocal.SequenceEqual(orderedByServer)); + Assert.IsTrue(orderedLocalDescending.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocal.SequenceEqual(orderedByServerDescending)); + Assert.IsFalse(orderedLocalDescending.SequenceEqual(orderedByServer)); + } + } +} +#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 new file mode 100644 index 0000000000..082bc272d5 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/PartsExtractionTest.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class PartsExtractionTest : DateTimeBaseTest + { + [Test] + public void ExtractHourTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly.Hour == FirstTimeOnly.Hour); + RunTest(s, c => c.MillisecondTimeOnly.Hour == FirstMillisecondTimeOnly.Hour); + RunTest(s, c => c.NullableTimeOnly.Value.Hour == NullableTimeOnly.Hour); + + RunWrongTest(s, c => c.TimeOnly.Hour == WrongTimeOnly.Hour); + RunWrongTest(s, c => c.MillisecondTimeOnly.Hour == WrongMillisecondTimeOnly.Hour); + RunWrongTest(s, c => c.NullableTimeOnly.Value.Hour == WrongTimeOnly.Hour); + }); + } + + [Test] + public void ExtractMinuteTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly.Minute == FirstTimeOnly.Minute); + RunTest(s, c => c.MillisecondTimeOnly.Minute == FirstMillisecondTimeOnly.Minute); + RunTest(s, c => c.NullableTimeOnly.Value.Minute == NullableTimeOnly.Minute); + + RunWrongTest(s, c => c.TimeOnly.Minute == WrongTimeOnly.Minute); + RunWrongTest(s, c => c.MillisecondTimeOnly.Minute == WrongMillisecondTimeOnly.Minute); + RunWrongTest(s, c => c.NullableTimeOnly.Value.Minute == WrongTimeOnly.Minute); + }); + } + + [Test] + public void ExtractSecondTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly.Second == FirstTimeOnly.Second); + RunTest(s, c => c.MillisecondTimeOnly.Second == FirstMillisecondTimeOnly.Second); + RunTest(s, c => c.NullableTimeOnly.Value.Second == NullableTimeOnly.Second); + + RunWrongTest(s, c => c.TimeOnly.Second == WrongTimeOnly.Second); + RunWrongTest(s, c => c.MillisecondTimeOnly.Second == WrongMillisecondTimeOnly.Second); + RunWrongTest(s, c => c.NullableTimeOnly.Value.Second == WrongTimeOnly.Second); + }); + } + + [Test] + public void ExtractMillisecondTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MillisecondTimeOnly.Millisecond == FirstMillisecondTimeOnly.Millisecond); + RunWrongTest(s, c => c.MillisecondTimeOnly.Second == WrongMillisecondTimeOnly.Millisecond); + }); + } + + [Test] + public void MysqlExtractMillisecondTest() + { + Require.ProviderIs(StorageProvider.MySql); + Require.ProviderVersionAtLeast(new Version(5, 6));// no support for fractions below 5.6 + ExecuteInsideSession((s) => { + var firstMillisecondTimeOnly = FirstMillisecondTimeOnly.AdjustTimeOnlyForCurrentProvider(); + RunTest(s, c => c.MillisecondTimeOnly.Millisecond == firstMillisecondTimeOnly.Millisecond); + RunWrongTest(s, c => c.MillisecondTimeOnly.Second == WrongMillisecondTimeOnly.Millisecond); + }); + } + + [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); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/TimeOnlyToString.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/TimeOnlyToString.cs new file mode 100644 index 0000000000..b204eeabcb --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/TimeOnlyToString.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class TimeOnlyToStringTest : DateTimeBaseTest + { + [Test] + public void ToStringTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.TimeOnly.ToString("o") == FirstTimeOnly.ToString("o")); + RunWrongTest(s, c => c.TimeOnly.ToString("o") == FirstTimeOnly.AddHours(1).ToString("o")); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/WhereTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/WhereTest.cs new file mode 100644 index 0000000000..203811c1b2 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/WhereTest.cs @@ -0,0 +1,64 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; + +namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys +{ + public class WhereTest : DateTimeBaseTest + { + [Test] + public void DateTimeWhereTest() + { + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.TimeOnly == FirstTimeOnly, c => c.Id); + WherePrivate(s, c => c.TimeOnly.Hour == FirstTimeOnly.Hour, c => c.Id); + WherePrivate(s, c => c.TimeOnly.Second == FirstTimeOnly.Second, c => c.Id); + }); + } + + [Test] + public void MillisecondDateTimeWhereTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.MillisecondTimeOnly == FirstMillisecondTimeOnly, c => c.Id); + WherePrivate(s, c => c.MillisecondTimeOnly.Hour == FirstMillisecondTimeOnly.Hour, c => c.Id); + WherePrivate(s, c => c.MillisecondTimeOnly.Millisecond == FirstMillisecondTimeOnly.Millisecond, c => c.Id); + }); + } + + [Test] + public void NullableDateTimeWhereTest() + { + ExecuteInsideSession((s) => { + WherePrivate(s, c => c.NullableTimeOnly == NullableTimeOnly, c => c.Id); + WherePrivate(s, c => c.NullableTimeOnly == null, c => c.Id); + WherePrivate(s, c => c.NullableTimeOnly.HasValue && c.NullableTimeOnly.Value.Hour == NullableTimeOnly.Hour, c => c.Id); + WherePrivate(s, c => c.NullableTimeOnly.HasValue && c.NullableTimeOnly.Value.Second == NullableTimeOnly.Second, c => c.Id); + }); + } + + private static void WherePrivate(Session session, Expression> whereExpression, Expression> orderByExpression) + where T : Entity + { + var compiledWhereExpression = whereExpression.Compile(); + var compiledOrderByExpression = orderByExpression.Compile(); + + var whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression); + var whereByServer = session.Query.All().Where(whereExpression).OrderBy(orderByExpression); + Assert.IsTrue(whereLocal.SequenceEqual(whereByServer)); + + whereByServer = session.Query.All().Where(whereExpression).OrderByDescending(orderByExpression); + Assert.IsFalse(whereLocal.SequenceEqual(whereByServer)); + } + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Storage/DateTimeStoragePrecisionTest.cs b/Orm/Xtensive.Orm.Tests/Storage/DateTimeStoragePrecisionTest.cs new file mode 100644 index 0000000000..196de31bca --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Storage/DateTimeStoragePrecisionTest.cs @@ -0,0 +1,105 @@ +// Copyright (C) 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.Linq; +using NUnit.Framework; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Tests.Storage.DateTimeStoragePrecisionTestModel; + +namespace Xtensive.Orm.Tests.Storage.DateTimeStoragePrecisionTestModel +{ + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class TestEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public DateTime FDateTime { get; set; } +#if NET6_0_OR_GREATER + + [Field] + public TimeOnly FTimeOnly { get; set; } +#endif + + public TestEntity(Session session, long idValue) + : base(session, idValue) + { + var dateTime = new DateTime(idValue, DateTimeKind.Utc); + FDateTime = dateTime; +#if NET6_0_OR_GREATER + FTimeOnly = TimeOnly.FromDateTime(dateTime); +#endif + } + } +} + +namespace Xtensive.Orm.Tests.Storage +{ + // Each storage is capable of storing certain amount of fractions of second. + // This test writes a value with 7 fractional points to storage and, + // depeding on storage, expects value of precision the storage have. + public class DateTimeStoragePrecisionTest : AutoBuildTest + { + private const long TestDateTimeTicks = 638130658792224229L; //2023-02-27 03:37:59.2224229 UTC + + protected override DomainConfiguration BuildConfiguration() + { + var configuration = DomainConfigurationFactory.Create(); + configuration.UpgradeMode = DomainUpgradeMode.Recreate; + configuration.Types.Register(typeof(TestEntity)); + return configuration; + } + + protected override void PopulateData() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + _ = new TestEntity(session, TestDateTimeTicks); + tx.Complete(); + } + } + + [Test] + public void DateTimeTest() + { + var dateTime = new DateTime(TestDateTimeTicks, DateTimeKind.Utc); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var entity = session.Query.All().First(e => e.Id == TestDateTimeTicks); + Assert.That(entity.FDateTime, Is.EqualTo(GetExpectedValue(dateTime))); + } + } +#if NET6_0_OR_GREATER + + [Test] + public void TimeOnlyTest() + { + var dateTime = new DateTime(TestDateTimeTicks, DateTimeKind.Utc); + var timeOnly = TimeOnly.FromDateTime(dateTime); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var entity = session.Query.All().First(e => e.Id == TestDateTimeTicks); + Assert.That(entity.FTimeOnly, Is.EqualTo(GetExpectedValue(timeOnly))); + } + } + + private static TimeOnly GetExpectedValue(in TimeOnly baseTimeOnly) => baseTimeOnly.AdjustTimeOnlyForCurrentProvider(); +#endif + + private static DateTime GetExpectedValue(in DateTime baseDateTime) + { + var provider = StorageProviderInfo.Instance.Provider; + return provider == StorageProvider.MySql + ? baseDateTime.AdjustDateTime(0, true) + : provider == StorageProvider.Oracle + ? baseDateTime.AdjustDateTime(6, true) + : baseDateTime.AdjustDateTimeForCurrentProvider(); + } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Storage/FieldDefaultValueTest.cs b/Orm/Xtensive.Orm.Tests/Storage/FieldDefaultValueTest.cs index 233621bde6..e285c199b6 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/FieldDefaultValueTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/FieldDefaultValueTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2008-2021 Xtensive LLC. +// Copyright (C) 2008-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: Dmitri Maximov @@ -145,6 +145,14 @@ public class X : Entity [Field(DefaultValue = "2012.12.12")] public DateTime FDateTime { get; set; } +#if NET6_0_OR_GREATER + + [Field(DefaultValue = "2012.12.12")] + public DateOnly FDateOnly { get; set; } + + [Field(DefaultValue = "00:35:53.35")] + public TimeOnly FTimeOnly { get; set; } +#endif [Field(DefaultValue = 1000)] public TimeSpan FTimeSpan { get; set; } @@ -223,6 +231,14 @@ public class X : Entity [Field(DefaultValue = "2012.12.12")] public DateTime? FNDateTime { get; set; } +#if NET6_0_OR_GREATER + [Field(DefaultValue = "2012.12.12")] + public DateOnly? FNDateOnly { get; set; } + + [Field(DefaultValue = "00:35:53.35")] + public TimeOnly? FNTimeOnly { get; set; } + +#endif [Field(DefaultValue = 1000)] public TimeSpan? FNTimeSpan { get; set; } @@ -331,6 +347,12 @@ public void DefaultValuesTest() Assert.AreEqual(true, x.FBool); Assert.AreEqual(byte.MaxValue, x.FByte); Assert.AreEqual(DateTime.Parse("2012.12.12"), x.FDateTime); + +#if NET6_0_OR_GREATER + Assert.AreEqual(DateOnly.Parse("2012.12.12"), x.FDateOnly); + Assert.AreEqual(TimeOnly.Parse("00:35:53.35"), x.FTimeOnly); + +#endif Assert.AreEqual(12.12M, x.FDecimal); Assert.AreEqual(float.MaxValue, x.FDouble); Assert.AreEqual(EByte.Max, x.FEByte); @@ -357,6 +379,10 @@ public void DefaultValuesTest() Assert.AreEqual(true, x.FNBool); Assert.AreEqual(byte.MaxValue, x.FNByte); Assert.AreEqual(DateTime.Parse("2012.12.12"), x.FNDateTime); +#if NET6_0_OR_GREATER //DO_DATEONLY + Assert.AreEqual(DateOnly.Parse("2012.12.12"), x.FNDateOnly); + Assert.AreEqual(TimeOnly.Parse("00:35:53.35"), x.FNTimeOnly); +#endif Assert.AreEqual(12.12M, x.FNDecimal); Assert.AreEqual(float.MaxValue, x.FNDouble); Assert.AreEqual(EByte.Max, x.FNEByte); diff --git a/Orm/Xtensive.Orm.Tests/Storage/LegacyDb/CrazyColumns2008Test.cs b/Orm/Xtensive.Orm.Tests/Storage/LegacyDb/CrazyColumns2008Test.cs index 5ac445c963..d2e976d9f4 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/LegacyDb/CrazyColumns2008Test.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/LegacyDb/CrazyColumns2008Test.cs @@ -19,11 +19,19 @@ public class Crazy : Entity [Field, Key] public Guid Id { get; private set; } +#if NET6_0_OR_GREATER + [Field] + public TimeOnly Time { get; set; } + + [Field] + public DateOnly Date { get; set; } +#else [Field] public DateTime Time { get; set; } [Field] public DateTime Date { get; set; } +#endif } } @@ -46,6 +54,31 @@ protected override DomainConfiguration BuildConfiguration() return config; } +#if NET6_0_OR_GREATER + [Test] + [Ignore("Fix later")] + public void CombinedTest() + { + var date = new DateTime(2000, 01, 01); + var time = new DateTime(1, 1, 1, 12, 00, 00); + using (var session = Domain.OpenSession()) + using (var ts = session.OpenTransaction()) { + var crazy1 = new Crazy { Date = DateOnly.FromDateTime(date), Time = TimeOnly.FromDateTime(DateTime.Now) }; + var crazy2 = new Crazy { Date = DateOnly.FromDateTime(DateTime.Now), Time = TimeOnly.FromDateTime(time) }; + ts.Complete(); + } + + using (var session = Domain.OpenSession()) + using (session.OpenTransaction()) { + foreach (var item in session.Query.All()) { + Console.WriteLine(item.Date); + Console.WriteLine(item.Time); + } + Assert.AreEqual(1, session.Query.All().Where(o => o.Date == DateOnly.FromDateTime(date)).Count()); + Assert.AreEqual(1, session.Query.All().Where(o => o.Time == TimeOnly.FromDateTime(time)).Count()); + } + } +#else [Test] [Ignore("Fix later")] public void CombinedTest() @@ -69,6 +102,7 @@ public void CombinedTest() Assert.AreEqual(1, session.Query.All().Where(o => o.Time==time).Count()); } } +#endif protected override string GetCreateDbScript(DomainConfiguration config) { diff --git a/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/Model.cs b/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/Model.cs index 770455d796..49ec9306aa 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/Model.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/Model.cs @@ -4,6 +4,8 @@ // Created by: Alexey Kulakov // Created: 2017.04.05 +using System; + namespace Xtensive.Orm.Tests.Storage.SchemaSharing.EntityManipulation.Model { namespace Part1 diff --git a/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/SimpleEntityManipulationTest.cs b/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/SimpleEntityManipulationTest.cs index e9bf5dd3e2..7b07cf93c7 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/SimpleEntityManipulationTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/SchemaSharing/EntityManipulation/SimpleEntityManipulationTest.cs @@ -10,7 +10,6 @@ using NUnit.Framework; using Xtensive.Core; using Xtensive.Orm.Configuration; -using Xtensive.Orm.Tests.Model.UselessTypeInTheMiddleTestModel; using model = Xtensive.Orm.Tests.Storage.SchemaSharing.EntityManipulation.Model; namespace Xtensive.Orm.Tests.Storage.SchemaSharing.EntityManipulation diff --git a/Orm/Xtensive.Orm.Tests/Storage/SessionEventsTest.cs b/Orm/Xtensive.Orm.Tests/Storage/SessionEventsTest.cs index 9a47014ed7..ab4d3837bb 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/SessionEventsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/SessionEventsTest.cs @@ -10,7 +10,6 @@ using Xtensive.Orm.Configuration; using Xtensive.Orm.Tests.Storage.SessionEventsTestModel; using System.Threading.Tasks; -using Renci.SshNet; using System.Linq; namespace Xtensive.Orm.Tests.Storage.SessionEventsTestModel diff --git a/Orm/Xtensive.Orm.Tests/Storage/SetFieldTest.cs b/Orm/Xtensive.Orm.Tests/Storage/SetFieldTest.cs index bb67b45686..eaa4b371d8 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/SetFieldTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/SetFieldTest.cs @@ -25,6 +25,14 @@ public class Book : Entity [Field] public DateTime Date { get; set; } +#if NET6_0_OR_GREATER + [Field] + public DateOnly DateOnly { get; set; } + + [Field] + public TimeOnly TimeOnly { get; set; } + +#endif [Field] public Direction? NullableDirection { get; set; } @@ -57,19 +65,26 @@ public void CombinedTest() }; var book = new Book(); - AssertIsCalled (() => { book.Title = "A"; }); + AssertIsCalled(() => { book.Title = "A"; }); AssertIsNotCalled(() => { book.Title = "A"; }); - AssertIsCalled (() => { book.Date = new DateTime(1,2,3); }); - AssertIsNotCalled(() => { book.Date = new DateTime(1,2,3); }); - - var image = new byte[] {1, 2, 3}; - AssertIsCalled (() => { book.Image = image; }); - AssertIsCalled (() => { book.Image = image; }); + AssertIsCalled(() => { book.Date = new DateTime(1, 2, 3); }); + AssertIsNotCalled(() => { book.Date = new DateTime(1, 2, 3); }); +#if NET6_0_OR_GREATER //DO_DATEONLY + + AssertIsCalled(() => { book.DateOnly = new DateOnly(1, 2, 3); }); + AssertIsNotCalled(() => { book.DateOnly = new DateOnly(1, 2, 3); }); + AssertIsCalled(() => { book.TimeOnly = new TimeOnly(1, 2, 3); }); + AssertIsNotCalled(() => { book.TimeOnly = new TimeOnly(1, 2, 3); }); +#endif + + var image = new byte[] { 1, 2, 3 }; + AssertIsCalled(() => { book.Image = image; }); + AssertIsCalled(() => { book.Image = image; }); AssertIsNotCalled(() => { book.Pair = null; }); - AssertIsCalled (() => { book.Pair = book; }); + AssertIsCalled(() => { book.Pair = book; }); AssertIsNotCalled(() => { book.Pair = book; }); - AssertIsCalled (() => { book.Pair = null; }); + AssertIsCalled(() => { book.Pair = null; }); AssertIsNotCalled(() => { book.Pair = null; }); } } @@ -89,7 +104,7 @@ private void AssertIsCalled(Action action) { int oldCallCount = fieldSetCallCount; action.Invoke(); - if (fieldSetCallCount==oldCallCount) + if (fieldSetCallCount == oldCallCount) Assert.Fail("Expected event didn't occur."); } @@ -97,7 +112,7 @@ private void AssertIsNotCalled(Action action) { int oldCallCount = fieldSetCallCount; action.Invoke(); - if (fieldSetCallCount!=oldCallCount) + if (fieldSetCallCount != oldCallCount) Assert.Fail("Event occurred, although it shouldn't."); } } diff --git a/Orm/Xtensive.Orm.Tests/Storage/TypeCompatibilityTest.cs b/Orm/Xtensive.Orm.Tests/Storage/TypeCompatibilityTest.cs index 27fba9a5bb..6eafe43715 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/TypeCompatibilityTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/TypeCompatibilityTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2008-2021 Xtensive LLC. +// Copyright (C) 2008-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: Dmitri Maximov @@ -113,6 +113,14 @@ public class X : Entity [Field] public DateTime FDateTime { get; set; } +#if NET6_0_OR_GREATER + [Field] + public DateOnly FDateOnly { get; set; } + + [Field] + public TimeOnly FTimeOnly { get; set; } + +#endif [Field] public TimeSpan FTimeSpan { get; set; } @@ -198,6 +206,14 @@ public class X : Entity [Field] public DateTime? FNDateTime { get; set; } +#if NET6_0_OR_GREATER + [Field] + public DateOnly? FNDateOnly { get; set; } + + [Field] + public TimeOnly? FNTimeOnly { get; set; } + +#endif [Field] public TimeSpan? FNTimeSpan { get; set; } @@ -274,17 +290,28 @@ public void DefaultValuesTest() t.Complete(); } - var field = typeof (StorageDriver).GetField("underlyingDriver", BindingFlags.Instance | BindingFlags.NonPublic); - var sqlDriver = (SqlDriver) field.GetValue(Domain.Handlers.StorageDriver); + var sqlDriver = TestSqlDriver.Create(session.Domain.Configuration.ConnectionInfo); var dataTypeInfo = sqlDriver.ServerInfo.DataTypes.DateTime; - var minValue = ((ValueRange) dataTypeInfo.ValueRange).MinValue; + var dateTimeMinValue = ((ValueRange) dataTypeInfo.ValueRange).MinValue; + +#if NET6_0_OR_GREATER + dataTypeInfo = sqlDriver.ServerInfo.DataTypes.DateOnly; + var dateOnlyMinValue = ((ValueRange) dataTypeInfo.ValueRange).MinValue; + + dataTypeInfo = sqlDriver.ServerInfo.DataTypes.TimeOnly; + var timeOnlyMinValue = ((ValueRange) dataTypeInfo.ValueRange).MinValue; +#endif using (var t = session.OpenTransaction()) { X x = session.Query.SingleOrDefault(key); Assert.AreEqual(false, x.FBool); Assert.AreEqual(0, x.FByte); Assert.AreEqual(null, x.FByteArray); - Assert.AreEqual(minValue, x.FDateTime); + Assert.AreEqual(dateTimeMinValue, x.FDateTime); +#if NET6_0_OR_GREATER + Assert.AreEqual(dateOnlyMinValue, x.FDateOnly); + Assert.AreEqual(timeOnlyMinValue, x.FTimeOnly); +#endif Assert.AreEqual(0, x.FDecimal); Assert.AreEqual(0, x.FDouble); Assert.AreEqual(EByte.Default, x.FEByte); @@ -312,6 +339,10 @@ public void DefaultValuesTest() Assert.AreEqual(null, x.FNBool); Assert.AreEqual(null, x.FNByte); Assert.AreEqual(null, x.FNDateTime); +#if NET6_0_OR_GREATER + Assert.AreEqual(null, x.FNDateOnly); + Assert.AreEqual(null, x.FNTimeOnly); +#endif Assert.AreEqual(null, x.FNDecimal); Assert.AreEqual(null, x.FNDouble); Assert.AreEqual(null, x.FNEByte); diff --git a/Orm/Xtensive.Orm.Tests/Upgrade/DateTimeDataConversions.cs b/Orm/Xtensive.Orm.Tests/Upgrade/DateTimeDataConversions.cs new file mode 100644 index 0000000000..cf625c0a47 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Upgrade/DateTimeDataConversions.cs @@ -0,0 +1,529 @@ +// Copyright (C) 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; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Providers; +using Xtensive.Orm.Upgrade; +using Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel; + + +namespace Xtensive.Orm.Tests.Upgrade +{ + [TestFixture] + public class DateTimeTypesDataConversionTest + { + private readonly Dictionary upgradeHandlers = new(); + private readonly Dictionary initEntities = new(); + + private readonly TimeSpan ServerTimeZone = StorageProviderInfo.Instance.TimeZoneProvider.TimeZoneOffset; + private readonly bool RequreToServerOffset = + StorageProviderInfo.Instance.Provider is StorageProvider.PostgreSql; + + private readonly DateTimeOffset dateTimeOffsetInitValue = + new DateTimeOffset(new DateTime(2017, 10, 23, 22, 33, 44, 321), new TimeSpan(3, 45, 0)); + private readonly DateTime dateTimeInitValue = new DateTime(2017, 10, 23, 22,33,44, 321); +#if NET6_0_OR_GREATER + private readonly TimeOnly timeOnlyInitValue = TimeOnly.FromTimeSpan(new TimeSpan(22, 33, 44)); + private readonly DateOnly dateOnlyInitValue = DateOnly.FromDateTime(new DateTime(2017, 10, 23)); +#endif + + [OneTimeSetUp] + public void OneTimeSetup() + { + upgradeHandlers[nameof(DateTimeToDateTimeOffsetTest)] = typeof(DateTimeToDateTimeOffsetUpgradeHandler); + upgradeHandlers[nameof(DateTimeOffsetToDateTimeTest)] = typeof(DateTimeOffsetToDateTimeUpgradeHandler); +#if NET6_0_OR_GREATER + + upgradeHandlers[nameof(DateTimeToDateOnlyTest)] = typeof(DateTimeToDateOnlyUpgradeHandler); + upgradeHandlers[nameof(DateTimeToTimeOnlyTest)] = typeof(DateTimeToTimeOnlyUpgradeHandler); + + upgradeHandlers[nameof(DateTimeOffsetToDateOnlyTest)] = typeof(DateTimeOffsetToDateOnlyUpgradeHandler); + upgradeHandlers[nameof(DateTimeOffsetToTimeOnlyTest)] = typeof(DateTimeOffsetToTimeOnlyUpgradeHandler); + + upgradeHandlers[nameof(DateOnlyToDateTimeTest)] = typeof(DateOnlyToDateTimeUpgradeHandler); + upgradeHandlers[nameof(DateOnlyToDateTimeOffsetTest)] = typeof(DateOnlyToDateTimeOffsetUpgradeHandler); + upgradeHandlers[nameof(DateOnlyToTimeOnlyTest)] = typeof(DateOnlyToTimeOnlyUpgradeHandler); + + upgradeHandlers[nameof(TimeOnlyToDateTimeTest)] = typeof(TimeOnlyToDateTimeUpgradeHandler); + upgradeHandlers[nameof(TimeOnlyToDateTimeOffsetTest)] = typeof(TimeOnlyToDateTimeOffsetUpgradeHandler); + upgradeHandlers[nameof(TimeOnlyToDateOnlyTest)] = typeof(TimeOnlyToDateOnlyUpgradeHandler); +#endif + + initEntities[nameof(DateTimeToDateTimeOffsetTest)] = (typeof(DateTimeEntity), dateTimeInitValue); + initEntities[nameof(DateTimeOffsetToDateTimeTest)] = (typeof(DateTimeOffsetEntity), dateTimeOffsetInitValue); +#if NET6_0_OR_GREATER + + initEntities[nameof(DateTimeToDateOnlyTest)] = (typeof(DateTimeEntity), dateTimeInitValue); + initEntities[nameof(DateTimeToTimeOnlyTest)] = (typeof(DateTimeEntity), dateTimeInitValue); + + initEntities[nameof(DateTimeOffsetToDateOnlyTest)] = (typeof(DateTimeOffsetEntity), dateTimeOffsetInitValue); + initEntities[nameof(DateTimeOffsetToTimeOnlyTest)] = (typeof(DateTimeOffsetEntity), dateTimeOffsetInitValue); + + initEntities[nameof(DateOnlyToDateTimeTest)] = (typeof(DateOnlyEntity), dateOnlyInitValue); + initEntities[nameof(DateOnlyToDateTimeOffsetTest)] = (typeof(DateOnlyEntity), dateOnlyInitValue); + initEntities[nameof(DateOnlyToTimeOnlyTest)] = (typeof(DateOnlyEntity), dateOnlyInitValue); + + initEntities[nameof(TimeOnlyToDateTimeTest)] = (typeof(TimeOnlyEntity), timeOnlyInitValue); + initEntities[nameof(TimeOnlyToDateTimeOffsetTest)] = (typeof(TimeOnlyEntity), timeOnlyInitValue); + initEntities[nameof(TimeOnlyToDateOnlyTest)] = (typeof(TimeOnlyEntity), timeOnlyInitValue); +#endif + } + + [SetUp] + public void BeforeEveryTestMethodSetup() + { + var entityAndValue = GetInitialEntityType(); + if (entityAndValue.type.Name.Contains("Offset", StringComparison.Ordinal)) { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + } + + using (var domain = Domain.Build(BuildConfiguration(DomainUpgradeMode.Recreate, entityAndValue.type))) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + _ = entityAndValue.type + .GetMethod(nameof(DateTimeEntity.CreateTestInstance), BindingFlags.Public | BindingFlags.Static) + .Invoke(null,new[] { session, entityAndValue.initValue}); + + tx.Complete(); + } + } + + [Test] + public void DateTimeToDateTimeOffsetTest() + { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + + UpgradeAndTestData(new DateTimeOffset(dateTimeInitValue, ServerTimeZone)); + } + + [Test] + public void DateTimeOffsetToDateTimeTest() + { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + + var expectedValue = RequreToServerOffset + ? dateTimeOffsetInitValue.ToOffset(ServerTimeZone).DateTime + : dateTimeOffsetInitValue.DateTime; + + UpgradeAndTestData(expectedValue); + } +#if NET6_0_OR_GREATER + + [Test] + public void DateTimeToDateOnlyTest() + { + UpgradeAndTestData(DateOnly.FromDateTime(dateTimeInitValue)); + } + + [Test] + public void DateTimeToTimeOnlyTest() + { + if (StorageProviderInfo.Instance.Provider == StorageProvider.MySql) { + UpgradeAndTestData(timeOnlyInitValue);// datetime in mysql is without fractions + } + else { + UpgradeAndTestData(TimeOnly.FromDateTime(dateTimeInitValue)); + } + } + + [Test] + public void DateTimeOffsetToDateOnlyTest() + { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + + var expectedValue = RequreToServerOffset + ? DateOnly.FromDateTime(dateTimeOffsetInitValue.ToOffset(ServerTimeZone).DateTime) + : DateOnly.FromDateTime(dateTimeOffsetInitValue.DateTime); + + UpgradeAndTestData(expectedValue); + } + + [Test] + public void DateTimeOffsetToTimeOnlyTest() + { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + + var expectedValue = RequreToServerOffset + ? TimeOnly.FromDateTime(dateTimeOffsetInitValue.ToOffset(ServerTimeZone).DateTime) + : TimeOnly.FromDateTime(dateTimeOffsetInitValue.DateTime); + + UpgradeAndTestData(expectedValue); + } + + [Test] + public void DateOnlyToDateTimeTest() + { + UpgradeAndTestData(dateOnlyInitValue.ToDateTime(TimeOnly.MinValue)); + } + + [Test] + public void DateOnlyToDateTimeOffsetTest() + { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + + UpgradeAndTestData(new DateTimeOffset(dateOnlyInitValue.ToDateTime(TimeOnly.MinValue), ServerTimeZone)); + } + + [Test] + public void DateOnlyToTimeOnlyTest() + { + Require.ProviderIsNot(StorageProvider.Sqlite, "No any error from storage on conversion"); + + var provider = StorageProviderInfo.Instance.Provider; + + if (provider == StorageProvider.PostgreSql) { + UpgradeAndExpectException(); + } + else { + UpgradeAndExpectException(); + } + } + + [Test] + public void TimeOnlyToDateTimeTest() + { + var provider = StorageProviderInfo.Instance.Provider; + + DateTime expectedValue; + if (provider == StorageProvider.Oracle) { + expectedValue = DateTime.MinValue.Add(timeOnlyInitValue.ToTimeSpan()); + } + else if (provider == StorageProvider.Firebird || provider == StorageProvider.MySql) { + expectedValue = DateTime.Now.Date.Add(timeOnlyInitValue.ToTimeSpan()); + } + else if(provider == StorageProvider.PostgreSql) { + expectedValue = new DateTime(1970, 1, 1).Add(timeOnlyInitValue.ToTimeSpan()); + } + else { + expectedValue = new DateTime(1900, 1, 1).Add(timeOnlyInitValue.ToTimeSpan()); + } + + UpgradeAndTestData(expectedValue); + } + + [Test] + public void TimeOnlyToDateTimeOffsetTest() + { + Require.AnyFeatureSupported(ProviderFeatures.DateTimeOffset | ProviderFeatures.DateTimeOffsetEmulation); + + var provider = StorageProviderInfo.Instance.Provider; + + DateTimeOffset expectedValue; + if (provider == StorageProvider.Oracle) { + expectedValue = new DateTimeOffset( + DateTime.MinValue.Add(timeOnlyInitValue.ToTimeSpan()), + TimeSpan.FromMinutes(60 * 4 + 2)); + } + else if (provider == StorageProvider.PostgreSql) { + expectedValue = new DateTimeOffset(new DateTime(1970, 1, 1)).Add(timeOnlyInitValue.ToTimeSpan()).ToOffset(ServerTimeZone); + } + else { + expectedValue = new DateTimeOffset(new DateTime(1900, 1, 1).Add(timeOnlyInitValue.ToTimeSpan()), ServerTimeZone); + } + + UpgradeAndTestData(expectedValue); + } + + [Test] + public void TimeOnlyToDateOnlyTest() + { + Require.ProviderIsNot(StorageProvider.Sqlite, "No any error from storage on conversion"); + + var provider = StorageProviderInfo.Instance.Provider; + + if (provider == StorageProvider.PostgreSql) { + UpgradeAndExpectException(); + } + else { + UpgradeAndExpectException(); + } + } +#endif + + private void UpgradeAndTestData(TExpectedValue expectedValue) + where TEntity : Entity + { + var configuration = BuildConfiguration(DomainUpgradeMode.Perform, typeof(TEntity), GetUpgradeHandler()); + + using (var domain = Domain.Build(configuration)) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var entity = session.Query.All().FirstOrDefault(); + Assert.That(entity["ConversionField"], Is.EqualTo(expectedValue)); + } + } + + private void UpgradeSafelyAndTestData(TExpectedValue expectedValue) + where TEntity : Entity + { + var configuration = BuildConfiguration(DomainUpgradeMode.Perform, typeof(TEntity), GetUpgradeHandler()); + + using (var domain = Domain.Build(configuration)) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var entity = session.Query.All().FirstOrDefault(); + Assert.That(entity["ConversionField"], Is.EqualTo(expectedValue)); + } + } + + private void UpgradeAndExpectException() + where TEntity : Entity + where TExpectedException : Exception + { + var configuration = BuildConfiguration(DomainUpgradeMode.PerformSafely, typeof(TEntity), GetUpgradeHandler()); + _ = Assert.Throws(() => Domain.Build(configuration)); + } + + private DomainConfiguration BuildConfiguration(DomainUpgradeMode upgradeMode, Type entityType, Type upgraderType = null) + { + var domainConfiguration = DomainConfigurationFactory.Create(); + + domainConfiguration.UpgradeMode = upgradeMode; + domainConfiguration.Types.Register(entityType); + if (upgradeMode is DomainUpgradeMode.Perform or DomainUpgradeMode.PerformSafely + && upgraderType is not null) { + domainConfiguration.Types.Register(upgraderType); + } + return domainConfiguration; + } + + private Type GetUpgradeHandler() => + upgradeHandlers[TestContext.CurrentContext.Test.MethodName]; + + private (Type type, object initValue) GetInitialEntityType() => + initEntities[TestContext.CurrentContext.Test.MethodName]; + } +} + +namespace Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel +{ + [HierarchyRoot] + [TableMapping("TestEntity")] + public class DateTimeEntity : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public DateTime ConversionField { get; set; } + + public static object CreateTestInstance(Session session, object value) + { + return new DateTimeEntity(session, (DateTime) value); + } + + public DateTimeEntity(Session session, DateTime value) + : base(session) + { + ConversionField = value; + } + } + + [HierarchyRoot] + [TableMapping("TestEntity")] + public class DateTimeOffsetEntity : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public DateTimeOffset ConversionField { get; set; } + + public static object CreateTestInstance(Session session, object value) + { + return new DateTimeOffsetEntity(session, (DateTimeOffset) value); + } + + public DateTimeOffsetEntity(Session session, DateTimeOffset value) + : base(session) + { + ConversionField = value; + } + } +#if NET6_0_OR_GREATER + + [HierarchyRoot] + [TableMapping("TestEntity")] + public class DateOnlyEntity : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public DateOnly ConversionField { get; set; } + + public static object CreateTestInstance(Session session, object value) + { + return new DateOnlyEntity(session, (DateOnly) value); + } + + public DateOnlyEntity(Session session, DateOnly value) + : base(session) + { + ConversionField = value; + } + } + + [HierarchyRoot] + [TableMapping("TestEntity")] + public class TimeOnlyEntity : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public TimeOnly ConversionField { get; set; } + + public static object CreateTestInstance(Session session, object value) + { + return new TimeOnlyEntity(session, (TimeOnly) value); + } + + public TimeOnlyEntity(Session session, TimeOnly value) + : base(session) + { + ConversionField = value; + } + } +#endif + + public class DateTimeToDateTimeOffsetUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateTimeEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateTimeOffsetToDateTimeUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateTimeOffsetEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } +#if NET6_0_OR_GREATER + + public class DateTimeToDateOnlyUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateTimeEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateTimeToTimeOnlyUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateTimeEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateTimeOffsetToDateOnlyUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateTimeOffsetEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateTimeOffsetToTimeOnlyUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateTimeOffsetEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateOnlyToDateTimeUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateOnlyEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateOnlyToDateTimeOffsetUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateOnlyEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class DateOnlyToTimeOnlyUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.DateOnlyEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class TimeOnlyToDateTimeUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.TimeOnlyEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class TimeOnlyToDateTimeOffsetUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.TimeOnlyEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } + + public class TimeOnlyToDateOnlyUpgradeHandler : UpgradeHandler + { + public override bool CanUpgradeFrom(string oldVersion) => true; + + protected override void AddUpgradeHints(ISet hints) + { + _ = hints.Add(RenameTypeHint.Create("Xtensive.Orm.Tests.Upgrade.DateTimeTypesDataConversionModel.TimeOnlyEntity")); + _ = hints.Add(ChangeFieldTypeHint.Create(e => e.ConversionField)); + } + } +#endif +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj index 2631363597..ed55956c3b 100644 --- a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj +++ b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/Orm/Xtensive.Orm/Linq/ExpressionWriter.cs b/Orm/Xtensive.Orm/Linq/ExpressionWriter.cs index 55dd8b66f4..a58655cf42 100644 --- a/Orm/Xtensive.Orm/Linq/ExpressionWriter.cs +++ b/Orm/Xtensive.Orm/Linq/ExpressionWriter.cs @@ -361,6 +361,18 @@ protected override Expression VisitConstant(ConstantExpression c) Write(c.Value.ToString()); Write("\")"); } +#if NET6_0_OR_GREATER + else if (type == WellKnownTypes.DateOnly) { + Write("DateOnly.Parse(\""); + Write(c.Value.ToString()); + Write("\")"); + } + else if (type == WellKnownTypes.TimeOnly) { + Write("TimeOnly.Parse(\""); + Write(c.Value.ToString()); + Write("\")"); + } +#endif else if (c.Value is Type typeValue) { Write("typeof("); Write(GetTypeName(typeValue)); diff --git a/Orm/Xtensive.Orm/Orm/Building/Builders/ValueTypeBuilder.cs b/Orm/Xtensive.Orm/Orm/Building/Builders/ValueTypeBuilder.cs index 21f55d1cad..a520be565c 100644 --- a/Orm/Xtensive.Orm/Orm/Building/Builders/ValueTypeBuilder.cs +++ b/Orm/Xtensive.Orm/Orm/Building/Builders/ValueTypeBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2020 Xtensive LLC. +// Copyright (C) 2011-2020 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 @@ -58,6 +58,22 @@ private static object AdjustValue(string fieldName, Type fieldType, object value timespan = TimeSpan.FromTicks(ticks); return timespan; } +#if NET6_0_OR_GREATER + + if (valueType == WellKnownTypes.TimeOnly) { + if (value is string timeOnlyString && !TimeOnly.TryParse(timeOnlyString, out var timeOnly)) { + throw FailToParseValue(fieldName, timeOnlyString); + } + return timeOnly; + } + + if (valueType == WellKnownTypes.DateOnly) { + if (value is string dateOnlyString && !DateOnly.TryParse(dateOnlyString, out var dateOnly)) { + throw FailToParseValue(fieldName, dateOnlyString); + } + return dateOnly; + } +#endif return Convert.ChangeType(value, valueType); } diff --git a/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs b/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs index 9da5e7637d..b33652de0e 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs @@ -45,9 +45,12 @@ public static bool IsAnonymousConstructor(this Expression expression) public static bool IsNewExpressionSupportedByStorage(this Expression expression) => expression.NodeType == ExpressionType.New - && (expression.Type == WellKnownTypes.TimeSpan - || expression.Type == WellKnownTypes.DateTime - || expression.Type == WellKnownTypes.DateTimeOffset); + && expression.Type switch { var t => + t == WellKnownTypes.TimeSpan || t == WellKnownTypes.DateTime || t == WellKnownTypes.DateTimeOffset +#if NET6_0_OR_GREATER + || t == WellKnownTypes.DateOnly || t == WellKnownTypes.TimeOnly +#endif + }; public static bool IsQuery(this Expression expression) => expression.Type.IsOfGenericInterface(WellKnownInterfaces.QueryableOfT); diff --git a/Orm/Xtensive.Orm/Orm/Linq/ItemToTupleConverter{TItem}.cs b/Orm/Xtensive.Orm/Orm/Linq/ItemToTupleConverter{TItem}.cs index ddb897a6c1..e6085a193e 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/ItemToTupleConverter{TItem}.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/ItemToTupleConverter{TItem}.cs @@ -97,13 +97,17 @@ private static bool TypeIsStorageMappable(Type type) type = type.StripNullable(); return type.IsPrimitive || type.IsEnum || - type==WellKnownTypes.ByteArray || - type==WellKnownTypes.Decimal || - type==WellKnownTypes.String || - type==WellKnownTypes.DateTime || - type==WellKnownTypes.DateTimeOffset || - type==WellKnownTypes.Guid || - type==WellKnownTypes.TimeSpan; + type == WellKnownTypes.ByteArray || + type == WellKnownTypes.Decimal || + type == WellKnownTypes.String || + type == WellKnownTypes.DateTime || + type == WellKnownTypes.DateTimeOffset || +#if NET6_0_OR_GREATER + type == WellKnownTypes.DateOnly || + type == WellKnownTypes.TimeOnly || +#endif + type == WellKnownTypes.Guid || + type == WellKnownTypes.TimeSpan; } diff --git a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs index de88d6c07c..65f947022d 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs @@ -50,9 +50,7 @@ public Delegate GetUntypedCompiler(MemberInfo target) { ArgumentValidator.EnsureArgumentNotNull(target, nameof(target)); - return compilers.TryGetValue(GetCompilerKey(target), out var compiler) - ? compiler - : null; + return compilers.GetValueOrDefault(GetCompilerKey(target)); } public Func GetCompiler(MemberInfo target) @@ -103,14 +101,15 @@ private void UpdateRegistry( { foreach (var (targetMember, compiler) in newRegistrations) { var key = GetCompilerKey(targetMember); - if (conflictHandlingMethod != ConflictHandlingMethod.Overwrite && compilers.ContainsKey(key)) { - if (conflictHandlingMethod == ConflictHandlingMethod.ReportError) { - throw new InvalidOperationException(string.Format( - Strings.ExCompilerForXIsAlreadyRegistered, targetMember.GetFullName(true))); + if (!compilers.TryAdd(key, compiler)) { + switch (conflictHandlingMethod) { + case ConflictHandlingMethod.Overwrite: + compilers[key] = compiler; + break; + case ConflictHandlingMethod.ReportError: + throw new InvalidOperationException(string.Format(Strings.ExCompilerForXIsAlreadyRegistered, targetMember.GetFullName(true))); } - continue; } - compilers[key] = compiler; } } @@ -304,11 +303,10 @@ private static void ValidateCompilerParameter(ParameterInfo parameter, Type requ private static CompilerKey GetCompilerKey(MemberInfo member) { var canonicalMember = member; - var sourceProperty = canonicalMember as PropertyInfo; - if (sourceProperty!=null) { + if (canonicalMember is PropertyInfo sourceProperty) { canonicalMember = sourceProperty.GetGetMethod(); // GetGetMethod returns null in case of non public getter. - if (canonicalMember==null) { + if (canonicalMember is null) { return default; } } diff --git a/Orm/Xtensive.Orm/Orm/Providers/DbCommandExtensions.cs b/Orm/Xtensive.Orm/Orm/Providers/DbCommandExtensions.cs index 4447c6af71..f60d262e03 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/DbCommandExtensions.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/DbCommandExtensions.cs @@ -36,6 +36,14 @@ public static string ToHumanReadableString(this DbCommand command) case DateTimeOffset dateTimeOffset: value = dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss.fffffffK"); break; +#if NET6_0_OR_GREATER + case DateOnly dateOnly: + value = dateOnly.ToString("yyyy-MM-dd"); + break; + case TimeOnly timeOnly: + value = timeOnly.ToString("HH:mm:ss.fffffff"); + break; +#endif } result.AppendFormat("{0}='{1}';", parameter.ParameterName, value); } diff --git a/Orm/Xtensive.Orm/Orm/Providers/DomainHandler.cs b/Orm/Xtensive.Orm/Orm/Providers/DomainHandler.cs index e5ed7fa2e5..baad2dc491 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/DomainHandler.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/DomainHandler.cs @@ -128,6 +128,10 @@ protected virtual IEnumerable GetProviderCompilerContainers() typeof (StringCompilers), typeof (DateTimeCompilers), typeof (DateTimeOffsetCompilers), +#if NET6_0_OR_GREATER + typeof (DateOnlyCompilers), + typeof (TimeOnlyCompilers), +#endif typeof (TimeSpanCompilers), typeof (MathCompilers), typeof (NumericCompilers), diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs index 15d084b9a7..687c851413 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs @@ -199,9 +199,8 @@ private static SqlExpression OracleBlobCompare(SqlExpression left, SqlExpression private SqlExpression CompileMember(MemberInfo member, SqlExpression instance, params SqlExpression[] arguments) { - var memberCompiler = memberCompilerProvider.GetCompiler(member); - if (memberCompiler==null) - throw new NotSupportedException(string.Format(Strings.ExMemberXIsNotSupported, member.GetFullName(true))); + var memberCompiler = memberCompilerProvider.GetCompiler(member) + ?? throw new NotSupportedException(string.Format(Strings.ExMemberXIsNotSupported, member.GetFullName(true))); return memberCompiler.Invoke(instance, arguments); } @@ -221,6 +220,14 @@ private static bool IsBooleanExpression(Expression expression) => private static bool IsDateTimeExpression(Expression expression) => IsExpressionOf(expression, WellKnownTypes.DateTime); +#if NET6_0_OR_GREATER + + private static bool IsDateOnlyExpression(Expression expression) => + IsExpressionOf(expression, WellKnownTypes.DateOnly); + + private static bool IsTimeOnlyExpression(Expression expression) => + IsExpressionOf(expression, WellKnownTypes.TimeOnly); +#endif private static bool IsDateTimeOffsetExpression(Expression expression) => IsExpressionOf(expression, WellKnownTypes.DateTimeOffset); diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs index 24e43ed153..bae6113bb6 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs @@ -219,10 +219,21 @@ protected override SqlExpression VisitBinary(BinaryExpression expression) if (dateTimeEmulation && left.NodeType != SqlNodeType.Null && right.NodeType != SqlNodeType.Null - && IsComparisonExpression(expression) - && (IsDateTimeExpression(expressionLeft) || IsDateTimeExpression(expressionRight))) { - left = SqlDml.Cast(left, SqlType.DateTime); - right = SqlDml.Cast(right, SqlType.DateTime); + && IsComparisonExpression(expression)) { + if (IsDateTimeExpression(expression.Left) || IsDateTimeExpression(expression.Right)) { + left = SqlDml.Cast(left, SqlType.DateTime); + right = SqlDml.Cast(right, SqlType.DateTime); + } +#if NET6_0_OR_GREATER + else if (IsDateOnlyExpression(expression.Left) || IsDateOnlyExpression(expression.Right)) { + left = SqlDml.Cast(left, SqlType.Date); + right = SqlDml.Cast(right, SqlType.Date); + } + else if (IsTimeOnlyExpression(expression.Left) || IsDateOnlyExpression(expression.Right)) { + left = SqlDml.Cast(left, SqlType.Time); + right = SqlDml.Cast(right, SqlType.Time); + } +#endif } //handle SQLite DateTimeOffset comparsion diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionTranslationHelpers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionTranslationHelpers.cs index 400c48abbd..13bc64c11a 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionTranslationHelpers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionTranslationHelpers.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2022 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 @@ -13,80 +13,58 @@ namespace Xtensive.Orm.Providers { internal static class ExpressionTranslationHelpers { - public static SqlExpression ToBool(SqlExpression target) - { - return Cast(target, WellKnownTypes.Bool); - } + public static SqlExpression ToBool(SqlExpression target) => + Cast(target, WellKnownTypes.Bool); - public static SqlExpression ToInt(SqlExpression target) - { - return Cast(target, WellKnownTypes.Int32); - } + public static SqlExpression ToInt(SqlExpression target) => + Cast(target, WellKnownTypes.Int32); - public static SqlExpression ToDouble(SqlExpression target) - { - return Cast(target, WellKnownTypes.Double); - } + public static SqlExpression ToDouble(SqlExpression target) => + Cast(target, WellKnownTypes.Double); - public static SqlExpression ToLong(SqlExpression target) - { - return Cast(target, WellKnownTypes.Int64); - } + public static SqlExpression ToLong(SqlExpression target) => + Cast(target, WellKnownTypes.Int64); - public static SqlExpression ToSbyte(SqlExpression target) - { - return Cast(target, WellKnownTypes.SByte); - } + public static SqlExpression ToSbyte(SqlExpression target) => + Cast(target, WellKnownTypes.SByte); - public static SqlExpression ToShort(SqlExpression target) - { - return Cast(target, WellKnownTypes.Int16); - } + public static SqlExpression ToShort(SqlExpression target) => + Cast(target, WellKnownTypes.Int16); - public static SqlExpression ToFloat(SqlExpression target) - { - return Cast(target, WellKnownTypes.Single); - } + public static SqlExpression ToFloat(SqlExpression target) => + Cast(target, WellKnownTypes.Single); - public static SqlExpression ToDecimal(SqlExpression target) - { - return Cast(target, WellKnownTypes.Decimal); - } + public static SqlExpression ToDecimal(SqlExpression target) => + Cast(target, WellKnownTypes.Decimal); - public static SqlExpression ToByte(SqlExpression target) - { - return Cast(target, WellKnownTypes.Byte); - } + public static SqlExpression ToByte(SqlExpression target) => + Cast(target, WellKnownTypes.Byte); - public static SqlExpression ToChar(SqlExpression target) - { - return Cast(target, WellKnownTypes.String); - } + public static SqlExpression ToChar(SqlExpression target) => + Cast(target, WellKnownTypes.String); - public static SqlExpression ToDateTime(SqlExpression target) - { - return Cast(target, WellKnownTypes.DateTime); - } + public static SqlExpression ToDateTime(SqlExpression target) => + Cast(target, WellKnownTypes.DateTime); +#if NET6_0_OR_GREATER - public static SqlExpression ToUint(SqlExpression target) - { - return Cast(target, WellKnownTypes.UInt32); - } + public static SqlExpression ToDate(SqlExpression target) => + Cast(target, WellKnownTypes.DateOnly); - public static SqlExpression ToUlong(SqlExpression target) - { - return Cast(target, WellKnownTypes.UInt64); - } + public static SqlExpression ToTime(SqlExpression target) => + Cast(target, WellKnownTypes.TimeOnly); +#endif - public static SqlExpression ToUshort(SqlExpression target) - { - return Cast(target, WellKnownTypes.UInt16); - } + public static SqlExpression ToUint(SqlExpression target) => + Cast(target, WellKnownTypes.UInt32); - public static SqlExpression ToString(SqlExpression target) - { - return Cast(target, WellKnownTypes.String); - } + public static SqlExpression ToUlong(SqlExpression target) => + Cast(target, WellKnownTypes.UInt64); + + public static SqlExpression ToUshort(SqlExpression target) => + Cast(target, WellKnownTypes.UInt16); + + public static SqlExpression ToString(SqlExpression target) => + Cast(target, WellKnownTypes.String); private static SqlExpression Cast(SqlExpression target, Type type) { @@ -94,4 +72,4 @@ private static SqlExpression Cast(SqlExpression target, Type type) return SqlDml.Cast(target, destinationType); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateOnlyCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateOnlyCompilers.cs new file mode 100644 index 0000000000..691b7c560a --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateOnlyCompilers.cs @@ -0,0 +1,148 @@ +// Copyright (C) 2022 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.Linq.Expressions; +using Xtensive.Sql; +using Xtensive.Sql.Dml; +using Operator = Xtensive.Reflection.WellKnown.Operator; + +namespace Xtensive.Orm.Providers +{ +#if NET6_0_OR_GREATER + + [CompilerContainer(typeof(SqlExpression))] + internal static class DateOnlyCompilers + { + #region Extractors + + [Compiler(typeof(DateOnly), "Year", TargetKind.PropertyGet)] + public static SqlExpression DateOnlyYear(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDatePart.Year, _this)); + + [Compiler(typeof(DateOnly), "Month", TargetKind.PropertyGet)] + public static SqlExpression DateOnlyMonth(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDatePart.Month, _this)); + + [Compiler(typeof(DateOnly), "Day", TargetKind.PropertyGet)] + public static SqlExpression DateOnlyDay(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDatePart.Day, _this)); + + + [Compiler(typeof(DateOnly), "DayOfYear", TargetKind.PropertyGet)] + public static SqlExpression DateOnlyDayOfYear(SqlExpression _this) + { + return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDatePart.DayOfYear, _this)); + } + + [Compiler(typeof(DateOnly), "DayOfWeek", TargetKind.PropertyGet)] + public static SqlExpression DateOnlyDayOfWeek(SqlExpression _this) + { + var baseExpression = ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDatePart.DayOfWeek, _this)); + var context = ExpressionTranslationContext.Current; + if (context == null) { + return baseExpression; + } + if (context.ProviderInfo.ProviderName == WellKnown.Provider.MySql) { + return baseExpression - 1; //Mysql starts days of week from 1 unlike in .Net. + } + return baseExpression; + } + + #endregion + + #region Constructors + + [Compiler(typeof(DateOnly), null, TargetKind.Constructor)] + public static SqlExpression DateOnlyCtor( + [Type(typeof(int))] SqlExpression year, + [Type(typeof(int))] SqlExpression month, + [Type(typeof(int))] SqlExpression day) => + SqlDml.DateConstruct(year, month, day); + + #endregion + + #region Operators + + [Compiler(typeof(DateOnly), Operator.Equality, TargetKind.Operator)] + public static SqlExpression DateOnlyOperatorEquality( + [Type(typeof(DateOnly))] SqlExpression d1, + [Type(typeof(DateOnly))] SqlExpression d2) + { + return d1 == d2; + } + + [Compiler(typeof(DateOnly), Operator.Inequality, TargetKind.Operator)] + public static SqlExpression DateOnlyOperatorInequality( + [Type(typeof(DateOnly))] SqlExpression d1, + [Type(typeof(DateOnly))] SqlExpression d2) + { + return d1 != d2; + } + + [Compiler(typeof(DateOnly), Operator.GreaterThan, TargetKind.Operator)] + public static SqlExpression DateOnlyOperatorGreaterThan( + [Type(typeof(DateOnly))] SqlExpression d1, + [Type(typeof(DateOnly))] SqlExpression d2) + { + return d1 > d2; + } + + [Compiler(typeof(DateOnly), Operator.GreaterThanOrEqual, TargetKind.Operator)] + public static SqlExpression DateOnlyOperatorGreaterThanOrEqual( + [Type(typeof(DateOnly))] SqlExpression d1, + [Type(typeof(DateOnly))] SqlExpression d2) + { + return d1 >= d2; + } + + [Compiler(typeof(DateOnly), Operator.LessThan, TargetKind.Operator)] + public static SqlExpression DateOnlyOperatorLessThan( + [Type(typeof(DateOnly))] SqlExpression d1, + [Type(typeof(DateOnly))] SqlExpression d2) + { + return d1 < d2; + } + + [Compiler(typeof(DateOnly), Operator.LessThanOrEqual, TargetKind.Operator)] + public static SqlExpression DateOnlyOperatorLessThanOrEqual( + [Type(typeof(DateOnly))] SqlExpression d1, + [Type(typeof(DateOnly))] SqlExpression d2) + { + return d1 <= d2; + } + + #endregion + + [Compiler(typeof(DateOnly), "AddYears")] + public static SqlExpression DateOnlyAddYears(SqlExpression _this, [Type(typeof(int))] SqlExpression value) => + SqlDml.DateAddYears(_this, value); + + [Compiler(typeof(DateOnly), "AddMonths")] + public static SqlExpression DateOnlyAddMonths(SqlExpression _this, [Type(typeof(int))] SqlExpression value) => + SqlDml.DateAddMonths(_this, value); + + [Compiler(typeof(DateOnly), "AddDays")] + public static SqlExpression DateOnlyAddDays(SqlExpression _this, [Type(typeof(int))] SqlExpression value) => + SqlDml.DateAddDays(_this, value); + + [Compiler(typeof(DateOnly), "ToString")] + public static SqlExpression DateOnlyToStringIso(SqlExpression _this) + { + throw new NotSupportedException(Strings.ExDateOnlyToStringMethodIsNotSupported); + } + + [Compiler(typeof(DateOnly), "ToString")] + public static SqlExpression DateOnlyToStringIso(SqlExpression _this, [Type(typeof(string))] SqlExpression value) + { + var stringValue = value as SqlLiteral; + + if (stringValue == null || !stringValue.Value.Equals("o", StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException(Strings.ExTranslationOfDateOnlyToStringWithArbitraryArgumentIsNotSupported); + + return SqlDml.DateToString(_this); + } + } +#endif +} diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateTimeCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateTimeCompilers.cs index df617de4e7..cadfe870f3 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateTimeCompilers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/DateTimeCompilers.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2003-2022 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.02.14 @@ -18,58 +18,40 @@ internal static class DateTimeCompilers #region Extractors [Compiler(typeof(DateTime), "Year", TargetKind.PropertyGet)] - public static SqlExpression DateTimeYear(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Year, _this)); - } + public static SqlExpression DateTimeYear(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Year, _this)); [Compiler(typeof(DateTime), "Month", TargetKind.PropertyGet)] - public static SqlExpression DateTimeMonth(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Month, _this)); - } + public static SqlExpression DateTimeMonth(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Month, _this)); [Compiler(typeof(DateTime), "Day", TargetKind.PropertyGet)] - public static SqlExpression DateTimeDay(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Day, _this)); - } + public static SqlExpression DateTimeDay(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Day, _this)); [Compiler(typeof(DateTime), "Hour", TargetKind.PropertyGet)] - public static SqlExpression DateTimeHour(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Hour, _this)); - } + public static SqlExpression DateTimeHour(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Hour, _this)); [Compiler(typeof(DateTime), "Minute", TargetKind.PropertyGet)] - public static SqlExpression DateTimeMinute(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Minute, _this)); - } + public static SqlExpression DateTimeMinute(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Minute, _this)); [Compiler(typeof(DateTime), "Second", TargetKind.PropertyGet)] - public static SqlExpression DateTimeSecond(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Second, _this)); - } + public static SqlExpression DateTimeSecond(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Second, _this)); [Compiler(typeof(DateTime), "Millisecond", TargetKind.PropertyGet)] - public static SqlExpression DateTimeMillisecond(SqlExpression _this) - { - return ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Millisecond, _this)); - } + public static SqlExpression DateTimeMillisecond(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlDateTimePart.Millisecond, _this)); [Compiler(typeof(DateTime), "TimeOfDay", TargetKind.PropertyGet)] - public static SqlExpression DateTimeTimeOfDay(SqlExpression _this) - { - return SqlDml.DateTimeMinusDateTime(_this, SqlDml.DateTimeTruncate(_this)); - } + public static SqlExpression DateTimeTimeOfDay(SqlExpression _this) => + SqlDml.DateTimeMinusDateTime(_this, SqlDml.DateTimeTruncate(_this)); [Compiler(typeof(DateTime), "Date", TargetKind.PropertyGet)] - public static SqlExpression DateTimeDate(SqlExpression _this) - { - return SqlDml.DateTimeTruncate(_this); - } + public static SqlExpression DateTimeDate(SqlExpression _this) => + SqlDml.DateTimeTruncate(_this); [Compiler(typeof(DateTime), "DayOfWeek", TargetKind.PropertyGet)] public static SqlExpression DateTimeDayOfWeek(SqlExpression _this) @@ -112,7 +94,7 @@ private static SqlExpression DateTimeConstruct( [Compiler(typeof(DateTime), null, TargetKind.Constructor)] public static SqlExpression DateTimeCtor( [Type(typeof(int))] SqlExpression year, - [Type(typeof(int))] SqlExpression month, + [Type(typeof(int))] SqlExpression month, [Type(typeof(int))] SqlExpression day) { return SqlDml.DateTimeConstruct(year, month, day); @@ -223,92 +205,56 @@ public static SqlExpression DateTimeOperatorSubtractionDateTime( #endregion [Compiler(typeof(DateTime), "Add")] - public static SqlExpression DateTimeAdd(SqlExpression _this, - [Type(typeof(TimeSpan))] SqlExpression value) - { - return SqlDml.DateTimePlusInterval(_this, value); - } + public static SqlExpression DateTimeAdd(SqlExpression _this, [Type(typeof(TimeSpan))] SqlExpression value) => + SqlDml.DateTimePlusInterval(_this, value); [Compiler(typeof(DateTime), "AddYears")] - public static SqlExpression DateTimeAddYears(SqlExpression _this, - [Type(typeof(int))] SqlExpression value) - { - return SqlDml.DateTimeAddYears(_this, value); - } + public static SqlExpression DateTimeAddYears(SqlExpression _this, [Type(typeof(int))] SqlExpression value) => + SqlDml.DateTimeAddYears(_this, value); [Compiler(typeof(DateTime), "AddMonths")] - public static SqlExpression DateTimeAddMonths(SqlExpression _this, - [Type(typeof(int))] SqlExpression value) - { - return SqlDml.DateTimeAddMonths(_this, value); - } + public static SqlExpression DateTimeAddMonths(SqlExpression _this, [Type(typeof(int))] SqlExpression value) => + SqlDml.DateTimeAddMonths(_this, value); [Compiler(typeof(DateTime), "AddDays")] - public static SqlExpression DateTimeAddDays(SqlExpression _this, - [Type(typeof(double))] SqlExpression value) - { - return SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromDays(value)); - } + public static SqlExpression DateTimeAddDays(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromDays(value)); [Compiler(typeof(DateTime), "AddHours")] - public static SqlExpression DateTimeAddHours(SqlExpression _this, - [Type(typeof(double))] SqlExpression value) - { - return SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromHours(value)); - } + public static SqlExpression DateTimeAddHours(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromHours(value)); [Compiler(typeof(DateTime), "AddMinutes")] - public static SqlExpression DateTimeAddMinutes(SqlExpression _this, - [Type(typeof(double))] SqlExpression value) - { - return SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromMinutes(value)); - } + public static SqlExpression DateTimeAddMinutes(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromMinutes(value)); [Compiler(typeof(DateTime), "AddSeconds")] - public static SqlExpression DateTimeAddSeconds(SqlExpression _this, - [Type(typeof(double))] SqlExpression value) - { - return SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromSeconds(value)); - } + public static SqlExpression DateTimeAddSeconds(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromSeconds(value)); [Compiler(typeof(DateTime), "AddMilliseconds")] - public static SqlExpression DateTimeAddMilliseconds(SqlExpression _this, - [Type(typeof(double))] SqlExpression value) - { - return SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromMilliseconds(value)); - } + public static SqlExpression DateTimeAddMilliseconds(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.DateTimePlusInterval(_this, TimeSpanCompilers.TimeSpanFromMilliseconds(value)); [Compiler(typeof(DateTime), "Subtract")] - public static SqlExpression DateTimeSubtractTimeSpan(SqlExpression _this, - [Type(typeof(TimeSpan))] SqlExpression value) - { - return SqlDml.DateTimeMinusInterval(_this, value); - } + public static SqlExpression DateTimeSubtractTimeSpan(SqlExpression _this, [Type(typeof(TimeSpan))] SqlExpression value) => + SqlDml.DateTimeMinusInterval(_this, value); [Compiler(typeof(DateTime), "Subtract")] - public static SqlExpression DateTimeSubtractDateTime(SqlExpression _this, - [Type(typeof(DateTime))] SqlExpression value) - { - return SqlDml.DateTimeMinusDateTime(_this, value); - } + public static SqlExpression DateTimeSubtractDateTime(SqlExpression _this, [Type(typeof(DateTime))] SqlExpression value) => + SqlDml.DateTimeMinusDateTime(_this, value); [Compiler(typeof(DateTime), "Now", TargetKind.Static | TargetKind.PropertyGet)] - public static SqlExpression DateTimeNow() - { - return SqlDml.CurrentTimeStamp(); - } + public static SqlExpression DateTimeNow() => + SqlDml.CurrentTimeStamp(); [Compiler(typeof(DateTime), "Today", TargetKind.Static | TargetKind.PropertyGet)] - public static SqlExpression DateTimeToday() - { - return SqlDml.CurrentDate(); - } + public static SqlExpression DateTimeToday() => + SqlDml.CurrentDate(); [Compiler(typeof(DateTime), "IsLeapYear", TargetKind.Static | TargetKind.Method)] - public static SqlExpression DateTimeIsLeapYear([Type(typeof(int))] SqlExpression year) - { - return ((year % 4==0) && (year % 100!=0)) || (year % 400==0); - } + public static SqlExpression DateTimeIsLeapYear([Type(typeof(int))] SqlExpression year) => + ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); [Compiler(typeof(DateTime), "DaysInMonth", TargetKind.Static | TargetKind.Method)] public static SqlExpression DateTimeDaysInMonth( @@ -321,7 +267,7 @@ public static SqlExpression DateTimeDaysInMonth( var result = SqlDml.Case(); result.Add(SqlDml.In(month, SqlDml.Array(1, 3, 5, 7, 8, 10, 12)), 31); - result.Add(month==2, februaryCase); + result.Add(month == 2, februaryCase); result.Else = 30; return result; @@ -338,13 +284,10 @@ public static SqlExpression DateTimeToStringIso(SqlExpression _this, [Type(typeo { var stringValue = value as SqlLiteral; - if (stringValue==null) - throw new NotSupportedException(Strings.ExTranslationOfDateTimeToStringWithArbitraryArgumentsIsNotSupported); - - if (!stringValue.Value.Equals("s")) - throw new NotSupportedException(Strings.ExTranslationOfDateTimeToStringWithArbitraryArgumentsIsNotSupported); + if (stringValue == null || !stringValue.Value.Equals("s")) + throw new NotSupportedException(Strings.ExTranslationOfDateTimeToStringWithArbitraryArgumentIsNotSupported); return SqlDml.DateTimeToStringIso(_this); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs new file mode 100644 index 0000000000..50f38321fe --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs @@ -0,0 +1,158 @@ +// Copyright (C) 2022 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 Xtensive.Reflection; +using Xtensive.Sql; +using Xtensive.Sql.Dml; +using Operator = Xtensive.Reflection.WellKnown.Operator; + +namespace Xtensive.Orm.Providers +{ +#if NET6_0_OR_GREATER + + [CompilerContainer(typeof(SqlExpression))] + internal static class TimeOnlyCompilers + { + private const long NanosecondsPerTick = 100; + + #region Extractors + + [Compiler(typeof(TimeOnly), "Hour", TargetKind.PropertyGet)] + public static SqlExpression TimeOnlyHour(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlTimePart.Hour, _this)); + + [Compiler(typeof(TimeOnly), "Minute", TargetKind.PropertyGet)] + public static SqlExpression TimeOnlyMinute(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlTimePart.Minute, _this)); + + [Compiler(typeof(TimeOnly), "Second", TargetKind.PropertyGet)] + public static SqlExpression TimeOnlySecond(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlTimePart.Second, _this)); + + [Compiler(typeof(TimeOnly), "Millisecond", TargetKind.PropertyGet)] + public static SqlExpression TimeOnlyMillisecond(SqlExpression _this) => + ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlTimePart.Millisecond, _this)); + + #endregion + + #region Constructors + [Compiler(typeof(TimeOnly), null, TargetKind.Constructor)] + public static SqlExpression TimeOnlyCtor( + [Type(typeof(int))] SqlExpression hour, + [Type(typeof(int))] SqlExpression minute, + [Type(typeof(int))] SqlExpression second, + [Type(typeof(int))] SqlExpression millisecond) => + SqlDml.TimeConstruct(hour, minute, second, millisecond); + + [Compiler(typeof(TimeOnly), null, TargetKind.Constructor)] + public static SqlExpression TimeOnlyCtor( + [Type(typeof(int))] SqlExpression hour, + [Type(typeof(int))] SqlExpression minute, + [Type(typeof(int))] SqlExpression second) => + SqlDml.TimeConstruct(hour, minute, second, 0); + + [Compiler(typeof(TimeOnly), null, TargetKind.Constructor)] + public static SqlExpression TimeOnlyCtor( + [Type(typeof(int))] SqlExpression hour, + [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); + + #endregion + + #region Operators + + [Compiler(typeof(TimeOnly), Operator.Equality, TargetKind.Operator)] + public static SqlExpression TimeOnlyOperatorEquality( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return t1 == t2; + } + + [Compiler(typeof(TimeOnly), Operator.Inequality, TargetKind.Operator)] + public static SqlExpression TimeOnlyOperatorInequality( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return t1 != t2; + } + + [Compiler(typeof(TimeOnly), Operator.GreaterThan, TargetKind.Operator)] + public static SqlExpression TimeOnlyyOperatorGreaterThan( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return t1 > t2; + } + + [Compiler(typeof(TimeOnly), Operator.GreaterThanOrEqual, TargetKind.Operator)] + public static SqlExpression TimeOnlyOperatorGreaterThanOrEqual( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return t1 >= t2; + } + + [Compiler(typeof(TimeOnly), Operator.LessThan, TargetKind.Operator)] + public static SqlExpression TimeOnlyOperatorLessThan( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return t1 < t2; + } + + [Compiler(typeof(TimeOnly), Operator.LessThanOrEqual, TargetKind.Operator)] + public static SqlExpression TimeOnlyOperatorLessThanOrEqual( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return t1 <= t2; + } + + [Compiler(typeof(TimeOnly), Operator.Subtraction, TargetKind.Operator)] + public static SqlExpression TimeOnlyOperatorSubtraction( + [Type(typeof(TimeOnly))] SqlExpression t1, + [Type(typeof(TimeOnly))] SqlExpression t2) + { + return SqlDml.TimeMinusTime(t1, t2); + } + + #endregion + + [Compiler(typeof(TimeOnly), "AddHours")] + public static SqlExpression TimeOnlyAddHours(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.TimeAddHours(_this, value); + + [Compiler(typeof(TimeOnly), "AddMinutes")] + public static SqlExpression TimeOnlyAddMinutes(SqlExpression _this, [Type(typeof(double))] SqlExpression value) => + SqlDml.TimeAddMinutes(_this, value); + + [Compiler(typeof(TimeOnly), "Add")] + public static SqlExpression TimeOnlyAdd(SqlExpression _this, [Type(typeof(TimeSpan))] SqlExpression value) => + SqlDml.TimePlusInterval(_this, value); + + [Compiler(typeof(TimeOnly), "ToString")] + public static SqlExpression TimeOnlyToStringIso(SqlExpression _this) + { + throw new NotSupportedException(Strings.ExTimeOnlyToStringMethodIsNotSupported); + } + + [Compiler(typeof(TimeOnly), "ToString")] + public static SqlExpression TimeOnlyToStringIso(SqlExpression _this, [Type(typeof(string))] SqlExpression value) + { + var stringValue = value as SqlLiteral; + + if (stringValue == null || !stringValue.Value.Equals("o", StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException(Strings.ExTranslationOfTimeOnlyToStringWithArbitraryArgumentIsNotSupported); + + return SqlDml.TimeToString(_this); + } + } +#endif +} diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs index 4b85992644..935c475289 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs @@ -5,6 +5,7 @@ // Created: 2009.02.24 using System; +using Xtensive.Reflection; using Xtensive.Sql; using Xtensive.Sql.Dml; using Operator = Xtensive.Reflection.WellKnown.Operator; @@ -31,8 +32,14 @@ internal static SqlExpression GenericIntervalConstruct( SqlExpression seconds, SqlExpression milliseconds) { + var context = ExpressionTranslationContext.Current; + var mapping = context.Driver.GetTypeMapping(typeof(long)); + var mappedType = mapping.MapType(); + var m = milliseconds + 1000L * (seconds + 60L * (minutes + 60L * (hours + 24L * days))); - var nanoseconds = NanosecondsPerMillisecond * SqlDml.Cast(m, SqlType.Int64); + var nanoseconds = (mappedType.Precision.HasValue) + ? NanosecondsPerMillisecond * SqlDml.Cast(m, mappedType.Type, (short) mappedType.Precision.Value, (short) mappedType.Scale.Value) + : NanosecondsPerMillisecond * SqlDml.Cast(m, mappedType.Type); return SqlDml.IntervalConstruct(nanoseconds); } diff --git a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs index c007b5e345..fc622c27cc 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs @@ -303,8 +303,18 @@ private SqlExpression GetOrderByExpression(SqlExpression expression, SortProvide } var columnType = columns[index].Type; - if (providerInfo.Supports(ProviderFeatures.DateTimeEmulation) && columnType == WellKnownTypes.DateTime) { - return SqlDml.Cast(expression, SqlType.DateTime); + if (providerInfo.Supports(ProviderFeatures.DateTimeEmulation)) { + if (columnType == WellKnownTypes.DateTime) { + return SqlDml.Cast(expression, SqlType.DateTime); + } +#if NET6_0_OR_GREATER + if (columnType == WellKnownTypes.DateOnly) { + return SqlDml.Cast(expression, SqlType.Date); + } + if (columnType == WellKnownTypes.TimeOnly) { + return SqlDml.Cast(expression, SqlType.Time); + } +#endif } if (providerInfo.Supports(ProviderFeatures.DateTimeOffsetEmulation) && columnType == WellKnownTypes.DateTimeOffset) { @@ -321,13 +331,8 @@ private SqlExpression GetJoinExpression(SqlExpression leftExpression, SqlExpress Pair columnPair; if (providerInfo.Supports(ProviderFeatures.DateTimeEmulation)) { columnPair = provider.EqualColumns[index]; - if (columnPair.First.Type == WellKnownTypes.DateTime) { - leftExpression = SqlDml.Cast(leftExpression, SqlType.DateTime); - } - - if (columnPair.Second.Type == WellKnownTypes.DateTime) { - rightExpression = SqlDml.Cast(rightExpression, SqlType.DateTime); - } + leftExpression = CastToDateTimeVariantIfNeeded(leftExpression, columnPair.First.Type); + rightExpression = CastToDateTimeVariantIfNeeded(rightExpression, columnPair.Second.Type); } if (providerInfo.Supports(ProviderFeatures.DateTimeOffsetEmulation)) { @@ -343,6 +348,28 @@ private SqlExpression GetJoinExpression(SqlExpression leftExpression, SqlExpress } return leftExpression == rightExpression; + + static SqlExpression CastToDateTimeVariantIfNeeded(SqlExpression expression,Type type) + { + SqlType? sqlType = null; + if (type == WellKnownTypes.DateTime) { + sqlType = SqlType.DateTime; + } +#if NET6_0_OR_GREATER + else if (type == WellKnownTypes.DateOnly) { + sqlType = SqlType.Date; + } + else if(type == WellKnownTypes.TimeOnly) { + sqlType = SqlType.Time; + } +#endif + if (sqlType == null) { + return expression; + } + else { + return SqlDml.Cast(expression, sqlType.Value); + } + } } public SqlExpression GetOuterExpression(ApplyParameter parameter, int columnIndex) diff --git a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs index e4f26b7c1f..477d5f0de0 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs @@ -133,7 +133,11 @@ private static Type GetMinMaxColumnType(Type sourceColumnType, AggregateType agg default: if (sourceColumnType==WellKnownTypes.TimeSpan || sourceColumnType==WellKnownTypes.DateTimeOffset) return sourceColumnType; - throw AggregateNotSupported(sourceColumnType, aggregateType); +#if NET6_0_OR_GREATER + if (sourceColumnType == WellKnownTypes.DateOnly || sourceColumnType == WellKnownTypes.TimeOnly) + return sourceColumnType; +#endif + throw AggregateNotSupported(sourceColumnType, aggregateType); } } diff --git a/Orm/Xtensive.Orm/Orm/Upgrade/Internals/SqlActionTranslator.cs b/Orm/Xtensive.Orm/Orm/Upgrade/Internals/SqlActionTranslator.cs index 89abe83292..1d006bd475 100644 --- a/Orm/Xtensive.Orm/Orm/Upgrade/Internals/SqlActionTranslator.cs +++ b/Orm/Xtensive.Orm/Orm/Upgrade/Internals/SqlActionTranslator.cs @@ -912,15 +912,16 @@ private void ChangeColumnType(PropertyChangeAction action) } else { var getValue = SqlDml.Case(); - getValue.Add(SqlDml.IsNull(tableRef[tempName]), GetDefaultValueExpression(targetColumn)); - - if (newSqlType.Type==SqlType.DateTimeOffset) - getValue.Add(SqlDml.IsNotNull(tableRef[tempName]), SqlDml.DateTimeToDateTimeOffset(tableRef[tempName])); - else if (newSqlType.Type==SqlType.DateTime && providerInfo.Supports(ProviderFeatures.DateTimeOffsetEmulation)) - getValue.Add(SqlDml.IsNotNull(tableRef[tempName]), SqlDml.Cast(SqlDml.Extract(SqlDateTimeOffsetPart.DateTime, tableRef[tempName]), newSqlType)); - else - getValue.Add(SqlDml.IsNotNull(tableRef[tempName]), SqlDml.Cast(tableRef[tempName], newSqlType)); + _ = getValue.Add(SqlDml.IsNull(tableRef[tempName]), GetDefaultValueExpression(targetColumn)); + SqlExpression convertExpression; + if (IsDateTimeType(newSqlType.Type) || IsDateTimeType(column.DataType.Type)) { + convertExpression = GetDataConversion(column.DataType, newSqlType, tableRef[tempName]); + } + else { + convertExpression = SqlDml.Cast(tableRef[tempName], newSqlType); + } + _ = getValue.Add(SqlDml.IsNotNull(tableRef[tempName]), convertExpression); copyValues.Values[tableRef[originalName]] = getValue; } upgradeOutput.BreakBatch(); @@ -931,6 +932,66 @@ private void ChangeColumnType(PropertyChangeAction action) DropColumn(table.TableColumns[tempName], cleanupOutput); } + private static SqlExpression GetDataConversion(SqlValueType oldType, SqlValueType newType, SqlTableColumn sqlTableColumn) + { + var oldSqlType = oldType.Type; + var newSqlType = newType.Type; + + if (oldSqlType == SqlType.DateTime && newSqlType == SqlType.DateTimeOffset) { + return SqlDml.DateTimeToDateTimeOffset(sqlTableColumn); + } + if (oldSqlType == SqlType.DateTimeOffset && newSqlType == SqlType.DateTime) { + return SqlDml.DateTimeOffsetToDateTime(sqlTableColumn); + } +#if NET6_0_OR_GREATER + if (oldSqlType == SqlType.DateTime && newSqlType == SqlType.Date) { + return SqlDml.DateTimeToDate(sqlTableColumn); + } + if (oldSqlType == SqlType.DateTime && newSqlType == SqlType.Time) { + return SqlDml.DateTimeToTime(sqlTableColumn); + } + if (oldSqlType == SqlType.DateTimeOffset && newSqlType == SqlType.Date) { + return SqlDml.DateTimeOffsetToDate(sqlTableColumn); + } + if (oldSqlType == SqlType.DateTimeOffset && newSqlType == SqlType.Time) { + return SqlDml.DateTimeOffsetToTime(sqlTableColumn); + } + if (oldSqlType == SqlType.Date && newSqlType == SqlType.DateTime) { + return SqlDml.DateToDateTime(sqlTableColumn); + } + if (oldSqlType == SqlType.Date && newSqlType == SqlType.DateTimeOffset) { + return SqlDml.DateToDateTimeOffset(sqlTableColumn); + } + if (oldSqlType == SqlType.Time && newSqlType == SqlType.DateTime) { + return SqlDml.TimeToDateTime(sqlTableColumn); + } + if (oldSqlType == SqlType.Time && newSqlType == SqlType.DateTimeOffset) { + return SqlDml.TimeToDateTimeOffset(sqlTableColumn); + } + //Date -> Time = invalid in most cases. + //Time -> Date = invalid in most cases. + //let storage throw exception on attempt +#endif + + return SqlDml.Cast(sqlTableColumn, newType); + } + +#if NET6_0_OR_GREATER + private static bool IsDateTimeType(in SqlType type) + { + return type == SqlType.DateTime + || type == SqlType.DateTimeOffset + || type == SqlType.Date + || type == SqlType.Time; + } +#else + private static bool IsDateTimeType(in SqlType type) + { + return type == SqlType.DateTime + || type == SqlType.DateTimeOffset; + } +#endif + private Table CreateTable(TableInfo tableInfo) { var node = resolver.Resolve(sqlModel, tableInfo.Name); diff --git a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs index 7656b99a1d..92631fb1a5 100644 --- a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs @@ -57,6 +57,13 @@ internal static class WellKnownTypes public static readonly Type NullableDateTime = typeof(DateTime?); public static readonly Type DateTimeOffset = typeof(DateTimeOffset); public static readonly Type NullableDateTimeOffset = typeof(DateTimeOffset?); +#if NET6_0_OR_GREATER + public static readonly Type DateOnly = typeof(DateOnly); + public static readonly Type TimeOnly = typeof(TimeOnly); + public static readonly Type NullableDateOnly = typeof(DateOnly?); + public static readonly Type NullableTimeOnly = typeof(TimeOnly?); +#endif + public static readonly Type Guid = typeof(Guid); public static readonly Type NullableGuid = typeof(Guid?); diff --git a/Orm/Xtensive.Orm/Sql/Compiler/Internals/PostCompiler.cs b/Orm/Xtensive.Orm/Sql/Compiler/Internals/PostCompiler.cs index 81db08cedf..84a4b1f6ee 100644 --- a/Orm/Xtensive.Orm/Sql/Compiler/Internals/PostCompiler.cs +++ b/Orm/Xtensive.Orm/Sql/Compiler/Internals/PostCompiler.cs @@ -40,10 +40,7 @@ public override void Visit(TextNode node) public override void Visit(VariantNode node) { - if (configuration.AlternativeBranches.Contains(node.Id)) - VisitNodes(node.Alternative); - else - VisitNodes(node.Main); + VisitNodes(configuration.AlternativeBranches.Contains(node.Id) ? node.Alternative : node.Main); } public override void Visit(PlaceholderNode node) diff --git a/Orm/Xtensive.Orm/Sql/Compiler/SqlCompiler.cs b/Orm/Xtensive.Orm/Sql/Compiler/SqlCompiler.cs index 295ce593bf..f4bda01366 100644 --- a/Orm/Xtensive.Orm/Sql/Compiler/SqlCompiler.cs +++ b/Orm/Xtensive.Orm/Sql/Compiler/SqlCompiler.cs @@ -1184,7 +1184,7 @@ public virtual void Visit(SqlFunctionCall node) { using (context.EnterScope(node)) { AppendTranslatedEntry(node); - + if (node.Arguments.Count > 0) { using (context.EnterCollectionScope()) { var argumentPosition = 0; @@ -2083,12 +2083,20 @@ public virtual void Visit(SqlExtract node) { using (context.EnterScope(node)) { AppendTranslatedEntry(node); - if (node.DateTimePart!= SqlDateTimePart.Nothing) { + if (node.IsDateTimePart) { translator.Translate(context.Output, node.DateTimePart); } - else if (node.IntervalPart!= SqlIntervalPart.Nothing) { + else if (node.IsIntervalPart) { translator.Translate(context.Output, node.IntervalPart); } +#if NET6_0_OR_GREATER + else if (node.IsDatePart) { + translator.Translate(context.Output, node.DatePart); + } + else if (node.IsTimePart) { + translator.Translate(context.Output, node.TimePart); + } +#endif else { translator.Translate(context.Output, node.DateTimeOffsetPart); } diff --git a/Orm/Xtensive.Orm/Sql/Compiler/SqlTranslator.cs b/Orm/Xtensive.Orm/Sql/Compiler/SqlTranslator.cs index 2908b3fcd3..a2e7ae8570 100644 --- a/Orm/Xtensive.Orm/Sql/Compiler/SqlTranslator.cs +++ b/Orm/Xtensive.Orm/Sql/Compiler/SqlTranslator.cs @@ -72,12 +72,27 @@ public abstract class SqlTranslator : SqlDriverBound /// public abstract string DateTimeFormatString { get; } +#if NET6_0_OR_GREATER + /// + /// Gets the format string. + /// See for details + /// + public virtual string DateOnlyFormatString => throw new NotImplementedException(); +#endif + /// /// Gets the time span format string. /// See for details. /// public abstract string TimeSpanFormatString { get; } +#if NET6_0_OR_GREATER + /// + /// Gets the format string. + /// + public virtual string TimeOnlyFormatString => throw new NotImplementedException(); +#endif + /// /// Gets the parameter prefix. /// @@ -1518,6 +1533,14 @@ public virtual void Translate(SqlCompilerContext context, object literalValue) case Guid: case byte[]: throw new NotSupportedException(string.Format(Strings.ExTranslationOfLiteralOfTypeXIsNotSupported, literalType.GetShortName())); +#if NET6_0_OR_GREATER + case DateOnly dateOnly: + output.Append(dateOnly.ToString(DateOnlyFormatString, DateTimeFormat)); + break; + case TimeOnly timeOnly: + output.Append(timeOnly.ToString(TimeOnlyFormatString, DateTimeFormat)); + break; +#endif default: _ = output.Append(literalValue.ToString()); break; @@ -2255,6 +2278,42 @@ public virtual void Translate(IOutput output, SqlDateTimePart dateTimePart) }); } +#if NET6_0_OR_GREATER + /// + /// Translates writes the result to the . + /// + /// The output to write to. + /// Enum value to translate. + public virtual void Translate(IOutput output, SqlDatePart datePart) + { + _ = output.Append(datePart switch { + SqlDatePart.Year => "YEAR", + SqlDatePart.Month => "MONTH", + SqlDatePart.Day => "DAY", + SqlDatePart.DayOfYear => "DAYOFYEAR", + SqlDatePart.DayOfWeek => "DAYOFWEEK", + _ => throw new ArgumentOutOfRangeException(nameof(datePart)) + }); + } + + /// + /// Translates writes the result to the . + /// + /// The output to write to. + /// Enum value to translate. + public virtual void Translate(IOutput output, SqlTimePart timePart) + { + _ = output.Append(timePart switch { + SqlTimePart.Hour => "HOUR", + SqlTimePart.Minute => "MINUTE", + SqlTimePart.Second => "SECOND", + SqlTimePart.Millisecond => "MILLISECOND", + SqlTimePart.Nanosecond => "NANOSECOND", + _ => throw new ArgumentOutOfRangeException(nameof(timePart)) + }); + } +#endif + /// /// Translates and writes result to the . /// diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs index b0be887cab..3f48159789 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs @@ -204,6 +204,18 @@ public static implicit operator SqlExpression(DateTime value) { return new SqlLiteral(value); } +#if NET6_0_OR_GREATER + + public static implicit operator SqlExpression(DateOnly value) + { + return new SqlLiteral(value); + } + + public static implicit operator SqlExpression(TimeOnly value) + { + return new SqlLiteral(value); + } +#endif public static implicit operator SqlExpression(DateTimeOffset value) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs index 1f08db33d9..972995fc38 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs @@ -1,40 +1,85 @@ -// 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.24 using System; using System.Diagnostics; +using System.Reflection; using Xtensive.Core; namespace Xtensive.Sql.Dml { public class SqlExtract : SqlExpression { - public SqlDateTimePart DateTimePart { get; private set; } - public SqlDateTimeOffsetPart DateTimeOffsetPart { get; private set; } - public SqlIntervalPart IntervalPart { get; private set; } + private const int TimeTypeId = 1; + private const int DateTypeId = 2; + private const int DateTimeTypeId = 3; + private const int DateTimeOffsetTypeId = 4; + private const int IntervalTypeId = 5; + + private SqlDateTimeOffsetPart internalValue; + private int typeMarker; + private bool typeHasTime; + + public SqlDateTimePart DateTimePart => + typeMarker == DateTimeTypeId ? internalValue.ToDateTimePartFast() : SqlDateTimePart.Nothing; +#if NET6_0_OR_GREATER + + public SqlDatePart DatePart => + typeMarker == DateTypeId ? internalValue.ToDatePartFast() : SqlDatePart.Nothing; + + public SqlTimePart TimePart => + typeMarker == TimeTypeId ? internalValue.ToTimePartFast() : SqlTimePart.Nothing; +#endif + + public SqlDateTimeOffsetPart DateTimeOffsetPart => + typeMarker == DateTimeOffsetTypeId ? internalValue : SqlDateTimeOffsetPart.Nothing; + + public SqlIntervalPart IntervalPart => + typeMarker == IntervalTypeId ? internalValue.ToIntervalPartFast(): SqlIntervalPart.Nothing; public SqlExpression Operand { get; private set; } + public bool IsSecondExtraction => + typeHasTime && internalValue == SqlDateTimeOffsetPart.Second; + public bool IsMillisecondExtraction => + typeHasTime && internalValue == SqlDateTimeOffsetPart.Millisecond; + + public bool IsDateTimeOffsetPart => typeMarker == DateTimeOffsetTypeId; + + public bool IsDateTimePart => typeMarker == DateTimeTypeId; +#if NET6_0_OR_GREATER + + public bool IsDatePart => typeMarker == DateTypeId; + + public bool IsTimePart => typeMarker == TimeTypeId; +#endif + + public bool IsIntervalPart => typeMarker == IntervalTypeId; + public override void ReplaceWith(SqlExpression expression) { var replacingExpression = ArgumentValidator.EnsureArgumentIs(expression); - DateTimePart = replacingExpression.DateTimePart; - DateTimeOffsetPart = replacingExpression.DateTimeOffsetPart; - IntervalPart = replacingExpression.IntervalPart; + internalValue = replacingExpression.internalValue; + typeMarker = replacingExpression.typeMarker; + typeHasTime = replacingExpression.typeHasTime; + //DateTimePart = replacingExpression.DateTimePart; + //DateTimeOffsetPart = replacingExpression.DateTimeOffsetPart; + //IntervalPart = replacingExpression.IntervalPart; Operand = replacingExpression.Operand; } internal override object Clone(SqlNodeCloneContext context) => context.NodeMapping.TryGetValue(this, out var clone) ? clone - : context.NodeMapping[this] = DateTimePart!=SqlDateTimePart.Nothing - ? new SqlExtract(DateTimePart, (SqlExpression) Operand.Clone(context)) - : IntervalPart!=SqlIntervalPart.Nothing - ? new SqlExtract(IntervalPart, (SqlExpression) Operand.Clone(context)) - : new SqlExtract(DateTimeOffsetPart, (SqlExpression) Operand.Clone(context)); + : context.NodeMapping[this] = new SqlExtract(internalValue, typeMarker, (SqlExpression)Operand.Clone(context)); + //DateTimePart!=SqlDateTimePart.Nothing + //? new SqlExtract(DateTimePart, (SqlExpression) Operand.Clone(context)) + //: IntervalPart!=SqlIntervalPart.Nothing + // ? new SqlExtract(IntervalPart, (SqlExpression) Operand.Clone(context)) + // : new SqlExtract(DateTimeOffsetPart, (SqlExpression) Operand.Clone(context)); public override void AcceptVisitor(ISqlVisitor visitor) { @@ -46,27 +91,64 @@ public override void AcceptVisitor(ISqlVisitor visitor) internal SqlExtract(SqlDateTimePart dateTimePart, SqlExpression operand) : base(SqlNodeType.Extract) { - DateTimePart = dateTimePart; - DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing; - IntervalPart = SqlIntervalPart.Nothing; + internalValue = dateTimePart.ToDtoPartFast(); + typeMarker = DateTimeTypeId; + typeHasTime = true; + + //DateTimePart = dateTimePart; + //DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing; + //IntervalPart = SqlIntervalPart.Nothing; Operand = operand; } internal SqlExtract(SqlIntervalPart intervalPart, SqlExpression operand) : base(SqlNodeType.Extract) { - DateTimePart = SqlDateTimePart.Nothing; - DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing; - IntervalPart = intervalPart; + internalValue = intervalPart.ToDtoPartFast(); + typeMarker = IntervalTypeId; + typeHasTime = true; + + //DateTimePart = SqlDateTimePart.Nothing; + //DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing; + //IntervalPart = intervalPart; + Operand = operand; + } + + internal SqlExtract(SqlDateTimeOffsetPart dateTimeOffsetPart, SqlExpression operand) + : base(SqlNodeType.Extract) + { + internalValue = dateTimeOffsetPart; + typeMarker = DateTimeOffsetTypeId; + typeHasTime = true; + Operand = operand; + } +#if NET6_0_OR_GREATER + + internal SqlExtract(SqlDatePart datePart, SqlExpression operand) + : base(SqlNodeType.Extract) + { + internalValue = datePart.ToDtoPartFast(); + typeMarker = DateTypeId; + typeHasTime = false; Operand = operand; } - public SqlExtract(SqlDateTimeOffsetPart dateTimeOffsetPart, SqlExpression operand) + internal SqlExtract(SqlTimePart timePart, SqlExpression operand) : base(SqlNodeType.Extract) { - DateTimePart = SqlDateTimePart.Nothing; - IntervalPart = SqlIntervalPart.Nothing; - DateTimeOffsetPart = dateTimeOffsetPart; + internalValue = timePart.ToDtoPartFast(); + typeMarker = TimeTypeId; + typeHasTime = true; + Operand = operand; + } +#endif + + private SqlExtract(SqlDateTimeOffsetPart internalValue, int typeMarker, SqlExpression operand) + :base(SqlNodeType.Extract) + { + this.internalValue = internalValue; + this.typeMarker = typeMarker; + typeHasTime = typeMarker == DateTimeTypeId || typeMarker == DateTimeOffsetTypeId || typeMarker == TimeTypeId || typeMarker == IntervalTypeId; Operand = operand; } } diff --git a/Orm/Xtensive.Orm/Sql/Dml/Extensions.cs b/Orm/Xtensive.Orm/Sql/Dml/Extensions.cs index bc574ede13..13a50393ca 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Extensions.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Extensions.cs @@ -77,5 +77,20 @@ public static string ToString(this SqlLockType lockType, bool humanReadable) result.Append("|Throw exception if locked"); return result.ToString(); } + + // See the description in the Parts file. Be careful. +#if NET6_0_OR_GREATER + internal static SqlDateTimeOffsetPart ToDtoPartFast(this SqlDatePart datePart) => (SqlDateTimeOffsetPart) (int) datePart; + internal static SqlDateTimeOffsetPart ToDtoPartFast(this SqlTimePart datePart) => (SqlDateTimeOffsetPart) (int) datePart; +#endif + internal static SqlDateTimeOffsetPart ToDtoPartFast(this SqlDateTimePart datePart) => (SqlDateTimeOffsetPart) (int) datePart; + internal static SqlDateTimeOffsetPart ToDtoPartFast(this SqlIntervalPart datePart) => (SqlDateTimeOffsetPart) (int) datePart; + +#if NET6_0_OR_GREATER + internal static SqlDatePart ToDatePartFast(this SqlDateTimeOffsetPart dtoPart) => (SqlDatePart)(int) dtoPart; + internal static SqlTimePart ToTimePartFast(this SqlDateTimeOffsetPart dtoPart) => (SqlTimePart)(int) dtoPart; +#endif + internal static SqlDateTimePart ToDateTimePartFast(this SqlDateTimeOffsetPart dtoPart) => (SqlDateTimePart) (int) dtoPart; + internal static SqlIntervalPart ToIntervalPartFast(this SqlDateTimeOffsetPart dtoPart) => (SqlIntervalPart)(int) dtoPart; } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlDatePart.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlDatePart.cs new file mode 100644 index 0000000000..0dfcf351fa --- /dev/null +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlDatePart.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; + +namespace Xtensive.Sql.Dml +{ + // IMPORTANT DateTime related enums are similar to each other to a certain degree + // and contain some subset of parts listed below. + // To be able to fast-convert between enums + // the same parts of enums have the same assigned value keep this pattern. + // Newer parts, if any should be added to this comment and assighed accordingly + // Year = 0, + // Month = 1, + // Day = 2, + // Hour = 3, + // Minute = 4, + // Second = 5, + // Millisecond = 6, + // Nanosecond = 7, + // TimeZoneHour = 8, + // TimeZoneMinute = 9, + // DayOfYear = 10, + // DayOfWeek = 11, + // Date = 12, + // DateTime = 13, + // LocalDateTime = 14, + // UtcDateTime = 15, + // Offset = 16, + // Nothing = 25, + + [Serializable] + public enum SqlDatePart + { + Year = 0, + Month = 1, + Day = 2, + DayOfYear = 10, + DayOfWeek = 11, + Nothing = 25, + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimeOffsetPart.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimeOffsetPart.cs index ef07fee4f7..c1d0b61a9c 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimeOffsetPart.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimeOffsetPart.cs @@ -1,31 +1,55 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2014-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; namespace Xtensive.Sql.Dml { + // IMPORTANT DateTime related enums are similar to each other to a certain degree + // and contain some subset of parts listed below. + // To be able to fast-convert between enums + // the same parts of enums have the same assigned value keep this pattern. + // Newer parts, if any should be added to this comment and assighed accordingly + // Year = 0, + // Month = 1, + // Day = 2, + // Hour = 3, + // Minute = 4, + // Second = 5, + // Millisecond = 6, + // Nanosecond = 7, + // TimeZoneHour = 8, + // TimeZoneMinute = 9, + // DayOfYear = 10, + // DayOfWeek = 11, + // Date = 12, + // DateTime = 13, + // LocalDateTime = 14, + // UtcDateTime = 15, + // Offset = 16, + // Nothing = 25, + [Serializable] public enum SqlDateTimeOffsetPart { - Year, - Month, - Day, - Hour, - Minute, - Second, - Millisecond, - Nanosecond, - TimeZoneHour, - TimeZoneMinute, - DayOfYear, - DayOfWeek, - Date, - DateTime, - LocalDateTime, - UtcDateTime, - Offset, - Nothing, + Year = 0, + Month = 1, + Day = 2, + Hour = 3, + Minute = 4, + Second = 5, + Millisecond = 6, + Nanosecond = 7, + TimeZoneHour = 8, + TimeZoneMinute = 9, + DayOfYear = 10, + DayOfWeek = 11, + Date = 12, + DateTime = 13, + LocalDateTime = 14, + UtcDateTime = 15, + Offset = 16, + Nothing = 25, } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimePart.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimePart.cs index 6650b64122..e431858e08 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimePart.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlDateTimePart.cs @@ -6,21 +6,45 @@ namespace Xtensive.Sql.Dml { + // IMPORTANT DateTime related enums are similar to each other to a certain degree + // and contain some subset of parts listed below. + // To be able to fast-convert between enums + // the same parts of enums have the same assigned value keep this pattern. + // Newer parts, if any should be added to this comment and assighed accordingly + // Year = 0, + // Month = 1, + // Day = 2, + // Hour = 3, + // Minute = 4, + // Second = 5, + // Millisecond = 6, + // Nanosecond = 7, + // TimeZoneHour = 8, + // TimeZoneMinute = 9, + // DayOfYear = 10, + // DayOfWeek = 11, + // Date = 12, + // DateTime = 13, + // LocalDateTime = 14, + // UtcDateTime = 15, + // Offset = 16, + // Nothing = 25, + [Serializable] public enum SqlDateTimePart { - Year, - Month, - Day, - Hour, - Minute, - Second, - Millisecond, - Nanosecond, - TimeZoneHour, - TimeZoneMinute, - DayOfYear, - DayOfWeek, - Nothing, + Year = 0, + Month = 1, + Day = 2, + Hour = 3, + Minute = 4, + Second = 5, + Millisecond = 6, + Nanosecond = 7, + TimeZoneHour = 8, + TimeZoneMinute = 9, + DayOfYear = 10, + DayOfWeek = 11, + Nothing = 25, } } diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs index a5d4701383..05a696c0d0 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs @@ -60,12 +60,31 @@ public enum SqlFunctionType // date time / interval functions // not ansi sql but our cross-server solution +#if NET6_0_OR_GREATER + DateConstruct, + DateAddYears, + DateAddMonths, + DateAddDays, + DateToString, + DateToDateTime, + DateToDateTimeOffset, + TimeConstruct, + TimeAddHours, + TimeAddMinutes, + TimeToString, + TimeToDateTime, + TimeToDateTimeOffset, +#endif DateTimeConstruct, DateTimeAddYears, DateTimeAddMonths, DateTimeTruncate, DateTimeToStringIso, +#if NET6_0_OR_GREATER + DateTimeToTime, + DateTimeToDate, +#endif IntervalConstruct, IntervalToMilliseconds, IntervalToNanoseconds, @@ -80,6 +99,11 @@ public enum SqlFunctionType DateTimeOffsetToLocalTime, DateTimeOffsetToUtcTime, DateTimeToDateTimeOffset, + DateTimeOffsetToDateTime, +#if NET6_0_OR_GREATER + DateTimeOffsetToTime, + DateTimeOffsetToDate, +#endif // .NET like rounding functions @@ -87,5 +111,6 @@ public enum SqlFunctionType RoundDecimalToZero, RoundDoubleToEven, RoundDoubleToZero, + //!!! max value is used for array size } } diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlIntervalPart.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlIntervalPart.cs index f5fcfe39f8..45e6a7dbb4 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlIntervalPart.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlIntervalPart.cs @@ -1,6 +1,6 @@ -// 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.02.25 @@ -8,15 +8,39 @@ namespace Xtensive.Sql.Dml { + // IMPORTANT DateTime related enums are similar to each other to a certain degree + // and contain some subset of parts listed below. + // To be able to fast-convert between enums + // the same parts of enums have the same assigned value keep this pattern. + // Newer parts, if any should be added to this comment and assighed accordingly + // Year = 0, + // Month = 1, + // Day = 2, + // Hour = 3, + // Minute = 4, + // Second = 5, + // Millisecond = 6, + // Nanosecond = 7, + // TimeZoneHour = 8, + // TimeZoneMinute = 9, + // DayOfYear = 10, + // DayOfWeek = 11, + // Date = 12, + // DateTime = 13, + // LocalDateTime = 14, + // UtcDateTime = 15, + // Offset = 16, + // Nothing = 25, + [Serializable] public enum SqlIntervalPart { - Day = 0, - Hour = 1, - Minute = 2, - Second = 3, - Millisecond = 4, - Nanosecond = 5, - Nothing = 10, + Day = 2, + Hour = 3, + Minute = 4, + Second = 5, + Millisecond = 6, + Nanosecond = 7, + Nothing = 25, } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlTimePart.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlTimePart.cs new file mode 100644 index 0000000000..fb861c59e6 --- /dev/null +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlTimePart.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +#if NET6_0_OR_GREATER + +using System; + +namespace Xtensive.Sql.Dml +{ + // IMPORTANT DateTime related enums are similar to each other to a certain degree + // and contain some subset of parts listed below. + // To be able to fast-convert between enums + // the same parts of enums have the same assigned value keep this pattern. + // Newer parts, if any should be added to this comment and assighed accordingly + // Year = 0, + // Month = 1, + // Day = 2, + // Hour = 3, + // Minute = 4, + // Second = 5, + // Millisecond = 6, + // Nanosecond = 7, + // TimeZoneHour = 8, + // TimeZoneMinute = 9, + // DayOfYear = 10, + // DayOfWeek = 11, + // Date = 12, + // DateTime = 13, + // LocalDateTime = 14, + // UtcDateTime = 15, + // Offset = 16, + // Nothing = 25, + [Serializable] + public enum SqlTimePart + { + Hour = 3, + Minute = 4, + Second = 5, + Millisecond = 6, + Nanosecond = 7, + Nothing = 25, + } +} +#endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Info/DataTypeCollection.cs b/Orm/Xtensive.Orm/Sql/Info/DataTypeCollection.cs index 8fd6935268..cc51c53382 100644 --- a/Orm/Xtensive.Orm/Sql/Info/DataTypeCollection.cs +++ b/Orm/Xtensive.Orm/Sql/Info/DataTypeCollection.cs @@ -27,8 +27,7 @@ public class DataTypeCollection : LockableBase, IEnumerable /// The instance. public DataTypeInfo this[string nativeType] { - get - { + get { DataTypeInfo result; nativeTypes.TryGetValue(nativeType, out result); return result; @@ -41,8 +40,7 @@ public DataTypeInfo this[string nativeType] /// The instance. public DataTypeInfo this[SqlType sqlType] { - get - { + get { DataTypeInfo result; sqlTypes.TryGetValue(sqlType, out result); return result; @@ -114,7 +112,7 @@ public void Add(SqlType sqlType, DataTypeInfo dataTypeInfo) public DataTypeInfo Decimal { get; set; } /// - /// Floating point number data from �3.40E + 38 through 3.40E + 38. + /// Floating point number data from –3.40E + 38 through 3.40E + 38. /// Storage size is 4 bytes. /// public DataTypeInfo Float { get; set; } @@ -145,6 +143,19 @@ public void Add(SqlType sqlType, DataTypeInfo dataTypeInfo) /// A representation of the interval data type. /// public DataTypeInfo Interval { get; set; } +#if NET6_0_OR_GREATER + + /// + /// Date data from January 1,1 A.D. through December 31, 9999 A.D. + /// Can have various ranges in different RDBMSs. + /// + public DataTypeInfo DateOnly { get; set; } + + /// + /// Time data. Values mignt be rounded to some fractions of a second. + /// + public DataTypeInfo TimeOnly { get; set; } +#endif /// /// Fixed-length Unicode character data of n characters. @@ -197,7 +208,7 @@ public override void Lock(bool recursive) base.Lock(recursive); foreach (DataTypeInfo item in this) { - if (item==null) + if (item == null) continue; sqlTypes[item.Type] = item; foreach (var type in item.NativeTypes) @@ -238,6 +249,10 @@ IEnumerator IEnumerable.GetEnumerator() yield return VarBinaryMax; yield return Guid; yield return Interval; +#if NET6_0_OR_GREATER + yield return DateOnly; + yield return TimeOnly; +#endif yield break; } diff --git a/Orm/Xtensive.Orm/Sql/Info/ValueRange.Types.cs b/Orm/Xtensive.Orm/Sql/Info/ValueRange.Types.cs index e8c7569b24..7d0e896580 100644 --- a/Orm/Xtensive.Orm/Sql/Info/ValueRange.Types.cs +++ b/Orm/Xtensive.Orm/Sql/Info/ValueRange.Types.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2003-2022 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.21 @@ -78,5 +78,15 @@ public partial class ValueRange /// public static readonly ValueRange TimeSpan = new ValueRange(System.TimeSpan.MinValue, System.TimeSpan.MaxValue); +#if NET6_0_OR_GREATER + /// + /// Standard value range for + /// + public static readonly ValueRange DateOnly = new(System.DateOnly.MinValue, System.DateOnly.MaxValue); + /// + /// Standard value range for + /// + public static readonly ValueRange TimeOnly = new(System.TimeOnly.MinValue, System.TimeOnly.MaxValue); +#endif } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Internals/SqlValidator.cs b/Orm/Xtensive.Orm/Sql/Internals/SqlValidator.cs index e7d3891f93..e22c0820d7 100644 --- a/Orm/Xtensive.Orm/Sql/Internals/SqlValidator.cs +++ b/Orm/Xtensive.Orm/Sql/Internals/SqlValidator.cs @@ -29,6 +29,10 @@ internal static class SqlValidator WellKnownTypes.Double, WellKnownTypes.Decimal, WellKnownTypes.DateTime, +#if NET6_0_OR_GREATER + WellKnownTypes.DateOnly, + WellKnownTypes.TimeOnly, +#endif WellKnownTypes.DateTimeOffset, WellKnownTypes.TimeSpan, WellKnownTypes.ByteArray, @@ -176,6 +180,10 @@ public static bool IsArithmeticExpression(SqlExpression node) case SqlNodeType.DateTimeOffsetMinusInterval: case SqlNodeType.DateTimeOffsetPlusInterval: case SqlNodeType.DateTimeOffsetMinusDateTimeOffset: +#if NET6_0_OR_GREATER + case SqlNodeType.TimePlusInterval: + case SqlNodeType.TimeMinusTime: +#endif return true; case SqlNodeType.Variant: var variant = (SqlVariant) node; diff --git a/Orm/Xtensive.Orm/Sql/SqlDml.cs b/Orm/Xtensive.Orm/Sql/SqlDml.cs index 39310bf55c..edd801eca3 100644 --- a/Orm/Xtensive.Orm/Sql/SqlDml.cs +++ b/Orm/Xtensive.Orm/Sql/SqlDml.cs @@ -494,7 +494,7 @@ public static SqlBinary LessThanOrEquals(SqlExpression left, SqlExpression right #endregion - # region DateTime functions + # region Date and/or Time functions public static SqlFunctionCall CurrentDate() { @@ -539,17 +539,41 @@ public static SqlExtract Extract(SqlDateTimePart part, SqlExpression operand) { ArgumentValidator.EnsureArgumentNotNull(operand, "operand"); SqlValidator.EnsureIsArithmeticExpression(operand); - if (part==SqlDateTimePart.Nothing) - throw new ArgumentException(); + if (part == SqlDateTimePart.Nothing) { + throw new ArgumentException(string.Format("Unable to extract {0} part", SqlDateTimePart.Nothing.ToString())); + } return new SqlExtract(part, operand); } - +#if NET6_0_OR_GREATER + + public static SqlExtract Extract(SqlDatePart part, SqlExpression operand) + { + ArgumentNullException.ThrowIfNull(operand, nameof(operand)); + SqlValidator.EnsureIsArithmeticExpression(operand); + if (part == SqlDatePart.Nothing) { + throw new ArgumentException(string.Format("Unable to extract {0} part", SqlDatePart.Nothing.ToString())); + } + return new SqlExtract(part, operand); + } + + public static SqlExtract Extract(SqlTimePart part, SqlExpression operand) + { + ArgumentNullException.ThrowIfNull(operand, nameof(operand)); + SqlValidator.EnsureIsArithmeticExpression(operand); + if (part == SqlTimePart.Nothing) { + throw new ArgumentException(string.Format("Unable to extract {0} part", SqlTimePart.Nothing.ToString())); + } + return new SqlExtract(part, operand); + } +#endif + public static SqlExtract Extract(SqlIntervalPart part, SqlExpression operand) { ArgumentValidator.EnsureArgumentNotNull(operand, "operand"); SqlValidator.EnsureIsArithmeticExpression(operand); - if (part==SqlIntervalPart.Nothing) - throw new ArgumentException(); + if (part == SqlIntervalPart.Nothing) { + throw new ArgumentException(string.Format("Unable to extract {0} part", SqlIntervalPart.Nothing.ToString())); + } return new SqlExtract(part, operand); } @@ -563,6 +587,38 @@ public static SqlFunctionCall DateTimeConstruct(SqlExpression year, SqlExpressio SqlValidator.EnsureIsArithmeticExpression(day); return new SqlFunctionCall(SqlFunctionType.DateTimeConstruct, year, month, day); } +#if NET6_0_OR_GREATER + + public static SqlFunctionCall DateConstruct(SqlExpression year, SqlExpression month, SqlExpression day) + { + ArgumentNullException.ThrowIfNull(year); + ArgumentNullException.ThrowIfNull(month); + ArgumentNullException.ThrowIfNull(day); + SqlValidator.EnsureIsArithmeticExpression(year); + SqlValidator.EnsureIsArithmeticExpression(month); + SqlValidator.EnsureIsArithmeticExpression(day); + return new SqlFunctionCall(SqlFunctionType.DateConstruct, year, month, day); + } + + public static SqlFunctionCall TimeConstruct(SqlExpression hour, + SqlExpression minute, + SqlExpression second, + SqlExpression millisecond) + { + ArgumentNullException.ThrowIfNull(hour); + ArgumentNullException.ThrowIfNull(minute); + ArgumentNullException.ThrowIfNull(second); + ArgumentNullException.ThrowIfNull(millisecond); + SqlValidator.EnsureIsArithmeticExpression(hour); + SqlValidator.EnsureIsArithmeticExpression(minute); + 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); + } +#endif public static SqlBinary DateTimePlusInterval(SqlExpression left, SqlExpression right) { @@ -570,6 +626,22 @@ public static SqlBinary DateTimePlusInterval(SqlExpression left, SqlExpression r ArgumentValidator.EnsureArgumentNotNull(right, "right"); return new SqlBinary(SqlNodeType.DateTimePlusInterval, left, right); } +#if NET6_0_OR_GREATER + + public static SqlBinary TimePlusInterval(SqlExpression left, SqlExpression right) + { + ArgumentValidator.EnsureArgumentNotNull(left, "left"); + ArgumentValidator.EnsureArgumentNotNull(right, "right"); + return new SqlBinary(SqlNodeType.TimePlusInterval, left, right); + } + + public static SqlBinary TimeMinusTime(SqlExpression left, SqlExpression right) + { + ArgumentValidator.EnsureArgumentNotNull(left, "left"); + ArgumentValidator.EnsureArgumentNotNull(right, "right"); + return new SqlBinary(SqlNodeType.TimeMinusTime, left, right); + } +#endif public static SqlBinary DateTimeMinusInterval(SqlExpression left, SqlExpression right) { @@ -599,6 +671,91 @@ public static SqlFunctionCall DateTimeAddMonths(SqlExpression source, SqlExpress return new SqlFunctionCall(SqlFunctionType.DateTimeAddMonths, source, months); } +#if NET6_0_OR_GREATER + public static SqlFunctionCall DateTimeToTime(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.DateTimeToTime, expression); + } + + public static SqlFunctionCall DateAddYears(SqlExpression source, SqlExpression years) + { + ArgumentValidator.EnsureArgumentNotNull(source, "source"); + ArgumentValidator.EnsureArgumentNotNull(years, "years"); + return new SqlFunctionCall(SqlFunctionType.DateAddYears, source, years); + } + + public static SqlFunctionCall DateAddMonths(SqlExpression source, SqlExpression months) + { + ArgumentValidator.EnsureArgumentNotNull(source, "source"); + ArgumentValidator.EnsureArgumentNotNull(months, "months"); + return new SqlFunctionCall(SqlFunctionType.DateAddMonths, source, months); + } + + public static SqlFunctionCall DateAddDays(SqlExpression source, SqlExpression days) + { + ArgumentValidator.EnsureArgumentNotNull(source, "source"); + ArgumentValidator.EnsureArgumentNotNull(days, "days"); + return new SqlFunctionCall(SqlFunctionType.DateAddDays, source, days); + } + + public static SqlFunctionCall DateToString(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.DateToString, expression); + } + + public static SqlFunctionCall DateToDateTime(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.DateToDateTime, expression); + } + + public static SqlFunctionCall DateTimeToDate(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.DateTimeToDate, expression); + } + + public static SqlFunctionCall DateToDateTimeOffset(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.DateToDateTimeOffset, expression); + } + + public static SqlFunctionCall TimeAddHours(SqlExpression source, SqlExpression hours) + { + ArgumentValidator.EnsureArgumentNotNull(source, "source"); + ArgumentValidator.EnsureArgumentNotNull(hours, "hours"); + return new SqlFunctionCall(SqlFunctionType.TimeAddHours, source, hours); + } + + public static SqlFunctionCall TimeAddMinutes(SqlExpression source, SqlExpression minutes) + { + ArgumentValidator.EnsureArgumentNotNull(source, "source"); + ArgumentValidator.EnsureArgumentNotNull(minutes, "minutes"); + return new SqlFunctionCall(SqlFunctionType.TimeAddMinutes, source, minutes); + } + + public static SqlFunctionCall TimeToString(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.TimeToString, expression); + } + + public static SqlFunctionCall TimeToDateTime(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.TimeToDateTime, expression); + } + + public static SqlFunctionCall TimeToDateTimeOffset(SqlExpression expression) + { + ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + return new SqlFunctionCall(SqlFunctionType.TimeToDateTimeOffset, expression); + } +#endif + public static SqlFunctionCall DateTimeToStringIso(SqlExpression expression) { ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); @@ -726,6 +883,26 @@ public static SqlFunctionCall DateTimeToDateTimeOffset(SqlExpression dateTime) return new SqlFunctionCall(SqlFunctionType.DateTimeToDateTimeOffset, dateTime); } + public static SqlFunctionCall DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) + { + ArgumentValidator.EnsureArgumentNotNull(dateTimeOffset, nameof(dateTimeOffset)); + return new SqlFunctionCall(SqlFunctionType.DateTimeOffsetToDateTime, dateTimeOffset); + } +#if NET6_0_OR_GREATER //DO_DATEONLY + + public static SqlFunctionCall DateTimeOffsetToTime(SqlExpression dateTimeOffset) + { + ArgumentValidator.EnsureArgumentNotNull(dateTimeOffset, nameof(dateTimeOffset)); + return new SqlFunctionCall(SqlFunctionType.DateTimeOffsetToTime, dateTimeOffset); + } + + public static SqlFunctionCall DateTimeOffsetToDate(SqlExpression dateTimeOffset) + { + ArgumentValidator.EnsureArgumentNotNull(dateTimeOffset, nameof(dateTimeOffset)); + return new SqlFunctionCall(SqlFunctionType.DateTimeOffsetToDate, dateTimeOffset); + } +#endif + #endregion #region FunctionCall diff --git a/Orm/Xtensive.Orm/Sql/SqlDriver.cs b/Orm/Xtensive.Orm/Sql/SqlDriver.cs index 107319c921..4417ba622b 100644 --- a/Orm/Xtensive.Orm/Sql/SqlDriver.cs +++ b/Orm/Xtensive.Orm/Sql/SqlDriver.cs @@ -421,6 +421,10 @@ private static void RegisterStandardMappings(TypeMappingRegistryBuilder builder) builder.Add(WellKnownTypes.TimeSpan, mapper.ReadTimeSpan, mapper.BindTimeSpan, mapper.MapTimeSpan); builder.Add(WellKnownTypes.Guid, mapper.ReadGuid, mapper.BindGuid, mapper.MapGuid); builder.Add(WellKnownTypes.ByteArray, mapper.ReadByteArray, mapper.BindByteArray, mapper.MapByteArray); +#if NET6_0_OR_GREATER + builder.Add(WellKnownTypes.DateOnly, mapper.ReadDateOnly, mapper.BindDateOnly, mapper.MapDateOnly); + builder.Add(WellKnownTypes.TimeOnly, mapper.ReadTimeOnly, mapper.BindTimeOnly, mapper.MapTimeOnly); +#endif } private static void RegisterStandardReverseMappings(TypeMappingRegistryBuilder builder) @@ -446,6 +450,10 @@ private static void RegisterStandardReverseMappings(TypeMappingRegistryBuilder b builder.AddReverse(SqlType.VarBinary, WellKnownTypes.ByteArray); builder.AddReverse(SqlType.VarBinaryMax, WellKnownTypes.ByteArray); builder.AddReverse(SqlType.Guid, WellKnownTypes.Guid); +#if NET6_0_OR_GREATER + builder.AddReverse(SqlType.Date, WellKnownTypes.DateOnly); + builder.AddReverse(SqlType.Time, WellKnownTypes.TimeOnly); +#endif } private Extractor BuildExtractor(SqlConnection connection) diff --git a/Orm/Xtensive.Orm/Sql/SqlNodeType.cs b/Orm/Xtensive.Orm/Sql/SqlNodeType.cs index 159a5520ab..02992c6537 100644 --- a/Orm/Xtensive.Orm/Sql/SqlNodeType.cs +++ b/Orm/Xtensive.Orm/Sql/SqlNodeType.cs @@ -47,6 +47,10 @@ public enum SqlNodeType DateTimeOffsetPlusInterval, DateTimeOffsetMinusInterval, DateTimeOffsetMinusDateTimeOffset, +#if NET6_0_OR_GREATER + TimePlusInterval, + TimeMinusTime, +#endif DeclareCursor, DefaultValue, Delete, diff --git a/Orm/Xtensive.Orm/Sql/SqlType.cs b/Orm/Xtensive.Orm/Sql/SqlType.cs index 9cba773976..965402e256 100644 --- a/Orm/Xtensive.Orm/Sql/SqlType.cs +++ b/Orm/Xtensive.Orm/Sql/SqlType.cs @@ -96,6 +96,22 @@ public struct SqlType : IEquatable /// Storage size is 8 to 10 bytes. /// public static readonly SqlType DateTimeOffset = new SqlType("DateTimeOffset"); +#if NET6_0_OR_GREATER + + /// + /// Date from January 1, 1753 through December 31, 9999, + /// Storage size is 3 bytes. + /// + public static readonly SqlType Date = new SqlType("Date"); + + /// + /// Time data from to an accuracy of one three-hundredth of a second (equivalent to 3.33 + /// milliseconds or 0.00333 seconds). Values are rounded to increments + /// of .000, .003, or .007 seconds. + /// Storage size is 6 bytes. + /// + public static readonly SqlType Time = new SqlType("Time"); +#endif /// /// Datetime interval. diff --git a/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs b/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs index ebf1c576fd..f95ea9b57c 100644 --- a/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs +++ b/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2021 Xtensive LLC. +// Copyright (C) 2009-2022 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 @@ -46,12 +46,12 @@ public virtual void BindBoolean(DbParameter parameter, object value) public virtual void BindChar(DbParameter parameter, object value) { parameter.DbType = DbType.String; - if (value==null) { + if (value == null) { parameter.Value = DBNull.Value; return; } var _char = (char) value; - parameter.Value = _char==default(char) ? string.Empty : _char.ToString(); + parameter.Value = _char == default(char) ? string.Empty : _char.ToString(); } public virtual void BindString(DbParameter parameter, object value) @@ -131,6 +131,20 @@ public virtual void BindDateTime(DbParameter parameter, object value) parameter.DbType = DbType.DateTime; parameter.Value = value ?? DBNull.Value; } +#if NET6_0_OR_GREATER + + public virtual void BindDateOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Date; + parameter.Value = value != null ? ((DateOnly) value).ToDateTime(TimeOnly.MinValue) : DBNull.Value; + } + + public virtual void BindTimeOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Time; + parameter.Value = value != null ? ((TimeOnly) value).ToTimeSpan() : DBNull.Value; + } +#endif public virtual void BindDateTimeOffset(DbParameter parameter, object value) { @@ -141,7 +155,7 @@ public virtual void BindDateTimeOffset(DbParameter parameter, object value) public virtual void BindTimeSpan(DbParameter parameter, object value) { parameter.DbType = DbType.Int64; - if (value!=null) { + if (value != null) { var timeSpan = ValueRangeValidator.Correct((TimeSpan) value, Int64TimeSpanRange); parameter.Value = timeSpan.Ticks * 100; } @@ -165,85 +179,61 @@ public virtual void BindByteArray(DbParameter parameter, object value) #region ReadXxx methods - public virtual object ReadBoolean(DbDataReader reader, int index) - { - return reader.GetBoolean(index); - } + public virtual object ReadBoolean(DbDataReader reader, int index) => + reader.GetBoolean(index); - public virtual object ReadChar(DbDataReader reader, int index) - { - return reader.GetString(index).SingleOrDefault(); - } + public virtual object ReadChar(DbDataReader reader, int index) => + reader.GetString(index).SingleOrDefault(); - public virtual object ReadString(DbDataReader reader, int index) - { - return reader.GetString(index); - } + public virtual object ReadString(DbDataReader reader, int index) => + reader.GetString(index); - public virtual object ReadByte(DbDataReader reader, int index) - { - return reader.GetByte(index); - } + public virtual object ReadByte(DbDataReader reader, int index) => + reader.GetByte(index); - public virtual object ReadSByte(DbDataReader reader, int index) - { - return Convert.ToSByte(reader[index]); - } + public virtual object ReadSByte(DbDataReader reader, int index) => + Convert.ToSByte(reader[index]); - public virtual object ReadShort(DbDataReader reader, int index) - { - return reader.GetInt16(index); - } + public virtual object ReadShort(DbDataReader reader, int index) => + reader.GetInt16(index); - public virtual object ReadUShort(DbDataReader reader, int index) - { - return Convert.ToUInt16(reader[index]); - } + public virtual object ReadUShort(DbDataReader reader, int index) => + Convert.ToUInt16(reader[index]); - public virtual object ReadInt(DbDataReader reader, int index) - { - return reader.GetInt32(index); - } + public virtual object ReadInt(DbDataReader reader, int index) => + reader.GetInt32(index); - public virtual object ReadUInt(DbDataReader reader, int index) - { - return Convert.ToUInt32(reader[index]); - } + public virtual object ReadUInt(DbDataReader reader, int index) => + Convert.ToUInt32(reader[index]); - public virtual object ReadLong(DbDataReader reader, int index) - { - return reader.GetInt64(index); - } + public virtual object ReadLong(DbDataReader reader, int index) => + reader.GetInt64(index); - public virtual object ReadULong(DbDataReader reader, int index) - { - return Convert.ToUInt64(reader[index]); - } + public virtual object ReadULong(DbDataReader reader, int index) => + Convert.ToUInt64(reader[index]); - public virtual object ReadFloat(DbDataReader reader, int index) - { - return reader.GetFloat(index); - } + public virtual object ReadFloat(DbDataReader reader, int index) => + reader.GetFloat(index); - public virtual object ReadDouble(DbDataReader reader, int index) - { - return reader.GetDouble(index); - } + public virtual object ReadDouble(DbDataReader reader, int index) => + reader.GetDouble(index); - public virtual object ReadDecimal(DbDataReader reader, int index) - { - return reader.GetDecimal(index); - } + public virtual object ReadDecimal(DbDataReader reader, int index) => + reader.GetDecimal(index); - public virtual object ReadDateTime(DbDataReader reader, int index) - { - return reader.GetDateTime(index); - } + public virtual object ReadDateTime(DbDataReader reader, int index) => + reader.GetDateTime(index); +#if NET6_0_OR_GREATER - public virtual object ReadDateTimeOffset(DbDataReader reader, int index) - { - return (DateTimeOffset) reader.GetValue(index); - } + public virtual object ReadDateOnly(DbDataReader reader, int index) => + DateOnly.FromDateTime(reader.GetFieldValue(index)); + + public virtual object ReadTimeOnly(DbDataReader reader, int index) => + TimeOnly.FromTimeSpan(reader.GetFieldValue(index)); +#endif + + public virtual object ReadDateTimeOffset(DbDataReader reader, int index) => + (DateTimeOffset) reader.GetValue(index); public virtual object ReadTimeSpan(DbDataReader reader, int index) { @@ -257,10 +247,8 @@ public virtual object ReadTimeSpan(DbDataReader reader, int index) return TimeSpan.FromTicks(value / 100); } - public virtual object ReadGuid(DbDataReader reader, int index) - { - return reader.GetGuid(index); - } + public virtual object ReadGuid(DbDataReader reader, int index) => + reader.GetGuid(index); public virtual object ReadByteArray(DbDataReader reader, int index) { @@ -282,76 +270,50 @@ public virtual object ReadByteArray(DbDataReader reader, int index) #region MapXxx methods - public virtual SqlValueType MapBoolean(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Boolean); - } + public virtual SqlValueType MapBoolean(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Boolean); - public virtual SqlValueType MapChar(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.VarChar, 1); - } + public virtual SqlValueType MapChar(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.VarChar, 1); - public virtual SqlValueType MapString(int? length, int? precision, int? scale) - { - return ChooseStreamType(SqlType.VarChar, SqlType.VarCharMax, length, VarCharMaxLength); - } + public virtual SqlValueType MapString(int? length, int? precision, int? scale) => + ChooseStreamType(SqlType.VarChar, SqlType.VarCharMax, length, VarCharMaxLength); - public virtual SqlValueType MapByte(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.UInt8); - } + public virtual SqlValueType MapByte(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.UInt8); - public virtual SqlValueType MapSByte(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Int8); - } + public virtual SqlValueType MapSByte(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Int8); - public virtual SqlValueType MapShort(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Int16); - } + public virtual SqlValueType MapShort(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Int16); - public virtual SqlValueType MapUShort(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.UInt16); - } + public virtual SqlValueType MapUShort(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.UInt16); - public virtual SqlValueType MapInt(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Int32); - } + public virtual SqlValueType MapInt(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Int32); - public virtual SqlValueType MapUInt(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.UInt32); - } + public virtual SqlValueType MapUInt(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.UInt32); - public virtual SqlValueType MapLong(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Int64); - } + public virtual SqlValueType MapLong(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Int64); - public virtual SqlValueType MapULong(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.UInt64); - } + public virtual SqlValueType MapULong(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.UInt64); - public virtual SqlValueType MapFloat(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Float); - } + public virtual SqlValueType MapFloat(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Float); - public virtual SqlValueType MapDouble(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Double); - } + public virtual SqlValueType MapDouble(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Double); public virtual SqlValueType MapDecimal(int? length, int? precision, int? scale) { - if (MaxDecimalPrecision==null) + if (MaxDecimalPrecision == null) return new SqlValueType(SqlType.Decimal); - if (precision==null) { + if (precision == null) { var resultPrecision = Math.Min(DecimalPrecisionLimit, MaxDecimalPrecision.Value); var resultScale = resultPrecision / 2; return new SqlValueType(SqlType.Decimal, resultPrecision, resultScale); @@ -363,38 +325,36 @@ public virtual SqlValueType MapDecimal(int? length, int? precision, int? scale) return new SqlValueType(SqlType.Decimal, null, null, precision, scale); } - public virtual SqlValueType MapDateTime(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.DateTime); - } + public virtual SqlValueType MapDateTime(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.DateTime); +#if NET6_0_OR_GREATER - public virtual SqlValueType MapDateTimeOffset(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.DateTimeOffset); - } + public virtual SqlValueType MapDateOnly(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Date); - public virtual SqlValueType MapTimeSpan(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Int64); - } + public virtual SqlValueType MapTimeOnly(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Time); +#endif - public virtual SqlValueType MapGuid(int? length, int? precision, int? scale) - { - return new SqlValueType(SqlType.Guid); - } + public virtual SqlValueType MapDateTimeOffset(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.DateTimeOffset); - public virtual SqlValueType MapByteArray(int? length, int? precision, int? scale) - { - return ChooseStreamType(SqlType.VarBinary, SqlType.VarBinaryMax, length, VarBinaryMaxLength); - } + public virtual SqlValueType MapTimeSpan(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Int64); + + public virtual SqlValueType MapGuid(int? length, int? precision, int? scale) => + new SqlValueType(SqlType.Guid); + + public virtual SqlValueType MapByteArray(int? length, int? precision, int? scale) => + ChooseStreamType(SqlType.VarBinary, SqlType.VarBinaryMax, length, VarBinaryMaxLength); #endregion protected static SqlValueType ChooseStreamType(SqlType varType, SqlType varMaxType, int? length, int? varTypeMaxLength) { - if (varTypeMaxLength==null) + if (varTypeMaxLength == null) return new SqlValueType(varMaxType); - if (length==null) + if (length == null) return new SqlValueType(varType, varTypeMaxLength.Value); if (length.Value > varTypeMaxLength.Value) return new SqlValueType(varMaxType); @@ -407,13 +367,13 @@ protected static SqlValueType ChooseStreamType(SqlType varType, SqlType varMaxTy public virtual void Initialize() { var varchar = Driver.ServerInfo.DataTypes.VarChar; - if (varchar!=null) + if (varchar != null) VarCharMaxLength = varchar.MaxLength; var varbinary = Driver.ServerInfo.DataTypes.VarBinary; - if (varbinary!=null) + if (varbinary != null) VarBinaryMaxLength = varbinary.MaxLength; var _decimal = Driver.ServerInfo.DataTypes.Decimal; - if (_decimal!=null) + if (_decimal != null) MaxDecimalPrecision = _decimal.MaxPrecision; } diff --git a/Orm/Xtensive.Orm/Strings.Designer.cs b/Orm/Xtensive.Orm/Strings.Designer.cs index 7c2398672c..c0394b5596 100644 --- a/Orm/Xtensive.Orm/Strings.Designer.cs +++ b/Orm/Xtensive.Orm/Strings.Designer.cs @@ -1352,6 +1352,15 @@ internal static string ExDatabaseMappingRequiresMultidatabaseDomainConfiguration } } + /// + /// Looks up a localized string similar to DateOnly.ToString() method is not supported, use the DateOnly.ToString("o").. + /// + internal static string ExDateOnlyToStringMethodIsNotSupported { + get { + return ResourceManager.GetString("ExDateOnlyToStringMethodIsNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to DateTime.ToString() method is not supported, use the DateTime.ToString("s").. /// @@ -4299,6 +4308,15 @@ internal static string ExThisStorageDoesNotSupportXValuesLessThanYSuppliedValueI } } + /// + /// Looks up a localized string similar to TimeOnly.ToString() method is not supported, use the TimeOnly.ToString("o").. + /// + internal static string ExTimeOnlyToStringMethodIsNotSupported { + get { + return ResourceManager.GetString("ExTimeOnlyToStringMethodIsNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Transaction is not active.. /// @@ -4326,12 +4344,21 @@ internal static string ExTransactionShouldNotBeActive { } } + /// + /// Looks up a localized string similar to Translation of DateOnly.ToString(string) with arbitrary arguments is not supported. Use DateOnly.ToString("s").. + /// + internal static string ExTranslationOfDateOnlyToStringWithArbitraryArgumentIsNotSupported { + get { + return ResourceManager.GetString("ExTranslationOfDateOnlyToStringWithArbitraryArgumentIsNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Translation of DateTime.ToString(string) with arbitrary arguments is not supported. Use DateTime.ToString("s").. /// - internal static string ExTranslationOfDateTimeToStringWithArbitraryArgumentsIsNotSupported { + internal static string ExTranslationOfDateTimeToStringWithArbitraryArgumentIsNotSupported { get { - return ResourceManager.GetString("ExTranslationOfDateTimeToStringWithArbitraryArgumentsIsNotSupported", resourceCulture); + return ResourceManager.GetString("ExTranslationOfDateTimeToStringWithArbitraryArgumentIsNotSupported", resourceCulture); } } @@ -4353,6 +4380,15 @@ internal static string ExTranslationOfLiteralOfTypeXIsNotSupported { } } + /// + /// Looks up a localized string similar to Translation of TimeOnly.ToString(string) with arbitrary arguments is not supported. Use TimeOnly.ToString("s").. + /// + internal static string ExTranslationOfTimeOnlyToStringWithArbitraryArgumentIsNotSupported { + get { + return ResourceManager.GetString("ExTranslationOfTimeOnlyToStringWithArbitraryArgumentIsNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Translation of {0} method does not support any parameter type, but {1}.. /// diff --git a/Orm/Xtensive.Orm/Strings.resx b/Orm/Xtensive.Orm/Strings.resx index b1bc272b2a..b3c93464c1 100644 --- a/Orm/Xtensive.Orm/Strings.resx +++ b/Orm/Xtensive.Orm/Strings.resx @@ -2390,7 +2390,7 @@ Error: {1} DateTime.ToString() method is not supported, use the DateTime.ToString("s"). - + Translation of DateTime.ToString(string) with arbitrary arguments is not supported. Use DateTime.ToString("s"). @@ -2588,4 +2588,16 @@ Error: {1} Query was compiled with DomainConfiguration.ShareStorageSchemaOverNodes option set to true, it requires PostCompilerConfiguration.SchemaMapping and PostCompilerConfiguration.DatabaseMapping collections to be provided. + + DateOnly.ToString() method is not supported, use the DateOnly.ToString("o"). + + + TimeOnly.ToString() method is not supported, use the TimeOnly.ToString("o"). + + + Translation of DateOnly.ToString(string) with arbitrary arguments is not supported. Use DateOnly.ToString("s"). + + + Translation of TimeOnly.ToString(string) with arbitrary arguments is not supported. Use TimeOnly.ToString("s"). + \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Tuples/Packed/PackedFieldAccessor.cs b/Orm/Xtensive.Orm/Tuples/Packed/PackedFieldAccessor.cs index 96c05a0401..4a5974695e 100644 --- a/Orm/Xtensive.Orm/Tuples/Packed/PackedFieldAccessor.cs +++ b/Orm/Xtensive.Orm/Tuples/Packed/PackedFieldAccessor.cs @@ -11,7 +11,7 @@ namespace Xtensive.Tuples.Packed { internal abstract class PackedFieldAccessor { - public static readonly PackedFieldAccessor[] All = new PackedFieldAccessor[18]; + public static readonly PackedFieldAccessor[] All = new PackedFieldAccessor[20]; /// /// Getter delegate. @@ -587,4 +587,32 @@ public DateTimeOffsetFieldAccessor() : base(GetSize() * 8, 17) { } } +#if NET6_0_OR_GREATER + + internal sealed class DateOnlyFieldAccessor : ValueFieldAccessor + { + protected override DateOnly Decode(long value) => + DateOnly.FromDayNumber((int)value); + + protected override long Encode(DateOnly value) => + value.DayNumber; + + public DateOnlyFieldAccessor() + : base(sizeof(int) * 8, 18) + { } + } + + internal sealed class TimeOnlyFieldAccessor : ValueFieldAccessor + { + protected override TimeOnly Decode(long value) => + new TimeOnly(value); + + protected override long Encode(TimeOnly value) => + value.Ticks; + + public TimeOnlyFieldAccessor() + : base(sizeof(long) * 8, 19) + { } + } +#endif } diff --git a/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs b/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs index e554c77124..c878cc729b 100644 --- a/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs +++ b/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs @@ -46,6 +46,10 @@ private static class ValueFieldAccessorResolver private static readonly ValueFieldAccessor SingleAccessor = new FloatFieldAccessor(); private static readonly ValueFieldAccessor DoubleAccessor = new DoubleFieldAccessor(); private static readonly ValueFieldAccessor DateTimeAccessor = new DateTimeFieldAccessor(); +#if NET6_0_OR_GREATER + private static readonly ValueFieldAccessor DateOnlyAccessor = new DateOnlyFieldAccessor(); + private static readonly ValueFieldAccessor TimeOnlyAccessor = new TimeOnlyFieldAccessor(); +#endif private static readonly ValueFieldAccessor TimeSpanAccessor = new TimeSpanFieldAccessor(); private static readonly ValueFieldAccessor DecimalAccessor = new DecimalFieldAccessor(); private static readonly ValueFieldAccessor GuidAccessor = new GuidFieldAccessor(); @@ -74,6 +78,10 @@ ValueFieldAccessor ResolveByType(Type type) ReferenceEquals(type, WellKnownTypes.Double) ? DoubleAccessor : ReferenceEquals(type, WellKnownTypes.DateTime) ? DateTimeAccessor : ReferenceEquals(type, WellKnownTypes.TimeSpan) ? TimeSpanAccessor : +#if NET6_0_OR_GREATER + ReferenceEquals(type, WellKnownTypes.DateOnly) ? DateOnlyAccessor : + ReferenceEquals(type, WellKnownTypes.TimeOnly) ? TimeOnlyAccessor : +#endif ReferenceEquals(type, WellKnownTypes.Decimal) ? DecimalAccessor : ReferenceEquals(type, WellKnownTypes.Guid) ? GuidAccessor : ReferenceEquals(type, WellKnownTypes.DateTimeOffset) ? DateTimeOffsetAccessor : null; @@ -94,6 +102,10 @@ ValueFieldAccessor ResolveByNullableType(Type type) ReferenceEquals(type, WellKnownTypes.NullableDouble) ? DoubleAccessor : ReferenceEquals(type, WellKnownTypes.NullableDateTime) ? DateTimeAccessor : ReferenceEquals(type, WellKnownTypes.NullableTimeSpan) ? TimeSpanAccessor : +#if NET6_0_OR_GREATER + ReferenceEquals(type, WellKnownTypes.NullableDateOnly) ? DateOnlyAccessor : + ReferenceEquals(type, WellKnownTypes.NullableTimeOnly) ? TimeOnlyAccessor : +#endif ReferenceEquals(type, WellKnownTypes.NullableDecimal) ? DecimalAccessor : ReferenceEquals(type, WellKnownTypes.NullableGuid) ? GuidAccessor : ReferenceEquals(type, WellKnownTypes.NullableDateTimeOffset) ? DateTimeOffsetAccessor : null; diff --git a/Orm/Xtensive.Orm/Xtensive.Orm.csproj b/Orm/Xtensive.Orm/Xtensive.Orm.csproj index dae58d992d..1260d3c65b 100644 --- a/Orm/Xtensive.Orm/Xtensive.Orm.csproj +++ b/Orm/Xtensive.Orm/Xtensive.Orm.csproj @@ -50,7 +50,7 @@ - +