From f96800683b24e69aa86a458965757a02c346ba08 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 28 Jan 2025 13:19:36 +0500 Subject: [PATCH 01/48] Update BitFaster.Caching to version 2.5.3 --- Orm/Xtensive.Orm/Xtensive.Orm.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm/Xtensive.Orm.csproj b/Orm/Xtensive.Orm/Xtensive.Orm.csproj index 24af9cafc..0b7a1746c 100644 --- a/Orm/Xtensive.Orm/Xtensive.Orm.csproj +++ b/Orm/Xtensive.Orm/Xtensive.Orm.csproj @@ -61,7 +61,7 @@ - + From f6f7864529dd8a89ebc71896de5636860d9da8ed Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 28 Jan 2025 13:22:56 +0500 Subject: [PATCH 02/48] Update Firebird client package to version 10.3.2 --- Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj b/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj index de71f4cda..5ff93ec8f 100644 --- a/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj +++ b/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj @@ -25,7 +25,7 @@ - + From 78ff9d7568b198453e80fcfadc83b9baabeeba4d Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 28 Jan 2025 15:29:07 +0500 Subject: [PATCH 03/48] Update log4net package to version 3.0.3 --- .../Xtensive.Orm.Logging.log4net.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/Xtensive.Orm.Logging.log4net/Xtensive.Orm.Logging.log4net.csproj b/Extensions/Xtensive.Orm.Logging.log4net/Xtensive.Orm.Logging.log4net.csproj index 82759f9fa..68c1f25a8 100644 --- a/Extensions/Xtensive.Orm.Logging.log4net/Xtensive.Orm.Logging.log4net.csproj +++ b/Extensions/Xtensive.Orm.Logging.log4net/Xtensive.Orm.Logging.log4net.csproj @@ -16,7 +16,7 @@ ReadMe.md - + From 8eb7842b7fb60839503e9903c8181b018670f9b6 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 28 Jan 2025 15:54:24 +0500 Subject: [PATCH 04/48] Update NLog package to version 5.3.4 --- .../Xtensive.Orm.Logging.NLog/Xtensive.Orm.Logging.NLog.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/Xtensive.Orm.Logging.NLog/Xtensive.Orm.Logging.NLog.csproj b/Extensions/Xtensive.Orm.Logging.NLog/Xtensive.Orm.Logging.NLog.csproj index 75313a09e..6fedbd002 100644 --- a/Extensions/Xtensive.Orm.Logging.NLog/Xtensive.Orm.Logging.NLog.csproj +++ b/Extensions/Xtensive.Orm.Logging.NLog/Xtensive.Orm.Logging.NLog.csproj @@ -16,7 +16,7 @@ ReadMe.md - + From 5dbcfdec587400b8d4da5fc6a37297d9a3fa6be8 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 28 Jan 2025 16:41:25 +0500 Subject: [PATCH 05/48] Update Ms SQL Server client library to version 5.2.2 --- Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj b/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj index 0f159cd16..e3305af60 100644 --- a/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj +++ b/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj @@ -25,7 +25,7 @@ - + From 09a4a9d2857f87e3b953f1fef124bc0631cdd143 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 28 Jan 2025 20:27:47 +0500 Subject: [PATCH 06/48] Update Oracle client library to version 23.7.0 --- Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj b/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj index f41e750d6..4222822d6 100644 --- a/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj +++ b/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj @@ -25,7 +25,7 @@ - + From 6e1ede4ea60ce4c2a15cb3ccc3996d8841f2327f Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 30 Jan 2025 10:53:56 +0500 Subject: [PATCH 07/48] Update MySQL client library to version 8.4.0 --- Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj b/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj index ec8c61745..7ce09d817 100644 --- a/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj +++ b/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj @@ -39,7 +39,7 @@ - + From 9a7b5ffe171f199ace491f75207d4baa62e3cf65 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 15:54:56 +0500 Subject: [PATCH 08/48] Update Postgresql client library package to version 6.0.13 --- Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj | 2 +- Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj b/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj index f15fe868b..b4bd3f57e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj +++ b/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj @@ -25,7 +25,7 @@ - + diff --git a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj index 0d3ae1dca..0b1256f46 100644 --- a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj +++ b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj @@ -20,7 +20,7 @@ - + From 74bc666eeb45165219d9fa240613daa8d88017f3 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 15:55:53 +0500 Subject: [PATCH 09/48] SqlExtract: Remove commented code --- .../Sql/Dml/Expressions/SqlExtract.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs index cfa9e9eeb..d192f7c7b 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs @@ -61,19 +61,11 @@ public override void ReplaceWith(SqlExpression expression) internalValue = replacingExpression.internalValue; typeMarker = replacingExpression.typeMarker; typeHasTime = replacingExpression.typeHasTime; - //DateTimePart = replacingExpression.DateTimePart; - //DateTimeOffsetPart = replacingExpression.DateTimeOffsetPart; - //IntervalPart = replacingExpression.IntervalPart; Operand = replacingExpression.Operand; } internal override SqlExtract Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new SqlExtract(t.internalValue, t.typeMarker, t.Operand.Clone(c))); - //t.DateTimePart != SqlDateTimePart.Nothing - // ? new SqlExtract(t.DateTimePart, t.Operand.Clone(c)) - // : t.IntervalPart != SqlIntervalPart.Nothing - // ? new SqlExtract(t.IntervalPart, t.Operand.Clone(c)) - // : new SqlExtract(t.DateTimeOffsetPart, t.Operand.Clone(c))); public override void AcceptVisitor(ISqlVisitor visitor) { @@ -88,10 +80,6 @@ internal SqlExtract(SqlDateTimePart dateTimePart, SqlExpression operand) internalValue = dateTimePart.ToDtoPartFast(); typeMarker = DateTimeTypeId; typeHasTime = true; - - //DateTimePart = dateTimePart; - //DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing; - //IntervalPart = SqlIntervalPart.Nothing; Operand = operand; } @@ -101,10 +89,6 @@ internal SqlExtract(SqlIntervalPart intervalPart, SqlExpression operand) internalValue = intervalPart.ToDtoPartFast(); typeMarker = IntervalTypeId; typeHasTime = true; - - //DateTimePart = SqlDateTimePart.Nothing; - //DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing; - //IntervalPart = intervalPart; Operand = operand; } @@ -140,7 +124,7 @@ private SqlExtract(SqlDateTimeOffsetPart internalValue, int typeMarker, SqlExpre { this.internalValue = internalValue; this.typeMarker = typeMarker; - typeHasTime = typeMarker == DateTimeTypeId || typeMarker == DateTimeOffsetTypeId || typeMarker == TimeTypeId || typeMarker == IntervalTypeId; + typeHasTime = typeMarker is DateTimeTypeId or DateTimeOffsetTypeId or TimeTypeId or IntervalTypeId; Operand = operand; } } From 49129f04dc9f1e7fdcf22ff842f823451a05c430 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 16:09:08 +0500 Subject: [PATCH 10/48] Postgre drivers: Add extra provider specific info to driver and pass postgre abstract driver to compiler/typemapper not the generic one ALSO - Added setting of two Postgre AppDomain switches. The first one disables DateTime/DateOnly/DateTimeOffset's min and max value conversion to Infinity/-Infinity, without it many of operations with the types are more complicated because require checks for infinities to provide correct results of queries. Second one disable legacy timestamps behavior, this legacy mode can be removed at any point of time in future we can't rely on it. --- .../Sql.Drivers.PostgreSql/Driver.cs | 8 +- .../Sql.Drivers.PostgreSql/DriverFactory.cs | 77 +++++++++++++++++-- .../PostgreServerInfo.cs | 26 +++++++ .../PostgreSqlTypeMapper.cs | 5 ++ .../Sql.Drivers.PostgreSql/v10_0/Compiler.cs | 4 +- .../Sql.Drivers.PostgreSql/v10_0/Driver.cs | 5 +- .../v10_0/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v12_0/Compiler.cs | 2 +- .../Sql.Drivers.PostgreSql/v12_0/Driver.cs | 5 +- .../v12_0/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_0/Compiler.cs | 3 +- .../Sql.Drivers.PostgreSql/v8_0/Driver.cs | 6 +- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 3 +- .../Sql.Drivers.PostgreSql/v8_1/Compiler.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_1/Driver.cs | 6 +- .../Sql.Drivers.PostgreSql/v8_1/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_2/Compiler.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_2/Driver.cs | 6 +- .../Sql.Drivers.PostgreSql/v8_2/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_3/Compiler.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_3/Driver.cs | 6 +- .../Sql.Drivers.PostgreSql/v8_3/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_4/Compiler.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_4/Driver.cs | 4 +- .../Sql.Drivers.PostgreSql/v8_4/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v9_0/Compiler.cs | 3 +- .../Sql.Drivers.PostgreSql/v9_0/Driver.cs | 6 +- .../Sql.Drivers.PostgreSql/v9_0/TypeMapper.cs | 4 +- .../Sql.Drivers.PostgreSql/v9_1/Compiler.cs | 4 +- .../Sql.Drivers.PostgreSql/v9_1/Driver.cs | 6 +- .../Sql.Drivers.PostgreSql/v9_1/TypeMapper.cs | 4 +- Orm/Xtensive.Orm.PostgreSql/WellKnown.cs | 14 ++++ Orm/Xtensive.Orm.PostgreSql/WellKnownTypes.cs | 1 + 33 files changed, 183 insertions(+), 65 deletions(-) create mode 100644 Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs create mode 100644 Orm/Xtensive.Orm.PostgreSql/WellKnown.cs diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs index 63d93ce21..a441b23d5 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs @@ -13,6 +13,11 @@ namespace Xtensive.Sql.Drivers.PostgreSql { internal abstract class Driver : SqlDriver { + /// + /// PosgreSQL-specific information about server. + /// + internal PostgreServerInfo PostgreServerInfo { get; } + [SecuritySafeCritical] protected override SqlConnection DoCreateConnection() { @@ -100,9 +105,10 @@ private SqlExceptionType ProcessClientSideException(NpgsqlException clientSideEx // Constructors - protected Driver(CoreServerInfo coreServerInfo) + protected Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) : base(coreServerInfo) { + PostgreServerInfo = pgServerInfo; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs index f0dbb07d5..4d524b305 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2021 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -23,6 +23,9 @@ public class DriverFactory : SqlDriverFactory { private const string DatabaseAndSchemaQuery = "select current_database(), current_schema()"; + private readonly static bool InfinityAliasForDatesEnabled; + private readonly static bool LegacyTimestamptBehaviorEnabled; + /// [SecuritySafeCritical] protected override string BuildConnectionString(UrlInfo url) @@ -74,6 +77,10 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf protected override async Task CreateDriverAsync( string connectionString, SqlDriverConfiguration configuration, CancellationToken token) { + // these settings needed to be read before any connection happens + var useInfinityAliasForDateTime = InfinityAliasForDatesEnabled; + var legacyTimestampBehavior = LegacyTimestamptBehaviorEnabled; + var connection = new NpgsqlConnection(connectionString); await using (connection.ConfigureAwait(false)) { if (configuration.DbConnectionAccessors.Count > 0) @@ -105,6 +112,11 @@ private static SqlDriver CreateDriverInstance( DefaultSchemaName = defaultSchema.Schema, }; + var pgsqlServerInfo = new PostgreServerInfo() { + InfinityAliasForDatesEnabled = InfinityAliasForDatesEnabled, + LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled + }; + if (version.Major < 8 || (version.Major == 8 && version.Minor < 3)) { throw new NotSupportedException(Strings.ExPostgreSqlBelow83IsNotSupported); } @@ -112,13 +124,13 @@ private static SqlDriver CreateDriverInstance( // We support 8.3, 8.4 and any 9.0+ return version.Major switch { - 8 when version.Minor == 3 => new v8_3.Driver(coreServerInfo), - 8 when version.Minor > 3 => new v8_4.Driver(coreServerInfo), - 9 when version.Minor == 0 => new v9_0.Driver(coreServerInfo), - 9 when version.Minor > 0 => new v9_1.Driver(coreServerInfo), - 10 => new v10_0.Driver(coreServerInfo), - 11 => new v10_0.Driver(coreServerInfo), - _ => new v12_0.Driver(coreServerInfo) + 8 when version.Minor == 3 => new v8_3.Driver(coreServerInfo, pgsqlServerInfo), + 8 when version.Minor > 3 => new v8_4.Driver(coreServerInfo, pgsqlServerInfo), + 9 when version.Minor == 0 => new v9_0.Driver(coreServerInfo, pgsqlServerInfo), + 9 when version.Minor > 0 => new v9_1.Driver(coreServerInfo, pgsqlServerInfo), + 10 => new v10_0.Driver(coreServerInfo, pgsqlServerInfo), + 11 => new v10_0.Driver(coreServerInfo, pgsqlServerInfo), + _ => new v12_0.Driver(coreServerInfo, pgsqlServerInfo) }; } @@ -188,5 +200,54 @@ await SqlHelper.NotifyConnectionInitializingAsync(accessors, } } } + + private static bool SetOrGetExistingDisableInfinityAliasForDatesSwitch(bool valueToSet) => + GetSwitchValueOrSet(Orm.PostgreSql.WellKnown.DateTimeToInfinityConversionSwitchName, valueToSet); + + private static bool SetOrGetExistingLegacyTimeStampBehaviorSwitch(bool valueToSet) => + GetSwitchValueOrSet(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, valueToSet); + + private static bool GetSwitchValueOrSet(string switchName, bool valueToSet) + { + if (!AppContext.TryGetSwitch(switchName, out var currentValue)) { + AppContext.SetSwitch(switchName, valueToSet); + return valueToSet; + } + else { + return currentValue; + } + } + + static DriverFactory() + { + // Starging from Npgsql 6.0 they broke compatibility by forcefully replacing + // DateTime.MinValue/MaxValue of parameters with -Infinity and Infinity values. + // This new "feature", though doesn't affect reading/writing of values and equality/inequality + // filters, breaks some of operations such as parts extraction, default values for columns + // (which are constants and declared on high levels of abstraction) and some others. + + // We turn it off to make current code work as before and make current data of + // the user be compatible with algorighms as long as possible. + // But if the user sets the switch then we work with what we have. + // Usage of such aliases makes us to create extra statements in SQL queries to provide + // the same results the queries which are already written, which may make queries a bit slower. + + // DO NOT REPLACE method call with constant value when debugging, CHANGE THE PARAMETER VALUE. + InfinityAliasForDatesEnabled = !SetOrGetExistingDisableInfinityAliasForDatesSwitch(valueToSet: true); + + // Legacy timestamp behavoir turns off certain parameter value binding requirements + // and makes Npgsql work like v4 or older. + // Current behavior require manual specification of unspecified kind for DateTime values, + // because Local or Utc kind now meand that underlying type of value to Timestamp without time zone + // and Timestamp with time zone respectively. + // It also affects DateTimeOffsets, now there is a requirement to move timezone of value to Utc + // this forces us to use only local timezone when reading values, which basically ignores + // Postgre's setting SET TIME ZONE for database session. + + // We have to use current mode, not the legacy one, because there is a chance of legacy mode elimination. + + // DO NOT REPLACE method call with constant value when debugging, CHANGE THE PARAMETER VALUE. + LegacyTimestamptBehaviorEnabled = SetOrGetExistingLegacyTimeStampBehaviorSwitch(valueToSet: false); + } } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs new file mode 100644 index 000000000..e01709876 --- /dev/null +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2025 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Sql.Drivers.PostgreSql +{ + /// + /// Contains PostgreSQL specific information for driver, including + /// special settings of Npgsql library which may need for driver. + /// + internal sealed class PostgreServerInfo + { + /// + /// Indicates whether DateTime.MinValue/MaxValue, DateTimeOffset.MinValue/MaxValue and DateOnly.MinValue/MaxValue, + /// are replaced with -Infinity/Infinity values inside Npgsql library. + /// By default replacement is disabled as long as it is allowed by Npgsql. + /// + public bool InfinityAliasForDatesEnabled { get; init; } + + /// + /// Indicates whether legacy behavior of timestamp(tz) type inside Npgsql library is enabled. + /// The setting has effect on parameter binding and also value reading from DbDataReader. + /// + public bool LegacyTimestampBehavior { get; init; } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlTypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlTypeMapper.cs index e41eba8e6..4ba21476d 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlTypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlTypeMapper.cs @@ -37,6 +37,11 @@ public override void BindValue(DbParameter parameter, object value) public override SqlValueType MapType(int? length, int? precision, int? scale) => new SqlValueType(sqlType); + internal protected ArgumentException ValueNotOfTypeError(string typeName) + { + return new ArgumentException($"Value is not of '{typeName}' type."); + } + // Constructors protected PostgreSqlTypeMapper(string frameworkType, NpgsqlDbType npgsqlDbType, SqlType sqlType) 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 aed1c3dee..c4b1cb4e7 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,4 +1,4 @@ -// Copyright (C) 2019-2023 Xtensive LLC. +// Copyright (C) 2019-2025 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 @@ -54,7 +54,7 @@ protected static SqlUserFunctionCall MakeTime( // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Driver.cs index f76e02aba..a36a1c94c 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Xtensive LLC. +// Copyright (C) 2019-2025 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 @@ -21,7 +21,8 @@ internal class Driver : v9_0.Driver protected override Info.ServerInfoProvider CreateServerInfoProvider() => new ServerInfoProvider(this); - public Driver(CoreServerInfo coreServerInfo) : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/TypeMapper.cs index 9e1908f62..f95e2dee3 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Xtensive LLC. +// Copyright (C) 2019-2025 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 @@ -10,7 +10,7 @@ internal class TypeMapper : v9_1.TypeMapper { // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Compiler.cs index 450685049..f7a67bdc8 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Compiler.cs @@ -8,7 +8,7 @@ internal class Compiler : v10_0.Compiler { // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Driver.cs index 4070a0f62..a4eba0c90 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Xtensive LLC. +// Copyright (C) 2020 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -19,7 +19,8 @@ internal class Driver : v10_0.Driver protected override Info.ServerInfoProvider CreateServerInfoProvider() => new ServerInfoProvider(this); - public Driver(CoreServerInfo coreServerInfo) : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/TypeMapper.cs index a63a27cda..b3de1f901 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v12_0/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Xtensive LLC. +// Copyright (C) 2020 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -8,7 +8,7 @@ internal class TypeMapper : v10_0.TypeMapper { // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(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 8b723edd8..94669a69f 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 @@ -569,9 +569,10 @@ private bool TryDivideOffsetIntoParts(SqlExpression offsetInMinutes, ref int hou // Constructors - protected internal Compiler(SqlDriver driver) + protected internal Compiler(PostgreSql.Driver driver) : base(driver) { + infinityAliasForDatesEnabled = driver.PostgreServerInfo.InfinityAliasForDatesEnabled; } } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Driver.cs index bf1e7874f..22474106e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 Xtensive LLC. // All rights reserved. // For conditions of distribution and use, see license. // Created by: Denis Krjuchkov @@ -51,8 +51,8 @@ protected override void RegisterCustomReverseMappings(TypeMappingRegistryBuilder // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index 4e419085d..69f81b727 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -200,9 +200,10 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { + legacyTimestampBehaviorEnabled = driver.PostgreServerInfo.LegacyTimestampBehavior; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Compiler.cs index 8f3e38f7e..4cd2a68e3 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -12,7 +12,7 @@ internal class Compiler : v8_0.Compiler { // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Driver.cs index 082752b95..1887fbf66 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -26,8 +26,8 @@ internal class Driver : v8_0.Driver // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/TypeMapper.cs index 9be3f9472..4e6dfad0e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_1/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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,7 +13,7 @@ internal class TypeMapper : v8_0.TypeMapper // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Compiler.cs index 68496eca6..c925071f6 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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,7 +13,7 @@ internal class Compiler : v8_1.Compiler { // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Driver.cs index ba36f2e06..4a032484d 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -25,8 +25,8 @@ internal class Driver : v8_1.Driver // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/TypeMapper.cs index 987922dc2..8f42593ff 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_2/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -12,7 +12,7 @@ internal class TypeMapper : v8_1.TypeMapper { // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Compiler.cs index f5c3cabe0..d5bd3c3c1 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2022 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -124,7 +124,7 @@ public override void Visit(SqlFreeTextTable node) // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Driver.cs index 603d96536..b6dbb4601 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -25,8 +25,8 @@ internal class Driver : v8_2.Driver // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/TypeMapper.cs index 41c200e1b..b95f6113b 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_3/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -24,7 +24,7 @@ public override void BindGuid(DbParameter parameter, object value) // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Compiler.cs index 34a39b804..d68d0e2e4 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2003-2020 Xtensive LLC. +// Copyright (C) 2003-2025 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 @@ -10,7 +10,7 @@ internal class Compiler : v8_3.Compiler { // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Driver.cs index 40960e64f..7a71a3db5 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/Driver.cs @@ -23,8 +23,8 @@ internal class Driver : v8_3.Driver // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/TypeMapper.cs index 2a4ca8a50..7e8b1fb30 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_4/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -10,7 +10,7 @@ internal class TypeMapper : v8_3.TypeMapper { // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs index 95509ae6a..b5a06437e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2020 Xtensive LLC. +// Copyright (C) 2012-2025 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 @@ -11,6 +11,7 @@ internal class Compiler : v8_4.Compiler // Constructors public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Driver.cs index d4137dff2..793870b1b 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2020 Xtensive LLC. +// Copyright (C) 2012-2025 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 @@ -23,8 +23,8 @@ internal class Driver : v8_4.Driver // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/TypeMapper.cs index 72b4c5cb2..bb10c1bf5 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2020 Xtensive LLC. +// Copyright (C) 2012-2025 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 @@ -10,7 +10,7 @@ internal class TypeMapper : v8_4.TypeMapper { // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Compiler.cs index 231cbb4cb..6496dc6bf 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Compiler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Xtensive LLC. +// Copyright (C) 2019-2025 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 @@ -10,7 +10,7 @@ internal class Compiler : v9_0.Compiler { // Constructors - public Compiler(SqlDriver driver) + public Compiler(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Driver.cs index 44dd8af0d..4ef160835 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/Driver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Xtensive LLC. +// Copyright (C) 2019-2025 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 @@ -23,8 +23,8 @@ internal class Driver : v9_0.Driver // Constructors - public Driver(CoreServerInfo coreServerInfo) - : base(coreServerInfo) + public Driver(CoreServerInfo coreServerInfo, PostgreServerInfo pgServerInfo) + : base(coreServerInfo, pgServerInfo) { } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/TypeMapper.cs index bb69696f0..5458dbe3f 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_1/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Xtensive LLC. +// Copyright (C) 2019-2025 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 @@ -10,7 +10,7 @@ internal class TypeMapper : v9_0.TypeMapper { // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { } diff --git a/Orm/Xtensive.Orm.PostgreSql/WellKnown.cs b/Orm/Xtensive.Orm.PostgreSql/WellKnown.cs new file mode 100644 index 000000000..97e4751c5 --- /dev/null +++ b/Orm/Xtensive.Orm.PostgreSql/WellKnown.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2025 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Orm.PostgreSql +{ + internal static class WellKnown + { + public const string DateTimeToInfinityConversionSwitchName = "Npgsql.DisableDateTimeInfinityConversions"; + public const string LegacyTimestampBehaviorSwitchName = "Npgsql.EnableLegacyTimestampBehavior"; + + public const int IntervalDaysInMonth = 30; + } +} diff --git a/Orm/Xtensive.Orm.PostgreSql/WellKnownTypes.cs b/Orm/Xtensive.Orm.PostgreSql/WellKnownTypes.cs index baa140bed..bc3d4e4de 100644 --- a/Orm/Xtensive.Orm.PostgreSql/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm.PostgreSql/WellKnownTypes.cs @@ -11,6 +11,7 @@ namespace Xtensive.Reflection.PostgreSql { internal static class WellKnownTypes { + public static readonly Type DateTimeType = typeof(DateTime); public static readonly Type DateTimeOffsetType = typeof(DateTimeOffset); public static readonly Type TimeSpanType = typeof(TimeSpan); public static readonly Type GuidType = typeof(Guid); From ac30a98d7b3829d2cc8f2557d638598167546d2e Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 16:17:39 +0500 Subject: [PATCH 11/48] Paths and Polygon types are fixed now and no longer throw NRE on getting count of points --- .../Sql.Drivers.PostgreSql/v8_0/PathMapper.cs | 18 ++++++++---------- .../v8_0/PolygonMapper.cs | 17 ++++++++--------- .../FieldAccessors/DefaultFieldAccessor.cs | 11 ++--------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PathMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PathMapper.cs index 0ae1f821e..cbd05a135 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PathMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PathMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2020 Xtensive LLC. +// Copyright (C) 2014-2025 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 @@ -24,18 +24,16 @@ public override void BindValue(DbParameter parameter, object value) } var npgsqlParameter = (NpgsqlParameter) parameter; - - npgsqlParameter.Value = value; npgsqlParameter.NpgsqlDbType = NpgsqlDbType.Path; - // The method Equals(Object, Object), wrapped in a block 'try', - // is required in order to determine that the value NpgsqlPath has been initialized with no parameters. - try { - _ = value.Equals(value); + if (value is NpgsqlPath path) { + // we should fix paths with no points + npgsqlParameter.Value = (path.Count > 0) + ? value + : new NpgsqlPath(new[] { new NpgsqlPoint() }); } - catch (Exception) { - // If the value NpgsqlPath has been initialized with no parameters, then must set the initial value. - npgsqlParameter.Value = new NpgsqlPath(new[] { new NpgsqlPoint() }); + else { + throw ValueNotOfTypeError(nameof(NpgsqlPolygon)); } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PolygonMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PolygonMapper.cs index 6b25c1977..5b89426c5 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PolygonMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PolygonMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2020 Xtensive LLC. +// Copyright (C) 2014-2025 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 @@ -24,17 +24,16 @@ public override void BindValue(DbParameter parameter, object value) } var npgsqlParameter = (NpgsqlParameter) parameter; - npgsqlParameter.Value = value; npgsqlParameter.NpgsqlDbType = NpgsqlDbType.Polygon; - // The method Equals(Object, Object), wrapped in a block 'try', - // is required in order to determine that the value NpgsqlPolygon has been initialized with no parameters. - try { - value.Equals(value); + if (value is NpgsqlPolygon poligon) { + // we should fix poligons with no points + npgsqlParameter.Value = (poligon.Count > 0) + ? value + : new NpgsqlPolygon(new[] { new NpgsqlPoint() }); } - catch (Exception) { - // If the value NpgsqlPolygon has been initialized with no parameters, then must set the initial value. - npgsqlParameter.Value = new NpgsqlPolygon(new[] {new NpgsqlPoint()}); + else { + throw ValueNotOfTypeError(nameof(NpgsqlPolygon)); } } diff --git a/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs b/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs index 68bcf6982..a86606e13 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2008-2020 Xtensive LLC. +// Copyright (C) 2008-2025 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 @@ -18,14 +18,7 @@ internal class DefaultFieldAccessor : FieldAccessor public override bool AreSameValues(object oldValue, object newValue) { if (isValueType || isString) { - // The method of Equals(object, object) wrapped with in a block 'try catch', - // because that for data types NpgsqlPath and NpgsqlPolygon which are defined without an initial value it works incorrectly. - try { - return Equals(oldValue, newValue); - } - catch (Exception) { - return false; - } + return Equals(oldValue, newValue); } return false; From 180425e6a34215beba75bb969422c2ccb985f238 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 18:13:03 +0500 Subject: [PATCH 12/48] Sql level tests for new behavior of Npgsql --- .../PostgreSql/InfinityAliasTest.cs | 869 ++++++++++++++++++ ...VsCurrentDateTimeOffsetParameterBinding.cs | 178 ++++ ...LegacyVsCurrentDateTimeParameterBinding.cs | 230 +++++ .../PostgreSql/NpgsqlIntervalMappingTest.cs | 329 +++++++ 4 files changed, 1606 insertions(+) create mode 100644 Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs create mode 100644 Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs create mode 100644 Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs create mode 100644 Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs new file mode 100644 index 000000000..3d1fa464f --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs @@ -0,0 +1,869 @@ +// Copyright (C) 2025 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 NUnit.Framework; + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + public sealed class InfinityAliasTest : SqlTest + { + private const string DateOnlyMinValueTable = "DateOnlyTable1"; + private const string DateOnlyMaxValueTable = "DateOnlyTable2"; + + private const string DateTimeMinValueTable = "DateTimeTable1"; + private const string DateTimeMaxValueTable = "DateTimeTable2"; + + private const string DateTimeOffsetMinValueTable = "DateTimeOffsetTable1"; + private const string DateTimeOffsetMaxValueTable = "DateTimeOffsetTable2"; + + private Xtensive.Sql.TypeMapping longTypeMapping; + private Xtensive.Sql.TypeMapping dateOnlyTypeMapping; + private Xtensive.Sql.TypeMapping dateTimeTypeMapping; + private Xtensive.Sql.TypeMapping dateTimeOffsetTypeMapping; + + protected override void CheckRequirements() + { + // DO NOT check provider here. + // Require class uses driver creation which casues AppContext switch setup before TestFixtureSetup() method called + } + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + Require.ProviderIs(StorageProvider.PostgreSql); + + longTypeMapping = Driver.TypeMappings[typeof(long)]; + dateOnlyTypeMapping = Driver.TypeMappings[typeof(DateOnly)]; + dateTimeTypeMapping = Driver.TypeMappings[typeof(DateTime)]; + dateTimeOffsetTypeMapping = Driver.TypeMappings[typeof(DateTimeOffset)]; + + DropTablesForDateTime(); + DropTablesForDateOnly(); + DropTablesForDateTimeOffset(); + + CreateTablesForDateTimeTests(); + CreateTablesForDateOnlyTests(); + CreateTablesForDateTimeOffsetTests(); + } + + [Test] + public void MinDateTimeSelectNoFilterTest() + { + var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = reader.GetInt64(0); + var datetimeValue = reader.GetDateTime(1); + Assert.That(datetimeValue, Is.EqualTo(DateTime.MinValue)); + } + } + } + + [Test] + public void MinDateTimeSelectByEqualityTest() + { + var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeMinValueTable}\" WHERE \"Value\" = $1"); + var filterP = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(filterP, DateTime.MinValue); + _ = command.Parameters.Add(filterP); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MinDateTimeSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMinDateTimeSelectDatePart(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MinDateTimeSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMinDateTimeSelectDatePart(); + } + + private void TestMinDateTimeSelectDatePart() + { + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Year)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Month)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Day)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Hour)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Minute)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Second)); + } + } + } + + + [Test] + public void MaxDateTimeSelectNoFilterTest() + { + var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = reader.GetInt64(0); + var datetimeValue = reader.GetDateTime(1); + var difference = (datetimeValue - DateTime.MaxValue).Duration(); + Assert.That(difference, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(0.001))); + } + } + } + + [Test] + public void MaxDateTimeSelectByEqualityTest() + { + var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeMaxValueTable}\" WHERE \"Value\" = $1"); + var filterP = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(filterP, DateTime.MaxValue); + _ = command.Parameters.Add(filterP); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MaxDateTimeSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMaxDateTimeSelectDatePart(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MaxDateTimeSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMaxDateTimeSelectDatePart(); + } + + private void TestMaxDateTimeSelectDatePart() + { + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Year)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Month)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Day)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Hour)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Minute)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Second)); + } + } + } + + + [Test] + public void MinDateOnlyNoFilterTest() + { + var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateOnlyMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = reader.GetInt64(0); + var datetimeValue = DateOnly.FromDateTime(reader.GetDateTime(1)); + Assert.That(datetimeValue, Is.EqualTo(DateOnly.MinValue)); + } + } + } + + [Test] + public void MinDateOnlyByEqualityTest() + { + var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateOnlyMinValueTable}\" WHERE \"Value\" = $1"); + var filterP = Connection.CreateParameter(); + dateOnlyTypeMapping.BindValue(filterP, DateOnly.MinValue); + _ = command.Parameters.Add(filterP); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MinDateOnlySelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMinDateOnlySelectDatePart(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MinDateOnlySelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMinDateOnlySelectDatePart(); + } + + private void TestMinDateOnlySelectDatePart() + { + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateOnly.MinValue.Year)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateOnly.MinValue.Month)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateOnly.MinValue.Day)); + } + } + } + + + [Test] + public void MaxDateOnlyNoFilterTest() + { + var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateOnlyMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = reader.GetInt64(0); + var datetimeValue = DateOnly.FromDateTime(reader.GetDateTime(1)); + Assert.That(datetimeValue, Is.EqualTo(DateOnly.MaxValue)); + } + } + } + + [Test] + public void MaxDateOnlyByEqualityTest() + { + var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateOnlyMaxValueTable}\" WHERE \"Value\" = $1"); + var filterP = Connection.CreateParameter(); + dateOnlyTypeMapping.BindValue(filterP, DateOnly.MaxValue); + _ = command.Parameters.Add(filterP); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MaxDateOnlySelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMaxDateOnlySelectDatePart(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MaxDateOnlySelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMaxDateOnlySelectDatePart(); + } + + private void TestMaxDateOnlySelectDatePart() + { + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateOnly.MaxValue.Year)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateOnly.MaxValue.Month)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetDouble(0); + Assert.That(yearPart, Is.EqualTo(DateOnly.MaxValue.Day)); + } + } + } + + + [Test] + public void MinDateTimeOffsetSelectNoFilterTest() + { + var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = reader.GetInt64(0); + var datetimeValue = reader.GetDateTime(1); + Assert.That(datetimeValue, Is.EqualTo(DateTimeOffset.MinValue.DateTime)); + } + } + } + + [Test] + public void MinDateTimeOffsetSelectByEqualityTest() + { + var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeOffsetMinValueTable}\" WHERE \"Value\" = $1"); + var filterP = Connection.CreateParameter(); + dateTimeOffsetTypeMapping.BindValue(filterP, DateTimeOffset.MinValue); + command.Parameters.Add(filterP); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MinDateTimeOffsetSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMinDateTimeOffsetSelectDatePart(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MinDateTimeOffsetSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMinDateTimeOffsetSelectDatePart(); + } + + private void TestMinDateTimeOffsetSelectDatePart() + { + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Year)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Month)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Day)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Hour)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Minute)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Second)); + } + } + } + + + [Test] + public void MaxDateTimeOffsetSelectNoFilterTest() + { + var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeOffsetMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = reader.GetInt64(0); + var datetimeValue = reader.GetDateTime(1); + var difference = (datetimeValue - DateTimeOffset.MaxValue).Duration(); + Assert.That(difference, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(0.001))); + } + } + } + + [Test] + public void MaxDateTimeOffsetSelectByEqualityTest() + { + var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeOffsetMaxValueTable}\" WHERE \"Value\" = $1"); + var filterP = Connection.CreateParameter(); + dateTimeOffsetTypeMapping.BindValue(filterP, DateTimeOffset.MaxValue); + command.Parameters.Add(filterP); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MaxDateTimeOffsetSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMaxDateTimeOffsetSelectDatePart(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void MaxDateTimeOffsetSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMaxDateTimeOffsetSelectDatePart(); + } + + private void TestMaxDateTimeOffsetSelectDatePart() + { + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Year)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Month)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Day)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Hour)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Minute)); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var yearPart = reader.GetInt64(0); + Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Second)); + } + } + } + + #region Create structure and populate data + private void CreateTablesForDateTimeTests() + { + var createDateTimeTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateTimeMinValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateTimeMinValueTable} PRIMARY KEY, \"Value\" timestamp);"); + using (createDateTimeTableCommand) { + _ = createDateTimeTableCommand.ExecuteNonQuery(); + } + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeMinValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, 1L); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, DateTime.MinValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + + createDateTimeTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateTimeMaxValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateTimeMaxValueTable} PRIMARY KEY, \"Value\" timestamp);"); + using (createDateTimeTableCommand) { + _ = createDateTimeTableCommand.ExecuteNonQuery(); + } + + command = Connection.CreateCommand($"INSERT INTO \"{DateTimeMaxValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, 2L); + _ = command.Parameters.Add(p1); + + p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, DateTime.MaxValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + private void CreateTablesForDateOnlyTests() + { + var createDateOnlyTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateOnlyMinValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateOnlyMinValueTable} PRIMARY KEY, \"Value\" date);"); + using (createDateOnlyTableCommand) { + _ = createDateOnlyTableCommand.ExecuteNonQuery(); + } + + var command = Connection.CreateCommand($"INSERT INTO \"{DateOnlyMinValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, 1L); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateOnlyTypeMapping.BindValue(p2, DateOnly.MinValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + + createDateOnlyTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateOnlyMaxValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateOnlyMaxValueTable} PRIMARY KEY, \"Value\" date);"); + using (createDateOnlyTableCommand) { + _ = createDateOnlyTableCommand.ExecuteNonQuery(); + } + + command = Connection.CreateCommand($"INSERT INTO \"{DateOnlyMaxValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, 2L); + _ = command.Parameters.Add(p1); + + p2 = Connection.CreateParameter(); + dateOnlyTypeMapping.BindValue(p2, DateOnly.MaxValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + private void CreateTablesForDateTimeOffsetTests() + { + var createDateTimeOffsetTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateTimeOffsetMinValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateTimeOffsetMinValueTable} PRIMARY KEY, \"Value\" timestamptz);"); + using (createDateTimeOffsetTableCommand) { + _ = createDateTimeOffsetTableCommand.ExecuteNonQuery(); + } + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeOffsetMinValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, 1L); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + p2.Value = DateTimeOffset.MinValue; + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + + createDateTimeOffsetTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateTimeOffsetMaxValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateTimeOffsetMaxValueTable} PRIMARY KEY, \"Value\" timestamptz);"); + using (createDateTimeOffsetTableCommand) { + _ = createDateTimeOffsetTableCommand.ExecuteNonQuery(); + } + + command = Connection.CreateCommand($"INSERT INTO \"{DateTimeOffsetMaxValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + p1 = Connection.CreateParameter(); + p1.Value = 2; + _ = command.Parameters.Add(p1); + + p2 = Connection.CreateParameter(); + p2.Value = DateTimeOffset.MaxValue; + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + #endregion + + #region Clear structure and data + private void DropTablesForDateTime() + { + var createDateTimeTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateTimeMinValueTable}\";"); + using (createDateTimeTableCommand) { + _ = createDateTimeTableCommand.ExecuteNonQuery(); + } + + createDateTimeTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateTimeMaxValueTable}\";"); + using (createDateTimeTableCommand) { + _ = createDateTimeTableCommand.ExecuteNonQuery(); + } + } + + private void DropTablesForDateOnly() + { + var createDateOnlyTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateOnlyMinValueTable}\";"); + using (createDateOnlyTableCommand) { + _ = createDateOnlyTableCommand.ExecuteNonQuery(); + } + + createDateOnlyTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateOnlyMaxValueTable}\";"); + using (createDateOnlyTableCommand) { + _ = createDateOnlyTableCommand.ExecuteNonQuery(); + } + } + + private void DropTablesForDateTimeOffset() + { + var createDateTimeOffsetTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateTimeOffsetMinValueTable}\" ;"); + using (createDateTimeOffsetTableCommand) { + _ = createDateTimeOffsetTableCommand.ExecuteNonQuery(); + } + + createDateTimeOffsetTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateTimeOffsetMaxValueTable}\" ;"); + using (createDateTimeOffsetTableCommand) { + _ = createDateTimeOffsetTableCommand.ExecuteNonQuery(); + } + } + #endregion + + private void CheckIfInfinityAliasTurnedOn() + { + if (AppContext.TryGetSwitch(Orm.PostgreSql.WellKnown.DateTimeToInfinityConversionSwitchName, out var flag) && flag) { + throw new IgnoreException("Require date to Infinity conversion"); + } + } + + private void CheckIfInfinityAliasTurnedOff() + { + if (!AppContext.TryGetSwitch(Orm.PostgreSql.WellKnown.DateTimeToInfinityConversionSwitchName, out var flag) || !flag) { + throw new IgnoreException("Require no date to Infinity conversion"); + } + } + + } +} diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs new file mode 100644 index 000000000..90b3d3066 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs @@ -0,0 +1,178 @@ +// Copyright (C) 2025 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 NUnit.Framework; + + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + public sealed class LegacyVsCurrentDateTimeOffsetParameterBinding : SqlTest + { + private const string DateTimeOffsetValueTable = "DateTimeOffsetTable"; + + private Xtensive.Sql.TypeMapping longTypeMapping; + private Xtensive.Sql.TypeMapping dateTimeOffsetTypeMapping; + + protected override void CheckRequirements() + { + // do not check provider here. + // Require class uses driver creation which casues AppContext switch setup before TestFixtureSetup() method called + } + + protected override void TestFixtureSetUp() + { + // use one or enother + //EnableLegacyTimestampBehavior(); + // or + DisableLegacyTimestampBehavior(); + + Require.ProviderIs(StorageProvider.PostgreSql); + + base.TestFixtureSetUp(); + + longTypeMapping = Driver.TypeMappings[typeof(long)]; + dateTimeOffsetTypeMapping = Driver.TypeMappings[typeof(DateTimeOffset)]; + + DropTablesForTests(); + + CreateTablesForTests(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteUtcValueLegacy() + { + CheckLegacyTurnedOn(); + + var utcValue = DateTimeOffset.UtcNow; + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeOffsetValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, utcValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeOffsetTypeMapping.BindValue(p2, utcValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteLocalValueLegacy() + { + CheckLegacyTurnedOn(); + + var localKindValue = DateTimeOffset.Now; + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeOffsetValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, localKindValue.Ticks); + p1.DbType = System.Data.DbType.Int64; + p1.Value = localKindValue.Ticks; + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeOffsetTypeMapping.BindValue(p2, localKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteUtcValue() + { + CheckLegacyTurnedOff(); + + var utcKindValue = DateTimeOffset.UtcNow; + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeOffsetValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, utcKindValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeOffsetTypeMapping.BindValue(p2, utcKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteLocalValue() + { + CheckLegacyTurnedOff(); + + var localKindValue = DateTimeOffset.Now; + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeOffsetValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, localKindValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeOffsetTypeMapping.BindValue(p2, localKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + #region Create structure + private void CreateTablesForTests() + { + var createTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateTimeOffsetValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateTimeOffsetValueTable} PRIMARY KEY, \"Value\" timestamp);"); + using (createTableCommand) { + _ = createTableCommand.ExecuteNonQuery(); + } + } + + #endregion + + #region Clear structure + private void DropTablesForTests() + { + var dropTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateTimeOffsetValueTable}\";"); + using (dropTableCommand) { + _ = dropTableCommand.ExecuteNonQuery(); + } + } + #endregion + + private void EnableLegacyTimestampBehavior() + { + AppContext.SetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, true); + } + + private void DisableLegacyTimestampBehavior() + { + AppContext.SetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, false); + } + + + private void CheckLegacyTurnedOn() + { + if (AppContext.TryGetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, out var flag) && !flag) { + throw new IgnoreException("Requires Legacy timestamp behavior"); + } + } + + private void CheckLegacyTurnedOff() + { + if (!AppContext.TryGetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, out var flag) || flag) { + throw new IgnoreException("Requires no Legacy timestamp behavior"); + } + } + } +} diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs new file mode 100644 index 000000000..626ce06a9 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs @@ -0,0 +1,230 @@ +// Copyright (C) 2025 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 NUnit.Framework; + + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + public sealed class LegacyVsCurrentDateTimeParameterBinding : SqlTest + { + private const string DateTimeValueTable = "DateTimeTable"; + + private const string DateTimeOffsetValueTable = "DateTimeOffsetTable1"; + + private Xtensive.Sql.TypeMapping longTypeMapping; + private Xtensive.Sql.TypeMapping dateTimeTypeMapping; + + protected override void CheckRequirements() + { + // do not check provider here. + // Require class uses driver creation which casues AppContext switch setup before TestFixtureSetup() method called + } + + protected override void TestFixtureSetUp() + { + // use one or enother + EnableLegacyTimestampBehavior(); + // or + //DisableLegacyTimestampBehavior(); + + Require.ProviderIs(StorageProvider.PostgreSql); + + base.TestFixtureSetUp(); + + longTypeMapping = Driver.TypeMappings[typeof(long)]; + dateTimeTypeMapping = Driver.TypeMappings[typeof(DateTime)]; + + DropTablesForTests(); + + CreateTablesForTests(); + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteUtcKindDateTimeValueLegacy() + { + CheckLegacyTurnedOn(); + + var utcKindValue = DateTime.UtcNow; + Assert.That(utcKindValue.Kind, Is.EqualTo(DateTimeKind.Utc)); + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, utcKindValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, utcKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteLocalKindDateTimeValueLegacy() + { + CheckLegacyTurnedOn(); + + var localKindValue = DateTime.Now; + Assert.That(localKindValue.Kind, Is.EqualTo(DateTimeKind.Local)); + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, localKindValue.Ticks); + p1.DbType = System.Data.DbType.Int64; + p1.Value = localKindValue.Ticks; + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, localKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteUnspecifiedKindDateTimeValueLegacy() + { + CheckLegacyTurnedOn(); + + var unspecifiedKindValue = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); + Assert.That(unspecifiedKindValue.Kind, Is.EqualTo(DateTimeKind.Unspecified)); + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, unspecifiedKindValue.Ticks); + p1.DbType = System.Data.DbType.Int64; + p1.Value = unspecifiedKindValue.Ticks; + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, unspecifiedKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteUtcKindDateTimeValue() + { + CheckLegacyTurnedOff(); + + var utcKindValue = DateTime.UtcNow; + Assert.That(utcKindValue.Kind, Is.EqualTo(DateTimeKind.Utc)); + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, utcKindValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, utcKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteLocalKindDateTimeValue() + { + CheckLegacyTurnedOff(); + + var localKindValue = DateTime.Now; + Assert.That(localKindValue.Kind, Is.EqualTo(DateTimeKind.Local)); + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, localKindValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, localKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + [Test] + [Explicit("Require manual set of AppContext switch")] + public void WriteUnspecifiedKindDateTimeValue() + { + CheckLegacyTurnedOff(); + + var unspecifiedKindValue = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); + Assert.That(unspecifiedKindValue.Kind, Is.EqualTo(DateTimeKind.Unspecified)); + + var command = Connection.CreateCommand($"INSERT INTO \"{DateTimeValueTable}\"(\"Id\", \"Value\") VALUES ($1, $2)"); + var p1 = Connection.CreateParameter(); + longTypeMapping.BindValue(p1, unspecifiedKindValue.Ticks); + _ = command.Parameters.Add(p1); + + var p2 = Connection.CreateParameter(); + dateTimeTypeMapping.BindValue(p2, unspecifiedKindValue); + _ = command.Parameters.Add(p2); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + #region Create structure + private void CreateTablesForTests() + { + var createTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{DateTimeValueTable}\" (\"Id\" bigint CONSTRAINT PK_{DateTimeValueTable} PRIMARY KEY, \"Value\" timestamp);"); + using (createTableCommand) { + _ = createTableCommand.ExecuteNonQuery(); + } + } + + #endregion + + #region Clear structure + private void DropTablesForTests() + { + var dropTableCommand = Connection.CreateCommand($"DROP TABLE IF EXISTS \"{DateTimeValueTable}\";"); + using (dropTableCommand) { + _ = dropTableCommand.ExecuteNonQuery(); + } + } + #endregion + + private void EnableLegacyTimestampBehavior() + { + AppContext.SetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, true); + } + + private void DisableLegacyTimestampBehavior() + { + AppContext.SetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, false); + } + + + private void CheckLegacyTurnedOn() + { + if (AppContext.TryGetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, out var flag) && !flag) { + throw new IgnoreException("Requires Legacy timestamp behavior"); + } + } + + private void CheckLegacyTurnedOff() + { + if (!AppContext.TryGetSwitch(Orm.PostgreSql.WellKnown.LegacyTimestampBehaviorSwitchName, out var flag) || flag) { + throw new IgnoreException("Requires no Legacy timestamp behavior"); + } + } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs new file mode 100644 index 000000000..76a5248a6 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs @@ -0,0 +1,329 @@ +// Copyright (C) 2025 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.Threading.Tasks; +using NUnit.Framework; +using Xtensive.Sql; + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + internal class NpgsqlIntervalMappingTest : SqlTest + { + private const string IdColumnName = "Id"; + private const string ValueColumnName = "Value"; + private const string TableName = "NpgsqlIntervalTest"; + + #region Test case sources + private static TimeSpan[] SecondsCases + { + get => new[] { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(15), + TimeSpan.FromSeconds(27), + TimeSpan.FromSeconds(30), + TimeSpan.FromSeconds(43), + TimeSpan.FromSeconds(55), + TimeSpan.FromSeconds(59), + + TimeSpan.FromSeconds(1).Negate(), + TimeSpan.FromSeconds(10).Negate(), + TimeSpan.FromSeconds(15).Negate(), + TimeSpan.FromSeconds(27).Negate(), + TimeSpan.FromSeconds(30).Negate(), + TimeSpan.FromSeconds(43).Negate(), + TimeSpan.FromSeconds(55).Negate(), + TimeSpan.FromSeconds(59).Negate(), + }; + } + + private static TimeSpan[] MinutesCases + { + get => new[] { + TimeSpan.FromMinutes(1), + TimeSpan.FromMinutes(10), + TimeSpan.FromMinutes(15), + TimeSpan.FromMinutes(27), + TimeSpan.FromMinutes(30), + TimeSpan.FromMinutes(43), + TimeSpan.FromMinutes(55), + TimeSpan.FromMinutes(59), + + TimeSpan.FromMinutes(1).Negate(), + TimeSpan.FromMinutes(10).Negate(), + TimeSpan.FromMinutes(15).Negate(), + TimeSpan.FromMinutes(27).Negate(), + TimeSpan.FromMinutes(30).Negate(), + TimeSpan.FromMinutes(43).Negate(), + TimeSpan.FromMinutes(55).Negate(), + TimeSpan.FromMinutes(59).Negate(), + }; + } + + private static TimeSpan[] HoursCases + { + get => new[] { + TimeSpan.FromHours(1), + TimeSpan.FromHours(10), + TimeSpan.FromHours(15), + TimeSpan.FromHours(20), + TimeSpan.FromHours(23), + + TimeSpan.FromHours(1).Negate(), + TimeSpan.FromHours(10).Negate(), + TimeSpan.FromHours(15).Negate(), + TimeSpan.FromHours(20).Negate(), + TimeSpan.FromHours(23).Negate(), + }; + } + + private static TimeSpan[] LessThanMonthCases + { + get => new[] { + TimeSpan.FromDays(1), + TimeSpan.FromDays(10), + TimeSpan.FromDays(15), + TimeSpan.FromDays(20), + TimeSpan.FromDays(23), + TimeSpan.FromDays(28), + TimeSpan.FromDays(29), + + TimeSpan.FromDays(1).Negate(), + TimeSpan.FromDays(10).Negate(), + TimeSpan.FromDays(15).Negate(), + TimeSpan.FromDays(20).Negate(), + TimeSpan.FromDays(23).Negate(), + TimeSpan.FromDays(28).Negate(), + TimeSpan.FromDays(29).Negate(), + }; + } + + private static TimeSpan[] MoreThanMonthCases + { + get => new[] { + TimeSpan.FromDays(32), + TimeSpan.FromDays(40), + TimeSpan.FromDays(65), + TimeSpan.FromDays(181), + TimeSpan.FromDays(182), + TimeSpan.FromDays(360), + TimeSpan.FromDays(363), + TimeSpan.FromDays(364), + TimeSpan.FromDays(365), + TimeSpan.FromDays(366), + TimeSpan.FromDays(730), + + TimeSpan.FromDays(32).Negate(), + TimeSpan.FromDays(40).Negate(), + TimeSpan.FromDays(65).Negate(), + TimeSpan.FromDays(181).Negate(), + TimeSpan.FromDays(182).Negate(), + TimeSpan.FromDays(360).Negate(), + TimeSpan.FromDays(363).Negate(), + TimeSpan.FromDays(364).Negate(), + TimeSpan.FromDays(365).Negate(), + TimeSpan.FromDays(366).Negate(), + TimeSpan.FromDays(730).Negate(), + }; + } + + private static TimeSpan[] MultipartValuesSource + { + get => new[] { + TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)), + TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)), + TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)), + TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)), + TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)), + TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)), + TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)), + TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)), + TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))), + TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))), + TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))), + TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))), + TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))), + TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))), + TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))), + TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))), + TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))), + TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))), + TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))), + TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))), + TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))), + TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + + TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)).Negate(), + TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)).Negate(), + TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)).Negate(), + TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)).Negate(), + TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)).Negate(), + TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)).Negate(), + TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)).Negate(), + TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)).Negate(), + TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))).Negate(), + TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))).Negate(), + TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))).Negate(), + TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))).Negate(), + TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))).Negate(), + TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(), + TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(), + TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(), + TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(), + TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(), + TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(), + TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(), + TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(), + TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + }; + } + #endregion + + private TypeMapping longMapping; + private TypeMapping timeSpanMapping; + + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + + longMapping = Driver.TypeMappings[typeof(long)]; + timeSpanMapping = Driver.TypeMappings[typeof(TimeSpan)]; + + var dropTableCommand = Connection + .CreateCommand( + $"DROP TABLE IF EXISTS \"{TableName}\";"); + using (dropTableCommand) { + _ = dropTableCommand.ExecuteNonQuery(); + } + + var createTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{TableName}\" (\"{IdColumnName}\" bigint CONSTRAINT PK_{TableName} PRIMARY KEY, \"{ValueColumnName}\" interval);"); + using (createTableCommand) { + _ = createTableCommand.ExecuteNonQuery(); + } + } + + protected override void TestFixtureTearDown() + { + longMapping = null; + timeSpanMapping = null; + + base.TestFixtureTearDown(); + } + + [Test] + [TestCaseSource(nameof(MultipartValuesSource))] + public void MultipartValueTest(TimeSpan testCase) + { + TestValue(testCase); + } + + [Test] + [TestCaseSource(nameof(SecondsCases))] + public void SecondsTest(TimeSpan testCase) + { + TestValue(testCase); + } + + + [Test] + [TestCaseSource(nameof(MinutesCases))] + public void MinutesTest(TimeSpan testCase) + { + TestValue(testCase); + } + + [Test] + [TestCaseSource(nameof(HoursCases))] + public void HoursTest(TimeSpan testCase) + { + TestValue(testCase); + } + + [Test] + [TestCaseSource(nameof(LessThanMonthCases))] + public void DaysTest(TimeSpan testCase) + { + TestValue(testCase); + } + + [Test] + [TestCaseSource(nameof(MoreThanMonthCases))] + public void DaysMoreThanMonthTest(TimeSpan testCase) + { + TestValue(testCase); + } + + + private void TestValue(TimeSpan testCase) + { + InsertValue(testCase.Ticks, testCase); + var rowFromDb = SelectValue(testCase.Ticks); + + Assert.That(TimeSpan.FromTicks(rowFromDb.Item1), Is.EqualTo(testCase)); + Assert.That(rowFromDb.Item2, Is.EqualTo(testCase)); + } + + private void InsertValue(long id, TimeSpan testCase) + { + var command = Connection.CreateCommand($"INSERT INTO \"{TableName}\"(\"{IdColumnName}\", \"{ValueColumnName}\") VALUES (@pId, @pValue)"); + var pId = Connection.CreateParameter(); + pId.ParameterName = "pId"; + longMapping.BindValue(pId, id); + _ = command.Parameters.Add(pId); + + var pValue = Connection.CreateParameter(); + pValue.ParameterName = "pValue"; + timeSpanMapping.BindValue(pValue, testCase); + _ = command.Parameters.Add(pValue); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + private (long, TimeSpan) SelectValue(long id) + { + var command = Connection.CreateCommand($"SELECT \"{IdColumnName}\", \"{ValueColumnName}\" FROM \"{TableName}\" WHERE \"{IdColumnName}\" = @pId"); + var pId = Connection.CreateParameter(); + pId.ParameterName = "pId"; + longMapping.BindValue(pId, id); + _ = command.Parameters.Add(pId); + + TimeSpan result = default; + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read() || result == default) { + var idFromDb = (long) longMapping.ReadValue(reader, 0); + var valueFromDb = (TimeSpan) timeSpanMapping.ReadValue(reader, 1); + return (idFromDb, valueFromDb); + } + } + + return default; + } + } +} From 3b35b2c12070fc0f17cf7f291792148c15a2e413 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 18:15:28 +0500 Subject: [PATCH 13/48] Tests for operations over Min/Max values of DateOnly/DateTime/DateTimeOffset Since we have to support Npgsql conversion of the values to infinity/-infinity we need to test such behavior --- .../TestHelper.cs | 8 + .../DateTimeAndDateTimeOffset/BaseTest.cs | 17 + .../DateOnly/DateOnlyToStringTest.cs | 10 + .../DateOnly/OperationsTest.cs | 36 ++ .../DateOnly/PartsExtractionTest.cs | 61 +++ .../DateTime/DateTimeToIsoTest.cs | 18 +- .../DateTime/OperationsTest.cs | 336 ++++++++++----- .../DateTime/PartsExtractionTest.cs | 137 +++++++ .../DateTimeBaseTest.cs | 5 + .../DateTimeOffset/OperationsTest.cs | 381 +++++++++++++----- .../DateTimeOffset/PartsExtractionTest.cs | 216 +++++----- .../DateTimeOffset/WhereTest.cs | 30 +- .../DateTimeOffsetBaseTest.cs | 81 ++-- .../Linq/DateTimeAndDateTimeOffset/Model.cs | 77 ++++ 14 files changed, 1059 insertions(+), 354 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs b/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs index e53e5398c..c9f0494b4 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs @@ -53,6 +53,14 @@ public static System.Configuration.Configuration GetConfigurationForAssembly(thi return instanceOfTypeFromAssembly.GetType().Assembly.GetAssemblyConfiguration(); } + public static DateTimeOffset AdjustDateTimeOffsetForCurrentProvider(this DateTimeOffset origin) + { + var baseDateTime = origin.DateTime; + var offset = origin.Offset; + + return new DateTimeOffset(baseDateTime.AdjustDateTimeForCurrentProvider(), offset); + } + /// /// Cuts down resolution of value if needed, according to current . /// diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs index dbb8671b4..15e3beb19 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs @@ -75,7 +75,9 @@ protected void RunTest(Expression> filter, int rightCount = 1) protected void RunTest(Session session, Expression> filter, int rightCount = 1) where T : Entity { + session.Events.DbCommandExecuting += Events_DbCommandExecuting; var count = session.Query.All().Count(filter); + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; Assert.AreEqual(rightCount, count); } @@ -90,5 +92,20 @@ protected void RunWrongTest(Session session, Expression> filter { RunTest(session, filter, 0); } + + private void Events_DbCommandExecuting(object sender, DbCommandEventArgs e) + { + var command = e.Command; + var commandText = command.CommandText; + Console.WriteLine("No Modifications SQL Text:"); + Console.WriteLine(commandText); + var parameters = command.Parameters; + + Console.Write(" Parameters: "); + for (int i = 0, count = parameters.Count; i < count; i++) { + var parameter = parameters[i]; + Console.WriteLine($"{parameter.ParameterName} = {parameter.Value}"); + } + } } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs index 8f749ea4b..6fb699db2 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs @@ -2,6 +2,7 @@ // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. +using System; using NUnit.Framework; using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; @@ -17,5 +18,14 @@ public void ToStringTest() RunWrongTest(s, c => c.DateOnly.ToString("o") == FirstDateOnly.AddDays(1).ToString("o")); }); } + + [Test] + public void MinMaxToStringTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.ToString("o") == DateOnly.MinValue.ToString("o")); + RunTest(s, c => c.MaxValue.ToString("o") == DateOnly.MaxValue.ToString("o")); + }); + } } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs index cb732ef83..cbc71efea 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs @@ -22,6 +22,18 @@ public void AddYearsTest() }); } + [Test] + public void AddYearsToMinMaxValuesTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddYears(1) == DateOnly.MinValue.AddYears(1)); + RunTest(s, c => c.MaxValue.AddYears(-33) == DateOnly.MaxValue.AddYears(-33)); + + RunWrongTest(s, c => c.MinValue.AddYears(1) == DateOnly.MinValue.AddYears(2)); + RunWrongTest(s, c => c.MaxValue.AddYears(-33) == DateOnly.MaxValue.AddYears(-34)); + }); + } + [Test] public void AddMonthsTest() { @@ -34,6 +46,18 @@ public void AddMonthsTest() }); } + [Test] + public void AddMonthsToMinMaxValues() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMonths(1) == DateOnly.MinValue.AddMonths(1)); + RunTest(s, c => c.MaxValue.AddMonths(-33) == DateOnly.MaxValue.AddMonths(-33)); + + RunWrongTest(s, c => c.MinValue.AddMonths(1) == DateOnly.MinValue.AddMonths(2)); + RunWrongTest(s, c => c.MaxValue.AddMonths(-33) == DateOnly.MaxValue.AddMonths(-34)); + }); + } + [Test] public void AddDaysTest() { @@ -45,5 +69,17 @@ public void AddDaysTest() RunWrongTest(s, c => c.NullableDateOnly.Value.AddDays(33) == NullableDateOnly.AddDays(44)); }); } + + [Test] + public void AddDaysToMinMaxValues() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddDays(1) == DateOnly.MinValue.AddDays(1)); + RunTest(s, c => c.MaxValue.AddDays(-33) == DateOnly.MaxValue.AddDays(-33)); + + RunWrongTest(s, c => c.MinValue.AddDays(1) == DateOnly.MinValue.AddDays(2)); + RunWrongTest(s, c => c.MaxValue.AddDays(-33) == DateOnly.MaxValue.AddDays(-34)); + }); + } } } \ 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 index 5703e2a9b..584acf15f 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs @@ -22,6 +22,19 @@ public void ExtractYearTest() }); } + + [Test] + public void MinMaxExtractYearTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Year == DateTime.MinValue.Year); + RunTest(s, c => c.MaxValue.Year == DateTime.MaxValue.Year); + + RunWrongTest(s, c => c.MinValue.Year == WrongDateOnly.Year); + RunWrongTest(s, c => c.MaxValue.Year == WrongDateOnly.Year); + }); + } + [Test] public void ExtractMonthTest() { @@ -41,6 +54,18 @@ public void ExtractMonthTest() }); } + [Test] + public void MinMaxExtractMonthTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Month == DateTime.MinValue.Month); + RunTest(s, c => c.MaxValue.Month == DateTime.MaxValue.Month); + + RunWrongTest(s, c => c.MinValue.Month == WrongDateOnly.Month); + RunWrongTest(s, c => c.MaxValue.Month == WrongDateOnly.Month); + }); + } + [Test] public void ExtractDayTest() { @@ -53,6 +78,18 @@ public void ExtractDayTest() }); } + [Test] + public void MinMaxExtractDayTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Day == DateTime.MinValue.Day); + RunTest(s, c => c.MaxValue.Day == DateTime.MaxValue.Day); + + RunWrongTest(s, c => c.MinValue.Day == WrongDateOnly.Day); + RunWrongTest(s, c => c.MaxValue.Day == WrongDateOnly.Day); + }); + } + [Test] public void ExtractDayOfYearTest() { @@ -65,6 +102,18 @@ public void ExtractDayOfYearTest() }); } + [Test] + public void MinMaxExtractDayOfYearTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfYear == DateTime.MinValue.DayOfYear); + RunTest(s, c => c.MaxValue.DayOfYear == DateTime.MaxValue.DayOfYear); + + RunWrongTest(s, c => c.MinValue.DayOfYear == WrongDateOnly.DayOfYear); + RunWrongTest(s, c => c.MaxValue.DayOfYear == WrongDateOnly.DayOfYear); + }); + } + [Test] public void ExtractDayOfWeekTest() { @@ -76,5 +125,17 @@ public void ExtractDayOfWeekTest() RunWrongTest(s, c => c.NullableDateOnly.Value.DayOfWeek == WrongDateOnly.DayOfWeek); }); } + + [Test] + public void MinMaxExtractDayOfWeekTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfWeek == DateTime.MinValue.DayOfWeek); + RunTest(s, c => c.MaxValue.DayOfWeek == DateTime.MaxValue.DayOfWeek); + + RunWrongTest(s, c => c.MinValue.DayOfWeek == DateTime.MinValue.AddDays(1).DayOfWeek); + RunWrongTest(s, c => c.MaxValue.DayOfWeek == DateTime.MaxValue.AddDays(-1).DayOfWeek); + }); + } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs index 48370ebfa..86f966301 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs @@ -4,6 +4,7 @@ // Created by: Alex Groznov // Created: 2016.08.01 +using System; using NUnit.Framework; using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; @@ -15,8 +16,21 @@ public class DateTimeToIsoTest : DateTimeBaseTest public void ToIsoStringTest() { ExecuteInsideSession((s) => { - RunTest(s, c => c.DateTime.ToString("s")==FirstDateTime.ToString("s")); - RunWrongTest(s, c => c.DateTime.ToString("s")==FirstDateTime.AddMinutes(1).ToString("s")); + RunTest(s, c => c.DateTime.ToString("s") == FirstDateTime.ToString("s")); + RunWrongTest(s, c => c.DateTime.ToString("s") == FirstDateTime.AddMinutes(1).ToString("s")); + }); + } + + + [Test] + public void MinMaxValuesToIsoStringTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.ToString("s") == DateTime.MinValue.ToString("s")); + RunTest(s, c => c.MaxValue.ToString("s") == DateTime.MaxValue.ToString("s")); + + RunWrongTest(s, c => c.MinValue.ToString("s") == FirstDateTime.AddMinutes(1).ToString("s")); + RunWrongTest(s, c => c.MaxValue.ToString("s") == FirstDateTime.AddMinutes(1).ToString("s")); }); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs index 3b7387221..48fb3cb40 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs @@ -4,6 +4,7 @@ // Created by: Alex Groznov // Created: 2016.08.01 +using System; using NUnit.Framework; using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; @@ -14,84 +15,162 @@ public class OperationsTest : DateTimeBaseTest [Test] public void AddYearsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.AddYears(1) == FirstDateTime.AddYears(1)); - RunTest(c => c.MillisecondDateTime.AddYears(-2) == FirstMillisecondDateTime.AddYears(-2)); - RunTest(c => c.NullableDateTime.Value.AddYears(33) == NullableDateTime.AddYears(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.AddYears(1) == FirstDateTime.AddYears(1)); + RunTest(s, c => c.MillisecondDateTime.AddYears(-2) == FirstMillisecondDateTime.AddYears(-2)); + RunTest(s, c => c.NullableDateTime.Value.AddYears(33) == NullableDateTime.AddYears(33)); - RunWrongTest(c => c.DateTime.AddYears(1) == FirstDateTime.AddYears(2)); - RunWrongTest(c => c.MillisecondDateTime.AddYears(-1) == FirstMillisecondDateTime.AddYears(-2)); - RunWrongTest(c => c.NullableDateTime.Value.AddYears(33) == NullableDateTime.AddYears(44)); + RunWrongTest(s, c => c.DateTime.AddYears(1) == FirstDateTime.AddYears(2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddYears(-1) == FirstMillisecondDateTime.AddYears(-2)); + RunWrongTest(s, c => c.NullableDateTime.Value.AddYears(33) == NullableDateTime.AddYears(44)); + }); + } + + [Test] + public void MinMaxValueAddYearsTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddYears(5) == DateTime.MinValue.AddYears(5)); + RunTest(s, c => c.MaxValue.AddYears(-5) == DateTime.MaxValue.AddYears(-5)); + + RunWrongTest(s, c => c.MinValue.AddYears(5) == DateTime.MinValue.AddYears(2)); + RunWrongTest(s, c => c.MaxValue.AddYears(-5) == DateTime.MaxValue.AddYears(-2)); }); } [Test] public void AddMonthsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.AddMonths(1) == FirstDateTime.AddMonths(1)); - RunTest(c => c.MillisecondDateTime.AddMonths(-2) == FirstMillisecondDateTime.AddMonths(-2)); - RunTest(c => c.NullableDateTime.Value.AddMonths(33) == NullableDateTime.AddMonths(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.AddMonths(1) == FirstDateTime.AddMonths(1)); + RunTest(s, c => c.MillisecondDateTime.AddMonths(-2) == FirstMillisecondDateTime.AddMonths(-2)); + RunTest(s, c => c.NullableDateTime.Value.AddMonths(33) == NullableDateTime.AddMonths(33)); - RunWrongTest(c => c.DateTime.AddMonths(1) == FirstDateTime.AddMonths(2)); - RunWrongTest(c => c.MillisecondDateTime.AddMonths(-1) == FirstMillisecondDateTime.AddMonths(-2)); - RunWrongTest(c => c.NullableDateTime.Value.AddMonths(33) == NullableDateTime.AddMonths(44)); + RunWrongTest(s, c => c.DateTime.AddMonths(1) == FirstDateTime.AddMonths(2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddMonths(-1) == FirstMillisecondDateTime.AddMonths(-2)); + RunWrongTest(s, c => c.NullableDateTime.Value.AddMonths(33) == NullableDateTime.AddMonths(44)); + }); + } + + [Test] + public void MinMaxValueAddMonthsTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMonths(5) == DateTime.MinValue.AddMonths(5)); + RunTest(s, c => c.MaxValue.AddMonths(-5) == DateTime.MaxValue.AddMonths(-5)); + + RunWrongTest(s, c => c.MinValue.AddMonths(5) == DateTime.MinValue.AddMonths(2)); + RunWrongTest(s, c => c.MaxValue.AddMonths(-5) == DateTime.MaxValue.AddMonths(-2)); }); } [Test] public void AddDaysTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.AddDays(1) == FirstDateTime.AddDays(1)); - RunTest(c => c.MillisecondDateTime.AddDays(-2) == FirstMillisecondDateTime.AddDays(-2)); - RunTest(c => c.NullableDateTime.Value.AddDays(33) == NullableDateTime.AddDays(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.AddDays(1) == FirstDateTime.AddDays(1)); + RunTest(s, c => c.MillisecondDateTime.AddDays(-2) == FirstMillisecondDateTime.AddDays(-2)); + RunTest(s, c => c.NullableDateTime.Value.AddDays(33) == NullableDateTime.AddDays(33)); + + RunWrongTest(s, c => c.DateTime.AddDays(1) == FirstDateTime.AddDays(2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddDays(-1) == FirstMillisecondDateTime.AddDays(-2)); + RunWrongTest(s, c => c.NullableDateTime.Value.AddDays(33) == NullableDateTime.AddDays(44)); + }); + } + + [Test] + public void MinMaxValueAddDaysTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddDays(5) == DateTime.MinValue.AddDays(5)); + RunTest(s, c => c.MaxValue.AddDays(-5) == DateTime.MaxValue.AddDays(-5)); - RunWrongTest(c => c.DateTime.AddDays(1) == FirstDateTime.AddDays(2)); - RunWrongTest(c => c.MillisecondDateTime.AddDays(-1) == FirstMillisecondDateTime.AddDays(-2)); - RunWrongTest(c => c.NullableDateTime.Value.AddDays(33) == NullableDateTime.AddDays(44)); + RunWrongTest(s, c => c.MinValue.AddDays(5) == DateTime.MinValue.AddDays(2)); + RunWrongTest(s, c => c.MaxValue.AddDays(-5) == DateTime.MaxValue.AddDays(-2)); }); } [Test] public void AddHoursTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.AddHours(1) == FirstDateTime.AddHours(1)); - RunTest(c => c.MillisecondDateTime.AddHours(-2) == FirstMillisecondDateTime.AddHours(-2)); - RunTest(c => c.NullableDateTime.Value.AddHours(33) == NullableDateTime.AddHours(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.AddHours(1) == FirstDateTime.AddHours(1)); + RunTest(s, c => c.MillisecondDateTime.AddHours(-2) == FirstMillisecondDateTime.AddHours(-2)); + RunTest(s, c => c.NullableDateTime.Value.AddHours(33) == NullableDateTime.AddHours(33)); - RunWrongTest(c => c.DateTime.AddHours(1) == FirstDateTime.AddHours(2)); - RunWrongTest(c => c.MillisecondDateTime.AddHours(-1) == FirstMillisecondDateTime.AddHours(-2)); - RunWrongTest(c => c.NullableDateTime.Value.AddHours(33) == NullableDateTime.AddHours(44)); + RunWrongTest(s, c => c.DateTime.AddHours(1) == FirstDateTime.AddHours(2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddHours(-1) == FirstMillisecondDateTime.AddHours(-2)); + RunWrongTest(s, c => c.NullableDateTime.Value.AddHours(33) == NullableDateTime.AddHours(44)); + }); + } + + [Test] + public void MinMaxValueAddHoursTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddHours(5) == DateTime.MinValue.AddHours(5)); + RunTest(s, c => c.MaxValue.AddHours(-5) == DateTime.MaxValue.AddHours(-5)); + + RunWrongTest(s, c => c.MinValue.AddHours(5) == DateTime.MinValue.AddHours(2)); + RunWrongTest(s, c => c.MaxValue.AddHours(-5) == DateTime.MaxValue.AddHours(-2)); }); } [Test] public void AddMinutesTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.AddMinutes(1) == FirstDateTime.AddMinutes(1)); - RunTest(c => c.MillisecondDateTime.AddMinutes(-2) == FirstMillisecondDateTime.AddMinutes(-2)); - RunTest(c => c.NullableDateTime.Value.AddMinutes(33) == NullableDateTime.AddMinutes(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.AddMinutes(1) == FirstDateTime.AddMinutes(1)); + RunTest(s, c => c.MillisecondDateTime.AddMinutes(-2) == FirstMillisecondDateTime.AddMinutes(-2)); + RunTest(s, c => c.NullableDateTime.Value.AddMinutes(33) == NullableDateTime.AddMinutes(33)); + + RunWrongTest(s, c => c.DateTime.AddMinutes(1) == FirstDateTime.AddMinutes(2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddMinutes(-1) == FirstMillisecondDateTime.AddMinutes(-2)); + RunWrongTest(s, c => c.NullableDateTime.Value.AddMinutes(33) == NullableDateTime.AddMinutes(44)); + }); + } + + [Test] + public void MinMaxValueAddMinutesTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMinutes(5) == DateTime.MinValue.AddMinutes(5)); + RunTest(s, c => c.MaxValue.AddMinutes(-5) == DateTime.MaxValue.AddMinutes(-5)); - RunWrongTest(c => c.DateTime.AddMinutes(1) == FirstDateTime.AddMinutes(2)); - RunWrongTest(c => c.MillisecondDateTime.AddMinutes(-1) == FirstMillisecondDateTime.AddMinutes(-2)); - RunWrongTest(c => c.NullableDateTime.Value.AddMinutes(33) == NullableDateTime.AddMinutes(44)); + RunWrongTest(s, c => c.MinValue.AddMinutes(5) == DateTime.MinValue.AddMinutes(2)); + RunWrongTest(s, c => c.MaxValue.AddMinutes(-5) == DateTime.MaxValue.AddMinutes(-2)); }); } [Test] public void AddSecondsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.AddSeconds(1) == FirstDateTime.AddSeconds(1)); - RunTest(c => c.MillisecondDateTime.AddSeconds(-2) == FirstMillisecondDateTime.AddSeconds(-2)); - RunTest(c => c.NullableDateTime.Value.AddSeconds(33) == NullableDateTime.AddSeconds(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.AddSeconds(1) == FirstDateTime.AddSeconds(1)); + RunTest(s, c => c.MillisecondDateTime.AddSeconds(-2) == FirstMillisecondDateTime.AddSeconds(-2)); + RunTest(s, c => c.NullableDateTime.Value.AddSeconds(33) == NullableDateTime.AddSeconds(33)); - RunWrongTest(c => c.DateTime.AddSeconds(1) == FirstDateTime.AddSeconds(2)); - RunWrongTest(c => c.MillisecondDateTime.AddSeconds(-1) == FirstMillisecondDateTime.AddSeconds(-2)); - RunWrongTest(c => c.NullableDateTime.Value.AddSeconds(33) == NullableDateTime.AddSeconds(44)); + RunWrongTest(s, c => c.DateTime.AddSeconds(1) == FirstDateTime.AddSeconds(2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddSeconds(-1) == FirstMillisecondDateTime.AddSeconds(-2)); + RunWrongTest(s, c => c.NullableDateTime.Value.AddSeconds(33) == NullableDateTime.AddSeconds(44)); + }); + } + + [Test] + public void MinMaxValueAddSecondsTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddSeconds(5) == DateTime.MinValue.AddSeconds(5)); + RunTest(s, c => c.MaxValue.AddSeconds(-5) == DateTime.MaxValue.AddSeconds(-5)); + + RunWrongTest(s, c => c.MinValue.AddSeconds(5) == DateTime.MinValue.AddSeconds(2)); + RunWrongTest(s, c => c.MaxValue.AddSeconds(-5) == DateTime.MaxValue.AddSeconds(-2)); }); } @@ -99,37 +178,68 @@ public void AddSecondsTest() public void AddMillisecondsTest() { Require.ProviderIsNot(StorageProvider.MySql); - ExecuteInsideSession(() => { - RunTest(c => c.MillisecondDateTime.AddMilliseconds(-2) == FirstMillisecondDateTime.AddMilliseconds(-2)); - RunWrongTest(c => c.MillisecondDateTime.AddMilliseconds(-1) == FirstMillisecondDateTime.AddMilliseconds(-2)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MillisecondDateTime.AddMilliseconds(-2) == FirstMillisecondDateTime.AddMilliseconds(-2)); + RunWrongTest(s, c => c.MillisecondDateTime.AddMilliseconds(-1) == FirstMillisecondDateTime.AddMilliseconds(-2)); + }); + } + + [Test] + public void MinMaxValueAddMillisecondsTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMilliseconds(5) == DateTime.MinValue.AddMilliseconds(5)); + RunTest(s, c => c.MaxValue.AddMilliseconds(-5) == DateTime.MaxValue.AddMilliseconds(-5)); + + RunWrongTest(s, c => c.MinValue.AddMilliseconds(5) == DateTime.MinValue.AddMilliseconds(2)); + RunWrongTest(s, c => c.MaxValue.AddMilliseconds(-5) == DateTime.MaxValue.AddMilliseconds(-2)); }); } [Test] public void AddTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Add(FirstOffset) == FirstDateTime.Add(FirstOffset)); - RunTest(c => c.MillisecondDateTime.Add(SecondOffset) == FirstMillisecondDateTime.Add(SecondOffset)); - RunTest(c => c.NullableDateTime.Value.Add(FirstOffset) == NullableDateTime.Add(FirstOffset)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Add(FirstOffset) == FirstDateTime.Add(FirstOffset)); + RunTest(s, c => c.MillisecondDateTime.Add(SecondOffset) == FirstMillisecondDateTime.Add(SecondOffset)); + RunTest(s, c => c.NullableDateTime.Value.Add(FirstOffset) == NullableDateTime.Add(FirstOffset)); + + RunWrongTest(s, c => c.DateTime.Add(FirstOffset) == FirstDateTime.Add(WrongOffset)); + RunWrongTest(s, c => c.MillisecondDateTime.Add(SecondOffset) == FirstMillisecondDateTime.Add(WrongOffset)); + RunWrongTest(s, c => c.NullableDateTime.Value.Add(FirstOffset) == NullableDateTime.Add(WrongOffset)); + }); + } - RunWrongTest(c => c.DateTime.Add(FirstOffset) == FirstDateTime.Add(WrongOffset)); - RunWrongTest(c => c.MillisecondDateTime.Add(SecondOffset) == FirstMillisecondDateTime.Add(WrongOffset)); - RunWrongTest(c => c.NullableDateTime.Value.Add(FirstOffset) == NullableDateTime.Add(WrongOffset)); + [Test] + public void MinValueAddTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Add(FirstOffset) == DateTime.MinValue.Add(FirstOffset)); + RunWrongTest(s, c => c.MinValue.Add(FirstOffset) == DateTime.MinValue.Add(WrongOffset)); }); } [Test] public void SubtractTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Subtract(FirstOffset) == FirstDateTime.Subtract(FirstOffset)); - RunTest(c => c.MillisecondDateTime.Subtract(SecondOffset) == FirstMillisecondDateTime.Subtract(SecondOffset)); - RunTest(c => c.NullableDateTime.Value.Subtract(FirstOffset) == NullableDateTime.Subtract(FirstOffset)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Subtract(FirstOffset) == FirstDateTime.Subtract(FirstOffset)); + RunTest(s, c => c.MillisecondDateTime.Subtract(SecondOffset) == FirstMillisecondDateTime.Subtract(SecondOffset)); + RunTest(s, c => c.NullableDateTime.Value.Subtract(FirstOffset) == NullableDateTime.Subtract(FirstOffset)); + + RunWrongTest(s, c => c.DateTime.Subtract(FirstOffset) == FirstDateTime.Subtract(WrongOffset)); + RunWrongTest(s, c => c.MillisecondDateTime.Subtract(SecondOffset) == FirstMillisecondDateTime.Subtract(WrongOffset)); + RunWrongTest(s, c => c.NullableDateTime.Value.Subtract(FirstOffset) == NullableDateTime.Subtract(WrongOffset)); + }); + } - RunWrongTest(c => c.DateTime.Subtract(FirstOffset) == FirstDateTime.Subtract(WrongOffset)); - RunWrongTest(c => c.MillisecondDateTime.Subtract(SecondOffset) == FirstMillisecondDateTime.Subtract(WrongOffset)); - RunWrongTest(c => c.NullableDateTime.Value.Subtract(FirstOffset) == NullableDateTime.Subtract(WrongOffset)); + [Test] + public void MaxValueSubtractTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Subtract(FirstOffset) == DateTime.MaxValue.Subtract(FirstOffset)); + RunWrongTest(s, c => c.MaxValue.Subtract(FirstOffset) == DateTime.MaxValue.Subtract(WrongOffset)); }); } @@ -137,42 +247,70 @@ public void SubtractTimeSpanTest() public void SubtractDateTimeTest() { Require.ProviderIsNot(StorageProvider.MySql); - ExecuteInsideSession(() => { - RunTest(c => c.DateTime.Subtract(SecondDateTime) == FirstDateTime.Subtract(SecondDateTime)); - RunTest(c => c.MillisecondDateTime.Subtract(SecondDateTime) == FirstMillisecondDateTime.Subtract(SecondDateTime)); - RunTest(c => c.NullableDateTime.Value.Subtract(SecondDateTime) == NullableDateTime.Subtract(SecondDateTime)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime.Subtract(SecondDateTime) == FirstDateTime.Subtract(SecondDateTime)); + RunTest(s, c => c.MillisecondDateTime.Subtract(SecondDateTime) == FirstMillisecondDateTime.Subtract(SecondDateTime)); + RunTest(s, c => c.NullableDateTime.Value.Subtract(SecondDateTime) == NullableDateTime.Subtract(SecondDateTime)); + + RunWrongTest(s, c => c.DateTime.Subtract(SecondDateTime) == FirstDateTime.Subtract(WrongDateTime)); + RunWrongTest(s, c => c.MillisecondDateTime.Subtract(SecondDateTime) == FirstMillisecondDateTime.Subtract(WrongDateTime)); + RunWrongTest(s, c => c.NullableDateTime.Value.Subtract(SecondDateTime) == NullableDateTime.Subtract(WrongDateTime)); + }); + } - RunWrongTest(c => c.DateTime.Subtract(SecondDateTime) == FirstDateTime.Subtract(WrongDateTime)); - RunWrongTest(c => c.MillisecondDateTime.Subtract(SecondDateTime) == FirstMillisecondDateTime.Subtract(WrongDateTime)); - RunWrongTest(c => c.NullableDateTime.Value.Subtract(SecondDateTime) == NullableDateTime.Subtract(WrongDateTime)); + [Test] + public void MaxValueSubtractDateTimeTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Subtract(SecondDateTime) == DateTime.MaxValue.Subtract(SecondDateTime)); + RunWrongTest(s, c => c.MaxValue.Subtract(SecondDateTime) == DateTime.MaxValue.Subtract(WrongDateTime)); }); } [Test] public void PlusTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime + FirstOffset == FirstDateTime + FirstOffset); - RunTest(c => c.MillisecondDateTime + SecondOffset == FirstMillisecondDateTime + SecondOffset); - RunTest(c => c.NullableDateTime + FirstOffset == NullableDateTime + FirstOffset); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime + FirstOffset == FirstDateTime + FirstOffset); + RunTest(s, c => c.MillisecondDateTime + SecondOffset == FirstMillisecondDateTime + SecondOffset); + RunTest(s, c => c.NullableDateTime + FirstOffset == NullableDateTime + FirstOffset); + + RunWrongTest(s, c => c.DateTime + FirstOffset == FirstDateTime + WrongOffset); + RunWrongTest(s, c => c.MillisecondDateTime + SecondOffset == FirstMillisecondDateTime + WrongOffset); + RunWrongTest(s, c => c.NullableDateTime + FirstOffset == NullableDateTime + WrongOffset); + }); + } - RunWrongTest(c => c.DateTime + FirstOffset == FirstDateTime + WrongOffset); - RunWrongTest(c => c.MillisecondDateTime + SecondOffset == FirstMillisecondDateTime + WrongOffset); - RunWrongTest(c => c.NullableDateTime + FirstOffset == NullableDateTime + WrongOffset); + [Test] + public void MinValuePlusTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue + FirstOffset == DateTime.MinValue + FirstOffset); + RunWrongTest(s, c => c.MinValue + FirstOffset == DateTime.MinValue + WrongOffset); }); } [Test] public void MinusTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTime - FirstOffset == FirstDateTime - FirstOffset); - RunTest(c => c.MillisecondDateTime - SecondOffset == FirstMillisecondDateTime - SecondOffset); - RunTest(c => c.NullableDateTime - FirstOffset == NullableDateTime - FirstOffset); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime - FirstOffset == FirstDateTime - FirstOffset); + RunTest(s, c => c.MillisecondDateTime - SecondOffset == FirstMillisecondDateTime - SecondOffset); + RunTest(s, c => c.NullableDateTime - FirstOffset == NullableDateTime - FirstOffset); + + RunWrongTest(s, c => c.DateTime - FirstOffset == FirstDateTime - WrongOffset); + RunWrongTest(s, c => c.MillisecondDateTime - SecondOffset == FirstMillisecondDateTime - WrongOffset); + RunWrongTest(s, c => c.NullableDateTime - FirstOffset == NullableDateTime - WrongOffset); + }); + } - RunWrongTest(c => c.DateTime - FirstOffset == FirstDateTime - WrongOffset); - RunWrongTest(c => c.MillisecondDateTime - SecondOffset == FirstMillisecondDateTime - WrongOffset); - RunWrongTest(c => c.NullableDateTime - FirstOffset == NullableDateTime - WrongOffset); + [Test] + public void MaxValueMinusTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - FirstOffset == DateTime.MaxValue - FirstOffset); + RunWrongTest(s, c => c.MaxValue - FirstOffset == DateTime.MaxValue - WrongOffset); }); } @@ -180,14 +318,24 @@ public void MinusTimeSpanTest() public void MinusDateTimeTest() { Require.ProviderIsNot(StorageProvider.MySql); - ExecuteInsideSession(() => { - RunTest(c => c.DateTime - SecondDateTime == FirstDateTime - SecondDateTime); - RunTest(c => c.MillisecondDateTime - SecondDateTime == FirstMillisecondDateTime - SecondDateTime); - RunTest(c => c.NullableDateTime - SecondDateTime == NullableDateTime - SecondDateTime); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTime - SecondDateTime == FirstDateTime - SecondDateTime); + RunTest(s, c => c.MillisecondDateTime - SecondDateTime == FirstMillisecondDateTime - SecondDateTime); + RunTest(s, c => c.NullableDateTime - SecondDateTime == NullableDateTime - SecondDateTime); + + RunWrongTest(s, c => c.DateTime - SecondDateTime == FirstDateTime - WrongDateTime); + RunWrongTest(s, c => c.MillisecondDateTime - SecondDateTime == FirstMillisecondDateTime - WrongDateTime); + RunWrongTest(s, c => c.NullableDateTime - SecondDateTime == NullableDateTime - WrongDateTime); + }); + } - RunWrongTest(c => c.DateTime - SecondDateTime == FirstDateTime - WrongDateTime); - RunWrongTest(c => c.MillisecondDateTime - SecondDateTime == FirstMillisecondDateTime - WrongDateTime); - RunWrongTest(c => c.NullableDateTime - SecondDateTime == NullableDateTime - WrongDateTime); + [Test] + public void MaxValueMinusDateTimeTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - SecondDateTime == DateTime.MaxValue - SecondDateTime); + RunWrongTest(s, c => c.MaxValue - SecondDateTime == DateTime.MaxValue - WrongDateTime); }); } @@ -195,19 +343,19 @@ public void MinusDateTimeTest() public void MysqlMinusDateTimeTest() { Require.ProviderIs(StorageProvider.MySql); - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { 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); - RunTest(c => c.NullableDateTime - secondDateTime == nullableDateTime - secondDateTime); + RunTest(s, c => c.DateTime - secondDateTime == firstDateTime - secondDateTime); + RunTest(s, c => c.MillisecondDateTime - secondDateTime == firstMillisecondDateTime - secondDateTime); + RunTest(s, c => c.NullableDateTime - secondDateTime == nullableDateTime - secondDateTime); - RunWrongTest(c => c.DateTime - secondDateTime == firstDateTime - WrongDateTime); - RunWrongTest(c => c.MillisecondDateTime - secondDateTime == firstMillisecondDateTime - WrongDateTime); - RunWrongTest(c => c.NullableDateTime - secondDateTime == nullableDateTime - WrongDateTime); + RunWrongTest(s, c => c.DateTime - secondDateTime == firstDateTime - WrongDateTime); + RunWrongTest(s, c => c.MillisecondDateTime - secondDateTime == firstMillisecondDateTime - WrongDateTime); + RunWrongTest(s, c => c.NullableDateTime - secondDateTime == nullableDateTime - WrongDateTime); }); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs index 1d1b2a574..4f221c786 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs @@ -26,6 +26,18 @@ public void ExtractYearTest() }); } + [Test] + public void MinMaxValueExtractYearTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Year == DateTime.MinValue.Year); + RunTest(s, c => c.MaxValue.Year == DateTime.MaxValue.Year); + + RunWrongTest(s, c => c.MinValue.Year == WrongDateTime.Year); + RunWrongTest(s, c => c.MaxValue.Year == WrongDateTime.Year); + }); + } + [Test] public void ExtractMonthTest() { @@ -40,6 +52,18 @@ public void ExtractMonthTest() }); } + [Test] + public void MinMaxValueExtractMonthTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Month == DateTime.MinValue.Month); + RunTest(s, c => c.MaxValue.Month == DateTime.MaxValue.Month); + + RunWrongTest(s, c => c.MinValue.Month == WrongDateTime.Month); + RunWrongTest(s, c => c.MaxValue.Month == WrongDateTime.Month); + }); + } + [Test] public void ExtractDayTest() { @@ -54,6 +78,18 @@ public void ExtractDayTest() }); } + [Test] + public void MinMaxValueExtractDayTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Day == DateTime.MinValue.Day); + RunTest(s, c => c.MaxValue.Day == DateTime.MaxValue.Day); + + RunWrongTest(s, c => c.MinValue.Day == WrongDateTime.Day); + RunWrongTest(s, c => c.MaxValue.Day == WrongDateTime.Day); + }); + } + [Test] public void ExtractHourTest() { @@ -68,6 +104,18 @@ public void ExtractHourTest() }); } + [Test] + public void MinMaxValueExtractHourTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Hour == DateTime.MinValue.Hour); + RunTest(s, c => c.MaxValue.Hour == DateTime.MaxValue.Hour); + + RunWrongTest(s, c => c.MinValue.Hour == WrongDateTime.Hour); + RunWrongTest(s, c => c.MaxValue.Hour == WrongDateTime.Hour); + }); + } + [Test] public void ExtractMinuteTest() { @@ -82,6 +130,19 @@ public void ExtractMinuteTest() }); } + + [Test] + public void MinMaxValueExtractMinuteTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Minute == DateTime.MinValue.Minute); + RunTest(s, c => c.MaxValue.Minute == DateTime.MaxValue.Minute); + + RunWrongTest(s, c => c.MinValue.Minute == WrongDateTime.Minute); + RunWrongTest(s, c => c.MaxValue.Minute == WrongDateTime.Minute); + }); + } + [Test] public void ExtractSecondTest() { @@ -96,6 +157,18 @@ public void ExtractSecondTest() }); } + [Test] + public void MinMaxValueExtractSecondTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Second == DateTime.MinValue.Second); + RunTest(s, c => c.MaxValue.Second == DateTime.MaxValue.Second); + + RunWrongTest(s, c => c.MinValue.Second == WrongDateTime.Second); + RunWrongTest(s, c => c.MaxValue.Second == WrongDateTime.Second); + }); + } + [Test] public void ExtractMillisecondTest() { @@ -106,6 +179,22 @@ public void ExtractMillisecondTest() }); } + [Test] + public void MinMaxValueExtractMillisecondTest() + { + Require.ProviderIsNot(StorageProvider.MySql); + ExecuteInsideSession((s) => { + + var minAdjusted = DateTime.MinValue.AdjustDateTimeForCurrentProvider(); + var maxAdjusted = DateTime.MaxValue.AdjustDateTimeForCurrentProvider(); + RunTest(s, c => c.MinValue.Millisecond == minAdjusted.Millisecond); + RunTest(s, c => c.MaxValue.Millisecond == 999); + + RunWrongTest(s, c => c.MinValue.Millisecond == WrongMillisecondDateTime.Millisecond); + RunWrongTest(s, c => c.MaxValue.Millisecond == WrongMillisecondDateTime.Millisecond); + }); + } + [Test] public void MysqlExtractMillisecondTest() { @@ -131,6 +220,18 @@ public void ExtractDateTest() }); } + [Test] + public void MinMaxValueExtractDateTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Date == DateTime.MinValue.Date); + RunTest(s, c => c.MaxValue.Date == DateTime.MaxValue.Date); + + RunWrongTest(s, c => c.MinValue.Date == WrongDateTime.Date); + RunWrongTest(s, c => c.MaxValue.Date == WrongDateTime.Date); + }); + } + [Test] [TestCase("2018-10-30 12:15:32.123")] [TestCase("2018-10-30 12:15:32.1234")] @@ -161,6 +262,18 @@ public void ExtractTimeOfDayTest() }); } + [Test] + public void MinMaxValueExtractTimeOfDayTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.TimeOfDay == DateTime.MinValue.TimeOfDay); + RunTest(s, c => c.MaxValue.TimeOfDay == DateTime.MaxValue.TimeOfDay); + + RunWrongTest(s, c => c.MinValue.TimeOfDay == WrongDateTime.TimeOfDay); + RunWrongTest(s, c => c.MaxValue.TimeOfDay == WrongDateTime.TimeOfDay); + }); + } + [Test] public void ExtractDayOfYearTest() { @@ -175,6 +288,18 @@ public void ExtractDayOfYearTest() }); } + [Test] + public void MinMaxValueExtractDayOfYearTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfYear == DateTime.MinValue.DayOfYear); + RunTest(s, c => c.MaxValue.DayOfYear == DateTime.MaxValue.DayOfYear); + + RunWrongTest(s, c => c.MinValue.DayOfYear == WrongDateTime.DayOfYear); + RunWrongTest(s, c => c.MaxValue.DayOfYear == WrongDateTime.DayOfYear); + }); + } + [Test] public void ExtractDayOfWeekTest() { @@ -189,6 +314,18 @@ public void ExtractDayOfWeekTest() }); } + [Test] + public void MinMaxValueExtractDayOfWeekTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfWeek == DateTime.MinValue.DayOfWeek); + RunTest(s, c => c.MaxValue.DayOfWeek == DateTime.MaxValue.DayOfWeek); + + RunWrongTest(s, c => c.MinValue.DayOfWeek == DateTime.MinValue.AddDays(1).DayOfWeek); + RunWrongTest(s, c => c.MaxValue.DayOfWeek == DateTime.MaxValue.AddDays(-1).DayOfWeek); + }); + } + [Test] public void ExtractTimeOfDayTicksTest() { diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs index 85b19ff1a..3abd46f81 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs @@ -45,9 +45,11 @@ protected override void RegisterTypes(DomainConfiguration configuration) configuration.Types.Register(typeof(DateTimeEntity)); configuration.Types.Register(typeof(MillisecondDateTimeEntity)); configuration.Types.Register(typeof(NullableDateTimeEntity)); + configuration.Types.Register(typeof(MinMaxDateTimeEntity)); configuration.Types.Register(typeof(AllPossiblePartsEntity)); configuration.Types.Register(typeof(DateOnlyEntity)); configuration.Types.Register(typeof(SingleDateOnlyEntity)); + configuration.Types.Register(typeof(MinMaxDateOnlyEntity)); configuration.Types.Register(typeof(TimeOnlyEntity)); configuration.Types.Register(typeof(SingleTimeOnlyEntity)); } @@ -198,6 +200,9 @@ protected override void PopulateEntities(Session session) _ = new NullableDateTimeEntity(session) { DateTime = null }; _ = AllPossiblePartsEntity.FromDateTime(session, FirstMillisecondDateTime, 321); + + _ = new MinMaxDateOnlyEntity(session) { MinValue = DateOnly.MinValue, MaxValue = DateOnly.MaxValue }; + _ = new MinMaxDateTimeEntity(session) { MinValue = DateTime.MinValue, MaxValue = DateTime.MaxValue }; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs index f02738374..6acaf6f55 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs @@ -15,205 +15,368 @@ public class OperationsTest : DateTimeOffsetBaseTest [Test] public void AddYearsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.AddYears(1) == FirstDateTimeOffset.AddYears(1)); - RunTest(c => c.MillisecondDateTimeOffset.AddYears(-2) == FirstMillisecondDateTimeOffset.AddYears(-2)); - RunTest(c => c.NullableDateTimeOffset.Value.AddYears(33) == NullableDateTimeOffset.AddYears(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.AddYears(1) == FirstDateTimeOffset.AddYears(1)); + RunTest(s, c => c.MillisecondDateTimeOffset.AddYears(-2) == FirstMillisecondDateTimeOffset.AddYears(-2)); + RunTest(s, c => c.NullableDateTimeOffset.Value.AddYears(33) == NullableDateTimeOffset.AddYears(33)); + + RunWrongTest(s, c => c.DateTimeOffset.AddYears(1) == FirstDateTimeOffset.AddYears(2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddYears(-1) == FirstMillisecondDateTimeOffset.AddYears(-2)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.AddYears(33) == NullableDateTimeOffset.AddYears(44)); + }); + } + + [Test] + public void MinMaxValueAddYearsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddYears(1) == DateTimeOffset.MinValue.AddYears(1)); + RunTest(s, c => c.MaxValue.AddYears(-2) == DateTimeOffset.MaxValue.AddYears(-2)); - RunWrongTest(c => c.DateTimeOffset.AddYears(1) == FirstDateTimeOffset.AddYears(2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddYears(-1) == FirstMillisecondDateTimeOffset.AddYears(-2)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.AddYears(33) == NullableDateTimeOffset.AddYears(44)); + RunWrongTest(s, c => c.MinValue.AddYears(1) == FirstDateTimeOffset.AddYears(2)); + RunWrongTest(s, c => c.MaxValue.AddYears(-1) == FirstDateTimeOffset.AddYears(-2)); }); } [Test] public void AddMonthsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.AddMonths(1) == FirstDateTimeOffset.AddMonths(1)); - RunTest(c => c.MillisecondDateTimeOffset.AddMonths(-2) == FirstMillisecondDateTimeOffset.AddMonths(-2)); - RunTest(c => c.NullableDateTimeOffset.Value.AddMonths(33) == NullableDateTimeOffset.AddMonths(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.AddMonths(1) == FirstDateTimeOffset.AddMonths(1)); + RunTest(s, c => c.MillisecondDateTimeOffset.AddMonths(-2) == FirstMillisecondDateTimeOffset.AddMonths(-2)); + RunTest(s, c => c.NullableDateTimeOffset.Value.AddMonths(33) == NullableDateTimeOffset.AddMonths(33)); + + RunWrongTest(s, c => c.DateTimeOffset.AddMonths(1) == FirstDateTimeOffset.AddMonths(2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddMonths(-1) == FirstMillisecondDateTimeOffset.AddMonths(-2)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.AddMonths(33) == NullableDateTimeOffset.AddMonths(44)); + }); + } + + [Test] + public void MinMaxValueAddMonthsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMonths(1) == DateTimeOffset.MinValue.AddMonths(1)); + RunTest(s, c => c.MaxValue.AddMonths(-2) == DateTimeOffset.MaxValue.AddMonths(-2)); - RunWrongTest(c => c.DateTimeOffset.AddMonths(1) == FirstDateTimeOffset.AddMonths(2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddMonths(-1) == FirstMillisecondDateTimeOffset.AddMonths(-2)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.AddMonths(33) == NullableDateTimeOffset.AddMonths(44)); + RunWrongTest(s, c => c.MinValue.AddMonths(1) == FirstDateTimeOffset.AddMonths(2)); + RunWrongTest(s, c => c.MaxValue.AddMonths(-1) == FirstDateTimeOffset.AddMonths(-2)); }); } [Test] public void AddDaysTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.AddDays(1) == FirstDateTimeOffset.AddDays(1)); - RunTest(c => c.MillisecondDateTimeOffset.AddDays(-2) == FirstMillisecondDateTimeOffset.AddDays(-2)); - RunTest(c => c.NullableDateTimeOffset.Value.AddDays(33) == NullableDateTimeOffset.AddDays(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.AddDays(1) == FirstDateTimeOffset.AddDays(1)); + RunTest(s, c => c.MillisecondDateTimeOffset.AddDays(-2) == FirstMillisecondDateTimeOffset.AddDays(-2)); + RunTest(s, c => c.NullableDateTimeOffset.Value.AddDays(33) == NullableDateTimeOffset.AddDays(33)); + + RunWrongTest(s, c => c.DateTimeOffset.AddDays(1) == FirstDateTimeOffset.AddDays(2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddDays(-1) == FirstMillisecondDateTimeOffset.AddDays(-2)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.AddDays(33) == NullableDateTimeOffset.AddDays(44)); + }); + } - RunWrongTest(c => c.DateTimeOffset.AddDays(1) == FirstDateTimeOffset.AddDays(2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddDays(-1) == FirstMillisecondDateTimeOffset.AddDays(-2)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.AddDays(33) == NullableDateTimeOffset.AddDays(44)); + [Test] + public void MinMaxValueAddDaysTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddDays(1) == DateTimeOffset.MinValue.AddDays(1)); + RunTest(s, c => c.MaxValue.AddDays(-2) == DateTimeOffset.MaxValue.AddDays(-2)); + + RunWrongTest(s, c => c.MinValue.AddDays(1) == FirstDateTimeOffset.AddDays(2)); + RunWrongTest(s, c => c.MaxValue.AddDays(-1) == FirstDateTimeOffset.AddDays(-2)); }); } [Test] public void AddHoursTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.AddHours(1) == FirstDateTimeOffset.AddHours(1)); - RunTest(c => c.MillisecondDateTimeOffset.AddHours(-2) == FirstMillisecondDateTimeOffset.AddHours(-2)); - RunTest(c => c.NullableDateTimeOffset.Value.AddHours(33) == NullableDateTimeOffset.AddHours(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.AddHours(1) == FirstDateTimeOffset.AddHours(1)); + RunTest(s, c => c.MillisecondDateTimeOffset.AddHours(-2) == FirstMillisecondDateTimeOffset.AddHours(-2)); + RunTest(s, c => c.NullableDateTimeOffset.Value.AddHours(33) == NullableDateTimeOffset.AddHours(33)); + + RunWrongTest(s, c => c.DateTimeOffset.AddHours(1) == FirstDateTimeOffset.AddHours(2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddHours(-1) == FirstMillisecondDateTimeOffset.AddHours(-2)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.AddHours(33) == NullableDateTimeOffset.AddHours(44)); + }); + } + + [Test] + public void MinMaxValueAddHoursTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddHours(1) == DateTimeOffset.MinValue.AddHours(1)); + RunTest(s, c => c.MaxValue.AddHours(-2) == DateTimeOffset.MaxValue.AddHours(-2)); - RunWrongTest(c => c.DateTimeOffset.AddHours(1) == FirstDateTimeOffset.AddHours(2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddHours(-1) == FirstMillisecondDateTimeOffset.AddHours(-2)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.AddHours(33) == NullableDateTimeOffset.AddHours(44)); + RunWrongTest(s, c => c.MinValue.AddHours(1) == FirstDateTimeOffset.AddHours(2)); + RunWrongTest(s, c => c.MaxValue.AddHours(-1) == FirstDateTimeOffset.AddHours(-2)); }); } [Test] public void AddMinutesTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.AddMinutes(1) == FirstDateTimeOffset.AddMinutes(1)); - RunTest(c => c.MillisecondDateTimeOffset.AddMinutes(-2) == FirstMillisecondDateTimeOffset.AddMinutes(-2)); - RunTest(c => c.NullableDateTimeOffset.Value.AddMinutes(33) == NullableDateTimeOffset.AddMinutes(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.AddMinutes(1) == FirstDateTimeOffset.AddMinutes(1)); + RunTest(s, c => c.MillisecondDateTimeOffset.AddMinutes(-2) == FirstMillisecondDateTimeOffset.AddMinutes(-2)); + RunTest(s, c => c.NullableDateTimeOffset.Value.AddMinutes(33) == NullableDateTimeOffset.AddMinutes(33)); + + RunWrongTest(s, c => c.DateTimeOffset.AddMinutes(1) == FirstDateTimeOffset.AddMinutes(2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddMinutes(-1) == FirstMillisecondDateTimeOffset.AddMinutes(-2)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.AddMinutes(33) == NullableDateTimeOffset.AddMinutes(44)); + }); + } - RunWrongTest(c => c.DateTimeOffset.AddMinutes(1) == FirstDateTimeOffset.AddMinutes(2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddMinutes(-1) == FirstMillisecondDateTimeOffset.AddMinutes(-2)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.AddMinutes(33) == NullableDateTimeOffset.AddMinutes(44)); + [Test] + public void MinMaxValueAddMinutesTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMinutes(1) == DateTimeOffset.MinValue.AddMinutes(1)); + RunTest(s, c => c.MaxValue.AddMinutes(-2) == DateTimeOffset.MaxValue.AddMinutes(-2)); + + RunWrongTest(s, c => c.MinValue.AddMinutes(1) == FirstDateTimeOffset.AddMinutes(2)); + RunWrongTest(s, c => c.MaxValue.AddMinutes(-1) == FirstDateTimeOffset.AddMinutes(-2)); }); } [Test] public void AddSecondsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.AddSeconds(1) == FirstDateTimeOffset.AddSeconds(1)); - RunTest(c => c.MillisecondDateTimeOffset.AddSeconds(-2) == FirstMillisecondDateTimeOffset.AddSeconds(-2)); - RunTest(c => c.NullableDateTimeOffset.Value.AddSeconds(33) == NullableDateTimeOffset.AddSeconds(33)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.AddSeconds(1) == FirstDateTimeOffset.AddSeconds(1)); + RunTest(s, c => c.MillisecondDateTimeOffset.AddSeconds(-2) == FirstMillisecondDateTimeOffset.AddSeconds(-2)); + RunTest(s, c => c.NullableDateTimeOffset.Value.AddSeconds(33) == NullableDateTimeOffset.AddSeconds(33)); + + RunWrongTest(s, c => c.DateTimeOffset.AddSeconds(1) == FirstDateTimeOffset.AddSeconds(2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddSeconds(-1) == FirstMillisecondDateTimeOffset.AddSeconds(-2)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.AddSeconds(33) == NullableDateTimeOffset.AddSeconds(44)); + }); + } + + [Test] + public void MinMaxValueAddSecondsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddSeconds(1) == DateTimeOffset.MinValue.AddSeconds(1)); + RunTest(s, c => c.MaxValue.AddSeconds(-2) == DateTimeOffset.MaxValue.AddSeconds(-2)); - RunWrongTest(c => c.DateTimeOffset.AddSeconds(1) == FirstDateTimeOffset.AddSeconds(2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddSeconds(-1) == FirstMillisecondDateTimeOffset.AddSeconds(-2)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.AddSeconds(33) == NullableDateTimeOffset.AddSeconds(44)); + RunWrongTest(s, c => c.MinValue.AddSeconds(1) == FirstDateTimeOffset.AddSeconds(2)); + RunWrongTest(s, c => c.MaxValue.AddSeconds(-1) == FirstDateTimeOffset.AddSeconds(-2)); }); } [Test] public void AddMillisecondsTest() { - ExecuteInsideSession(() => { - RunTest(c => c.MillisecondDateTimeOffset.AddMilliseconds(-2) == FirstMillisecondDateTimeOffset.AddMilliseconds(-2)); - RunWrongTest(c => c.MillisecondDateTimeOffset.AddMilliseconds(-1) == FirstMillisecondDateTimeOffset.AddMilliseconds(-2)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MillisecondDateTimeOffset.AddMilliseconds(-2) == FirstMillisecondDateTimeOffset.AddMilliseconds(-2)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.AddMilliseconds(-1) == FirstMillisecondDateTimeOffset.AddMilliseconds(-2)); + }); + } + + [Test] + public void MinMaxValueAddMillisecondsTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.AddMilliseconds(1) == DateTimeOffset.MinValue.AddMilliseconds(1)); + RunTest(s, c => c.MaxValue.AddMilliseconds(-2) == DateTimeOffset.MaxValue.AddMilliseconds(-2)); + + RunWrongTest(s, c => c.MinValue.AddMilliseconds(1) == FirstDateTimeOffset.AddMilliseconds(2)); + RunWrongTest(s, c => c.MaxValue.AddMilliseconds(-1) == FirstDateTimeOffset.AddMilliseconds(-2)); }); } [Test] public void AddTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.Add(FirstOffset) == FirstDateTimeOffset.Add(FirstOffset)); - RunTest(c => c.MillisecondDateTimeOffset.Add(SecondOffset) == FirstMillisecondDateTimeOffset.Add(SecondOffset)); - RunTest(c => c.NullableDateTimeOffset.Value.Add(FirstOffset) == NullableDateTimeOffset.Add(FirstOffset)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.Add(FirstOffset) == FirstDateTimeOffset.Add(FirstOffset)); + RunTest(s, c => c.MillisecondDateTimeOffset.Add(SecondOffset) == FirstMillisecondDateTimeOffset.Add(SecondOffset)); + RunTest(s, c => c.NullableDateTimeOffset.Value.Add(FirstOffset) == NullableDateTimeOffset.Add(FirstOffset)); + + RunWrongTest(s, c => c.DateTimeOffset.Add(FirstOffset) == FirstDateTimeOffset.Add(WrongOffset)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Add(SecondOffset) == FirstMillisecondDateTimeOffset.Add(WrongOffset)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Add(FirstOffset) == NullableDateTimeOffset.Add(WrongOffset)); + }); + } - RunWrongTest(c => c.DateTimeOffset.Add(FirstOffset) == FirstDateTimeOffset.Add(WrongOffset)); - RunWrongTest(c => c.MillisecondDateTimeOffset.Add(SecondOffset) == FirstMillisecondDateTimeOffset.Add(WrongOffset)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Add(FirstOffset) == NullableDateTimeOffset.Add(WrongOffset)); + [Test] + public void MinValueAddTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Add(FirstOffset) == DateTimeOffset.MinValue.Add(FirstOffset)); + RunWrongTest(s, c => c.MinValue.Add(FirstOffset) == FirstDateTimeOffset.Add(WrongOffset)); }); } [Test] public void SubtractTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.Subtract(FirstOffset) == FirstDateTimeOffset.Subtract(FirstOffset)); - RunTest(c => c.MillisecondDateTimeOffset.Subtract(SecondOffset) == FirstMillisecondDateTimeOffset.Subtract(SecondOffset)); - RunTest(c => c.NullableDateTimeOffset.Value.Subtract(FirstOffset) == NullableDateTimeOffset.Subtract(FirstOffset)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.Subtract(FirstOffset) == FirstDateTimeOffset.Subtract(FirstOffset)); + RunTest(s, c => c.MillisecondDateTimeOffset.Subtract(SecondOffset) == FirstMillisecondDateTimeOffset.Subtract(SecondOffset)); + RunTest(s, c => c.NullableDateTimeOffset.Value.Subtract(FirstOffset) == NullableDateTimeOffset.Subtract(FirstOffset)); + + RunWrongTest(s, c => c.DateTimeOffset.Subtract(FirstOffset) == FirstDateTimeOffset.Subtract(WrongOffset)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Subtract(SecondOffset) == FirstMillisecondDateTimeOffset.Subtract(WrongOffset)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Subtract(FirstOffset) == NullableDateTimeOffset.Subtract(WrongOffset)); + }); + } - RunWrongTest(c => c.DateTimeOffset.Subtract(FirstOffset) == FirstDateTimeOffset.Subtract(WrongOffset)); - RunWrongTest(c => c.MillisecondDateTimeOffset.Subtract(SecondOffset) == FirstMillisecondDateTimeOffset.Subtract(WrongOffset)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Subtract(FirstOffset) == NullableDateTimeOffset.Subtract(WrongOffset)); + [Test] + public void MaxValueSubtractTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Subtract(FirstOffset) == DateTimeOffset.MaxValue.Subtract(FirstOffset)); + RunWrongTest(s, c => c.MaxValue.Subtract(FirstOffset) == FirstDateTimeOffset.Subtract(WrongOffset)); }); } [Test] public void SubtractDateTimeTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.Subtract(SecondDateTime) == FirstDateTimeOffset.Subtract(SecondDateTime)); - RunTest(c => c.MillisecondDateTimeOffset.Subtract(SecondDateTime) == FirstMillisecondDateTimeOffset.Subtract(SecondDateTime)); - RunTest(c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTime) == NullableDateTimeOffset.Subtract(SecondDateTime)); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.Subtract(SecondDateTime) == FirstDateTimeOffset.Subtract(SecondDateTime)); + RunTest(s, c => c.MillisecondDateTimeOffset.Subtract(SecondDateTime) == FirstMillisecondDateTimeOffset.Subtract(SecondDateTime)); + RunTest(s, c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTime) == NullableDateTimeOffset.Subtract(SecondDateTime)); + + RunWrongTest(s, c => c.DateTimeOffset.Subtract(SecondDateTime) == FirstDateTimeOffset.Subtract(WrongDateTime)); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Subtract(SecondDateTime) == FirstMillisecondDateTimeOffset.Subtract(WrongDateTime)); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTime) == NullableDateTimeOffset.Subtract(WrongDateTime)); + }); + } - RunWrongTest(c => c.DateTimeOffset.Subtract(SecondDateTime) == FirstDateTimeOffset.Subtract(WrongDateTime)); - RunWrongTest(c => c.MillisecondDateTimeOffset.Subtract(SecondDateTime) == FirstMillisecondDateTimeOffset.Subtract(WrongDateTime)); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTime) == NullableDateTimeOffset.Subtract(WrongDateTime)); + [Test] + public void MaxValueSubtractDateTimeTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Subtract(SecondDateTime) == DateTimeOffset.MaxValue.Subtract(SecondDateTime)); + RunWrongTest(s, c => c.MaxValue.Subtract(SecondDateTime) == FirstDateTimeOffset.Subtract(WrongDateTime)); }); } [Test] public void SubstractDateTimeOffsetAndIntervalUsageTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds == FirstDateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds); - RunTest(c => c.MillisecondDateTimeOffset.Subtract(FirstDateTimeOffset).TotalMilliseconds == FirstMillisecondDateTimeOffset.Subtract(FirstDateTimeOffset).TotalMilliseconds); - RunTest(c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTimeOffset).TotalMilliseconds == NullableDateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds == FirstDateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds); + RunTest(s, c => c.MillisecondDateTimeOffset.Subtract(FirstDateTimeOffset).TotalMilliseconds == FirstMillisecondDateTimeOffset.Subtract(FirstDateTimeOffset).TotalMilliseconds); + RunTest(s, c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTimeOffset).TotalMilliseconds == NullableDateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds); + + RunWrongTest(s, c => c.DateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds == FirstDateTimeOffset.Subtract(WrongDateTimeOffset).TotalMilliseconds); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds == FirstMillisecondDateTimeOffset.Subtract(WrongDateTimeOffset).TotalMilliseconds); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTimeOffset).TotalMilliseconds == NullableDateTimeOffset.Subtract(WrongDateTimeOffset).TotalMilliseconds); + }); + } - RunWrongTest(c => c.DateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds == FirstDateTimeOffset.Subtract(WrongDateTimeOffset).TotalMilliseconds); - RunWrongTest(c => c.MillisecondDateTimeOffset.Subtract(SecondDateTimeOffset).TotalMilliseconds == FirstMillisecondDateTimeOffset.Subtract(WrongDateTimeOffset).TotalMilliseconds); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Subtract(SecondDateTimeOffset).TotalMilliseconds == NullableDateTimeOffset.Subtract(WrongDateTimeOffset).TotalMilliseconds); + [Test] + public void MaxValueSubstractDateTimeOffsetAndIntervalUsageTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Subtract(SecondDateTimeOffset) == DateTimeOffset.MaxValue.Subtract(SecondDateTimeOffset)); + RunTest(s, c => c.MaxValue.Subtract(SecondDateTimeOffset).TotalMilliseconds == DateTimeOffset.MaxValue.Subtract(SecondDateTimeOffset).TotalMilliseconds); + + RunWrongTest(s, c => c.MaxValue.Subtract(SecondDateTimeOffset) == DateTimeOffset.MaxValue.Subtract(WrongDateTimeOffset)); + RunWrongTest(s, c => c.MaxValue.Subtract(SecondDateTimeOffset).TotalMilliseconds == DateTimeOffset.MaxValue.Subtract(WrongDateTimeOffset).TotalMilliseconds); }); } [Test] public void PlusTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset + FirstOffset == FirstDateTimeOffset + FirstOffset); - RunTest(c => c.MillisecondDateTimeOffset + SecondOffset == FirstMillisecondDateTimeOffset + SecondOffset); - RunTest(c => c.NullableDateTimeOffset + FirstOffset == NullableDateTimeOffset + FirstOffset); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset + FirstOffset == FirstDateTimeOffset + FirstOffset); + RunTest(s, c => c.MillisecondDateTimeOffset + SecondOffset == FirstMillisecondDateTimeOffset + SecondOffset); + RunTest(s, c => c.NullableDateTimeOffset + FirstOffset == NullableDateTimeOffset + FirstOffset); + + RunWrongTest(s, c => c.DateTimeOffset + FirstOffset == FirstDateTimeOffset + WrongOffset); + RunWrongTest(s, c => c.MillisecondDateTimeOffset + SecondOffset == FirstMillisecondDateTimeOffset + WrongOffset); + RunWrongTest(s, c => c.NullableDateTimeOffset + FirstOffset == NullableDateTimeOffset + WrongOffset); + }); + } - RunWrongTest(c => c.DateTimeOffset + FirstOffset == FirstDateTimeOffset + WrongOffset); - RunWrongTest(c => c.MillisecondDateTimeOffset + SecondOffset == FirstMillisecondDateTimeOffset + WrongOffset); - RunWrongTest(c => c.NullableDateTimeOffset + FirstOffset == NullableDateTimeOffset + WrongOffset); + [Test] + public void MinValuePlusTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue + FirstOffset == DateTimeOffset.MinValue + FirstOffset); + + RunWrongTest(s, c => c.MinValue + FirstOffset == DateTimeOffset.MinValue + WrongOffset); }); } [Test] public void MinusTimeSpanTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset - FirstOffset == FirstDateTimeOffset - FirstOffset); - RunTest(c => c.MillisecondDateTimeOffset - SecondOffset == FirstMillisecondDateTimeOffset - SecondOffset); - RunTest(c => c.NullableDateTimeOffset - FirstOffset == NullableDateTimeOffset - FirstOffset); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset - FirstOffset == FirstDateTimeOffset - FirstOffset); + RunTest(s, c => c.MillisecondDateTimeOffset - SecondOffset == FirstMillisecondDateTimeOffset - SecondOffset); + RunTest(s, c => c.NullableDateTimeOffset - FirstOffset == NullableDateTimeOffset - FirstOffset); + + RunWrongTest(s, c => c.DateTimeOffset - FirstOffset == FirstDateTimeOffset - WrongOffset); + RunWrongTest(s, c => c.MillisecondDateTimeOffset - SecondOffset == FirstMillisecondDateTimeOffset - WrongOffset); + RunWrongTest(s, c => c.NullableDateTimeOffset - FirstOffset == NullableDateTimeOffset - WrongOffset); + }); + } - RunWrongTest(c => c.DateTimeOffset - FirstOffset == FirstDateTimeOffset - WrongOffset); - RunWrongTest(c => c.MillisecondDateTimeOffset - SecondOffset == FirstMillisecondDateTimeOffset - WrongOffset); - RunWrongTest(c => c.NullableDateTimeOffset - FirstOffset == NullableDateTimeOffset - WrongOffset); + [Test] + public void MaxValueMinusTimeSpanTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - FirstOffset == DateTimeOffset.MaxValue - FirstOffset); + RunWrongTest(s, c => c.MaxValue - FirstOffset == DateTimeOffset.MaxValue - WrongOffset); }); } [Test] public void MinusDateTimeTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset - SecondDateTime == FirstDateTimeOffset - SecondDateTime); - RunTest(c => c.MillisecondDateTimeOffset - SecondDateTime == FirstMillisecondDateTimeOffset - SecondDateTime); - RunTest(c => c.NullableDateTimeOffset - SecondDateTime == NullableDateTimeOffset - SecondDateTime); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset - SecondDateTime == FirstDateTimeOffset - SecondDateTime); + RunTest(s, c => c.MillisecondDateTimeOffset - SecondDateTime == FirstMillisecondDateTimeOffset - SecondDateTime); + RunTest(s, c => c.NullableDateTimeOffset - SecondDateTime == NullableDateTimeOffset - SecondDateTime); + + RunWrongTest(s, c => c.DateTimeOffset - SecondDateTime == FirstDateTimeOffset - WrongDateTime); + RunWrongTest(s, c => c.MillisecondDateTimeOffset - SecondDateTime == FirstMillisecondDateTimeOffset - WrongDateTime); + RunWrongTest(s, c => c.NullableDateTimeOffset - SecondDateTime == NullableDateTimeOffset - WrongDateTime); + }); + } - RunWrongTest(c => c.DateTimeOffset - SecondDateTime == FirstDateTimeOffset - WrongDateTime); - RunWrongTest(c => c.MillisecondDateTimeOffset - SecondDateTime == FirstMillisecondDateTimeOffset - WrongDateTime); - RunWrongTest(c => c.NullableDateTimeOffset - SecondDateTime == NullableDateTimeOffset - WrongDateTime); + [Test] + public void MaxValueMinusDateTimeTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - SecondDateTime == DateTimeOffset.MaxValue - SecondDateTime); + RunWrongTest(s, c => c.MaxValue - SecondDateTime == DateTimeOffset.MaxValue - WrongDateTime); }); } [Test] public void MinusDateTimeOffsetAndIntervalUsageTest() { - ExecuteInsideSession(() => { - RunTest(c => (c.DateTimeOffset - SecondDateTimeOffset).TotalMilliseconds == (FirstDateTimeOffset - SecondDateTimeOffset).TotalMilliseconds); - RunTest(c => (c.MillisecondDateTimeOffset - FirstDateTimeOffset).TotalMilliseconds == (FirstMillisecondDateTimeOffset - FirstDateTimeOffset).TotalMilliseconds); - RunTest(c => (c.NullableDateTimeOffset.Value - SecondDateTimeOffset).TotalMilliseconds == (NullableDateTimeOffset - SecondDateTimeOffset).TotalMilliseconds); + ExecuteInsideSession((s) => { + RunTest(s, c => (c.DateTimeOffset - SecondDateTimeOffset).TotalMilliseconds == (FirstDateTimeOffset - SecondDateTimeOffset).TotalMilliseconds); + RunTest(s, c => (c.MillisecondDateTimeOffset - FirstDateTimeOffset).TotalMilliseconds == (FirstMillisecondDateTimeOffset - FirstDateTimeOffset).TotalMilliseconds); + RunTest(s, c => (c.NullableDateTimeOffset.Value - SecondDateTimeOffset).TotalMilliseconds == (NullableDateTimeOffset - SecondDateTimeOffset).TotalMilliseconds); + + RunWrongTest(s, c => (c.DateTimeOffset - SecondDateTimeOffset).TotalMilliseconds == (FirstDateTimeOffset - WrongDateTimeOffset).TotalMilliseconds); + RunWrongTest(s, c => (c.MillisecondDateTimeOffset - SecondDateTimeOffset).TotalMilliseconds == (FirstMillisecondDateTimeOffset - WrongDateTimeOffset).TotalMilliseconds); + RunWrongTest(s, c => (c.NullableDateTimeOffset.Value - SecondDateTimeOffset).TotalMilliseconds == (NullableDateTimeOffset - WrongDateTimeOffset).TotalMilliseconds); + }); + } + + [Test] + public void MaxValueMinusDateTimeOffsetAndIntervalUsageTest() + { + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - FirstDateTimeOffset == DateTimeOffset.MaxValue - FirstDateTimeOffset); + RunTest(s, c => (c.MaxValue - FirstDateTimeOffset).TotalMilliseconds == (DateTimeOffset.MaxValue - FirstDateTimeOffset).TotalMilliseconds); - RunWrongTest(c => (c.DateTimeOffset - SecondDateTimeOffset).TotalMilliseconds == (FirstDateTimeOffset - WrongDateTimeOffset).TotalMilliseconds); - RunWrongTest(c => (c.MillisecondDateTimeOffset - SecondDateTimeOffset).TotalMilliseconds == (FirstMillisecondDateTimeOffset - WrongDateTimeOffset).TotalMilliseconds); - RunWrongTest(c => (c.NullableDateTimeOffset.Value - SecondDateTimeOffset).TotalMilliseconds == (NullableDateTimeOffset - WrongDateTimeOffset).TotalMilliseconds); + RunWrongTest(s, c => c.MaxValue - FirstDateTimeOffset == DateTimeOffset.MaxValue - WrongDateTimeOffset); + RunWrongTest(s, c => (c.MaxValue - FirstDateTimeOffset).TotalMilliseconds == (DateTimeOffset.MaxValue - WrongDateTimeOffset).TotalMilliseconds); }); } @@ -221,10 +384,10 @@ public void MinusDateTimeOffsetAndIntervalUsageTest() public void ToUniversalTime() { Require.ProviderIsNot(StorageProvider.PostgreSql, "ToUniversalTime is not supported"); - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.ToUniversalTime() == FirstDateTimeOffset.ToUniversalTime()); - RunTest(c => c.MillisecondDateTimeOffset.ToUniversalTime() == FirstMillisecondDateTimeOffset.ToUniversalTime()); - RunTest(c => c.NullableDateTimeOffset.Value.ToUniversalTime() == NullableDateTimeOffset.ToUniversalTime()); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.ToUniversalTime() == FirstDateTimeOffset.ToUniversalTime()); + RunTest(s, c => c.MillisecondDateTimeOffset.ToUniversalTime() == FirstMillisecondDateTimeOffset.ToUniversalTime()); + RunTest(s, c => c.NullableDateTimeOffset.Value.ToUniversalTime() == NullableDateTimeOffset.ToUniversalTime()); }); } @@ -232,8 +395,8 @@ public void ToUniversalTime() public void ToUniversalTimePostgresql() { Require.ProviderIs(StorageProvider.PostgreSql, "ToUniversalTime is not supported"); - ExecuteInsideSession(() => { - var ex = Assert.Throws(()=> RunTest(c => c.DateTimeOffset.ToUniversalTime() == FirstDateTimeOffset.ToUniversalTime())); + ExecuteInsideSession((s) => { + var ex = Assert.Throws(()=> RunTest(s, c => c.DateTimeOffset.ToUniversalTime() == FirstDateTimeOffset.ToUniversalTime())); Assert.That(ex.InnerException, Is.TypeOf()); }); } @@ -242,8 +405,8 @@ public void ToUniversalTimePostgresql() public void ToLocalTimePostgresql() { Require.ProviderIs(StorageProvider.PostgreSql, "ToLocalTime is not supported"); - ExecuteInsideSession(() => { - var ex = Assert.Throws(() => RunTest(c => c.DateTimeOffset.ToLocalTime() == FirstDateTimeOffset.ToLocalTime())); + ExecuteInsideSession((s) => { + var ex = Assert.Throws(() => RunTest(s, c => c.DateTimeOffset.ToLocalTime() == FirstDateTimeOffset.ToLocalTime())); Assert.That(ex.InnerException, Is.TypeOf()); }); } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs index 05d5b3239..2f31c0808 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs @@ -15,137 +15,137 @@ public class PartsExtractionTest : DateTimeOffsetBaseTest [Test] public void ExtractYearTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.Year==FirstDateTimeOffset.Year); - RunTest(c => c.MillisecondDateTimeOffset.Year==FirstMillisecondDateTimeOffset.Year); - RunTest(c => c.NullableDateTimeOffset.Value.Year==NullableDateTimeOffset.Year); - - RunWrongTest(c => c.DateTimeOffset.Year==WrongDateTimeOffset.Year); - RunWrongTest(c => c.MillisecondDateTimeOffset.Year==WrongMillisecondDateTimeOffset.Year); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Year==WrongDateTimeOffset.Year); + ExecuteInsideSession((s) => { + RunTest(s ,c => c.DateTimeOffset.Year==FirstDateTimeOffset.Year); + RunTest(s, c => c.MillisecondDateTimeOffset.Year==FirstMillisecondDateTimeOffset.Year); + RunTest(s, c => c.NullableDateTimeOffset.Value.Year==NullableDateTimeOffset.Year); + + RunWrongTest(s, c => c.DateTimeOffset.Year==WrongDateTimeOffset.Year); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Year==WrongMillisecondDateTimeOffset.Year); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Year==WrongDateTimeOffset.Year); }); } [Test] public void ExtractMonthTest() { - ExecuteInsideSession(() => { - RunTest(c => c.DateTimeOffset.Month==FirstDateTimeOffset.Month); - RunTest(c => c.MillisecondDateTimeOffset.Month==FirstMillisecondDateTimeOffset.Month); - RunTest(c => c.NullableDateTimeOffset.Value.Month==NullableDateTimeOffset.Month); - - RunWrongTest(c => c.DateTimeOffset.Month==WrongDateTimeOffset.Month); - RunWrongTest(c => c.MillisecondDateTimeOffset.Month==WrongMillisecondDateTimeOffset.Month); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Month==WrongDateTimeOffset.Month); + ExecuteInsideSession((s) => { + RunTest(s, c => c.DateTimeOffset.Month==FirstDateTimeOffset.Month); + RunTest(s ,c => c.MillisecondDateTimeOffset.Month==FirstMillisecondDateTimeOffset.Month); + RunTest(s, c => c.NullableDateTimeOffset.Value.Month==NullableDateTimeOffset.Month); + + RunWrongTest(s, c => c.DateTimeOffset.Month==WrongDateTimeOffset.Month); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Month==WrongMillisecondDateTimeOffset.Month); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Month==WrongDateTimeOffset.Month); }); } [Test] public void ExtractDayTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.Day==firstDateTimeOffset.Day); - RunTest(c => c.MillisecondDateTimeOffset.Day==firstMillisecondDateTimeOffset.Day); - RunTest(c => c.NullableDateTimeOffset.Value.Day==nullableDateTimeOffset.Day); + RunTest(s, c => c.DateTimeOffset.Day==firstDateTimeOffset.Day); + RunTest(s, c => c.MillisecondDateTimeOffset.Day==firstMillisecondDateTimeOffset.Day); + RunTest(s, c => c.NullableDateTimeOffset.Value.Day==nullableDateTimeOffset.Day); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.Day==wrongDateTimeOffset.Day); - RunWrongTest(c => c.MillisecondDateTimeOffset.Day==wrongMillisecondDateTimeOffset.Day); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Day==wrongDateTimeOffset.Day); + RunWrongTest(s, c => c.DateTimeOffset.Day==wrongDateTimeOffset.Day); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Day==wrongMillisecondDateTimeOffset.Day); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Day==wrongDateTimeOffset.Day); }); } [Test] public void ExtractHourTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.Hour==firstDateTimeOffset.Hour); - RunTest(c => c.MillisecondDateTimeOffset.Hour==firstMillisecondDateTimeOffset.Hour); - RunTest(c => c.NullableDateTimeOffset.Value.Hour==nullableDateTimeOffset.Hour); + RunTest(s, c => c.DateTimeOffset.Hour==firstDateTimeOffset.Hour); + RunTest(s, c => c.MillisecondDateTimeOffset.Hour==firstMillisecondDateTimeOffset.Hour); + RunTest(s, c => c.NullableDateTimeOffset.Value.Hour==nullableDateTimeOffset.Hour); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.Hour==wrongDateTimeOffset.Hour); - RunWrongTest(c => c.MillisecondDateTimeOffset.Hour==wrongMillisecondDateTimeOffset.Hour); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Hour==wrongDateTimeOffset.Hour); + RunWrongTest(s, c => c.DateTimeOffset.Hour==wrongDateTimeOffset.Hour); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Hour==wrongMillisecondDateTimeOffset.Hour); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Hour==wrongDateTimeOffset.Hour); }); } [Test] public void ExtractMinuteTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.Minute==firstDateTimeOffset.Minute); - RunTest(c => c.MillisecondDateTimeOffset.Minute==firstMillisecondDateTimeOffset.Minute); - RunTest(c => c.NullableDateTimeOffset.Value.Minute==nullableDateTimeOffset.Minute); + RunTest(s, c => c.DateTimeOffset.Minute==firstDateTimeOffset.Minute); + RunTest(s, c => c.MillisecondDateTimeOffset.Minute==firstMillisecondDateTimeOffset.Minute); + RunTest(s, c => c.NullableDateTimeOffset.Value.Minute==nullableDateTimeOffset.Minute); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.Minute==wrongDateTimeOffset.Minute); - RunWrongTest(c => c.MillisecondDateTimeOffset.Minute==wrongMillisecondDateTimeOffset.Minute); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Minute==wrongDateTimeOffset.Minute); + RunWrongTest(s, c => c.DateTimeOffset.Minute==wrongDateTimeOffset.Minute); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Minute==wrongMillisecondDateTimeOffset.Minute); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Minute==wrongDateTimeOffset.Minute); }); } [Test] public void ExtractSecondTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.Second==firstDateTimeOffset.Second); - RunTest(c => c.MillisecondDateTimeOffset.Second==firstMillisecondDateTimeOffset.Second); - RunTest(c => c.NullableDateTimeOffset.Value.Second==nullableDateTimeOffset.Second); + RunTest(s, c => c.DateTimeOffset.Second==firstDateTimeOffset.Second); + RunTest(s, c => c.MillisecondDateTimeOffset.Second==firstMillisecondDateTimeOffset.Second); + RunTest(s, c => c.NullableDateTimeOffset.Value.Second==nullableDateTimeOffset.Second); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.Second==wrongDateTimeOffset.Second); - RunWrongTest(c => c.MillisecondDateTimeOffset.Second==wrongMillisecondDateTimeOffset.Second); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Second==wrongDateTimeOffset.Second); + RunWrongTest(s, c => c.DateTimeOffset.Second==wrongDateTimeOffset.Second); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Second==wrongMillisecondDateTimeOffset.Second); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Second==wrongDateTimeOffset.Second); }); } [Test] public void ExtractMillisecondTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); - RunTest(c => c.MillisecondDateTimeOffset.Millisecond==firstMillisecondDateTimeOffset.Millisecond); + RunTest(s, c => c.MillisecondDateTimeOffset.Millisecond==firstMillisecondDateTimeOffset.Millisecond); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.MillisecondDateTimeOffset.Second==wrongMillisecondDateTimeOffset.Millisecond); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Second==wrongMillisecondDateTimeOffset.Millisecond); }); } [Test] public void ExtractDateTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.Date==firstDateTimeOffset.Date); - RunTest(c => c.MillisecondDateTimeOffset.Date==firstMillisecondDateTimeOffset.Date); - RunTest(c => c.NullableDateTimeOffset.Value.Date==nullableDateTimeOffset.Date); + RunTest(s, c => c.DateTimeOffset.Date==firstDateTimeOffset.Date); + RunTest(s, c => c.MillisecondDateTimeOffset.Date==firstMillisecondDateTimeOffset.Date); + RunTest(s, c => c.NullableDateTimeOffset.Value.Date==nullableDateTimeOffset.Date); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.Date==wrongDateTimeOffset.Date); - RunWrongTest(c => c.MillisecondDateTimeOffset.Date==wrongMillisecondDateTimeOffset.Date); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Date==wrongDateTimeOffset.Date); + RunWrongTest(s, c => c.DateTimeOffset.Date==wrongDateTimeOffset.Date); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.Date==wrongMillisecondDateTimeOffset.Date); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Date==wrongDateTimeOffset.Date); }); } @@ -158,34 +158,34 @@ public void ExtractDateTest() public void ExtractDateFromMicrosecondsTest(string testValueString) { Require.ProviderIs(StorageProvider.SqlServer); - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var testDateTimeOffset = DateTimeOffset.Parse(testValueString); - _ = new SingleDateTimeOffsetEntity() { MillisecondDateTimeOffset = testDateTimeOffset }; - RunTest(c => c.MillisecondDateTimeOffset.Date == testDateTimeOffset.Date); + _ = new SingleDateTimeOffsetEntity(s) { MillisecondDateTimeOffset = testDateTimeOffset }; + RunTest(s, c => c.MillisecondDateTimeOffset.Date == testDateTimeOffset.Date); }); } [Test] public void ExtractTimeOfDayTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); - RunTest(c => c.DateTimeOffset.TimeOfDay==firstDateTimeOffset.TimeOfDay); + RunTest(s, c => c.DateTimeOffset.TimeOfDay==firstDateTimeOffset.TimeOfDay); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.TimeOfDay==wrongDateTimeOffset.TimeOfDay); + RunWrongTest(s, c => c.DateTimeOffset.TimeOfDay==wrongDateTimeOffset.TimeOfDay); }); } [Test] public void ExtractTimeOfDayWithMillisecondsTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); - RunTest(c => c.MillisecondDateTimeOffset.TimeOfDay==firstMillisecondDateTimeOffset.TimeOfDay); + RunTest(s, c => c.MillisecondDateTimeOffset.TimeOfDay==firstMillisecondDateTimeOffset.TimeOfDay); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.MillisecondDateTimeOffset.TimeOfDay==wrongMillisecondDateTimeOffset.TimeOfDay); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.TimeOfDay==wrongMillisecondDateTimeOffset.TimeOfDay); }); } @@ -194,132 +194,132 @@ public void ExtractTimeOfDayTicksTest() { Require.ProviderIsNot(StorageProvider.PostgreSql | StorageProvider.Oracle); - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); - RunTest(c => c.DateTimeOffset.TimeOfDay.Ticks == firstDateTimeOffset.TimeOfDay.Ticks); + RunTest(s, c => c.DateTimeOffset.TimeOfDay.Ticks == firstDateTimeOffset.TimeOfDay.Ticks); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.TimeOfDay.Ticks == wrongDateTimeOffset.TimeOfDay.Ticks); + RunWrongTest(s, c => c.DateTimeOffset.TimeOfDay.Ticks == wrongDateTimeOffset.TimeOfDay.Ticks); }); } [Test] public void ExtractTimeOfDayOfNullableValueTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.NullableDateTimeOffset.Value.TimeOfDay==nullableDateTimeOffset.TimeOfDay); + RunTest(s, c => c.NullableDateTimeOffset.Value.TimeOfDay==nullableDateTimeOffset.TimeOfDay); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); - RunWrongTest(c => c.NullableDateTimeOffset.Value.TimeOfDay==wrongDateTimeOffset.TimeOfDay); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.TimeOfDay==wrongDateTimeOffset.TimeOfDay); }); } [Test] public void ExtractDayOfYearTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.DayOfYear==firstDateTimeOffset.DayOfYear); - RunTest(c => c.MillisecondDateTimeOffset.DayOfYear==firstMillisecondDateTimeOffset.DayOfYear); - RunTest(c => c.NullableDateTimeOffset.Value.DayOfYear==nullableDateTimeOffset.DayOfYear); + RunTest(s, c => c.DateTimeOffset.DayOfYear==firstDateTimeOffset.DayOfYear); + RunTest(s, c => c.MillisecondDateTimeOffset.DayOfYear==firstMillisecondDateTimeOffset.DayOfYear); + RunTest(s, c => c.NullableDateTimeOffset.Value.DayOfYear==nullableDateTimeOffset.DayOfYear); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.DayOfYear==wrongDateTimeOffset.DayOfYear); - RunWrongTest(c => c.MillisecondDateTimeOffset.DayOfYear==wrongMillisecondDateTimeOffset.DayOfYear); - RunWrongTest(c => c.NullableDateTimeOffset.Value.DayOfYear==wrongDateTimeOffset.DayOfYear); + RunWrongTest(s, c => c.DateTimeOffset.DayOfYear==wrongDateTimeOffset.DayOfYear); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.DayOfYear==wrongMillisecondDateTimeOffset.DayOfYear); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.DayOfYear==wrongDateTimeOffset.DayOfYear); }); } [Test] public void ExtractDayOfWeekTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.DayOfWeek==firstDateTimeOffset.DayOfWeek); - RunTest(c => c.MillisecondDateTimeOffset.DayOfWeek==firstMillisecondDateTimeOffset.DayOfWeek); - RunTest(c => c.NullableDateTimeOffset.Value.DayOfWeek==nullableDateTimeOffset.DayOfWeek); + RunTest(s, c => c.DateTimeOffset.DayOfWeek==firstDateTimeOffset.DayOfWeek); + RunTest(s, c => c.MillisecondDateTimeOffset.DayOfWeek==firstMillisecondDateTimeOffset.DayOfWeek); + RunTest(s, c => c.NullableDateTimeOffset.Value.DayOfWeek==nullableDateTimeOffset.DayOfWeek); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.DayOfWeek==wrongDateTimeOffset.DayOfWeek); - RunWrongTest(c => c.MillisecondDateTimeOffset.DayOfWeek==wrongMillisecondDateTimeOffset.DayOfWeek); - RunWrongTest(c => c.NullableDateTimeOffset.Value.DayOfWeek==wrongDateTimeOffset.DayOfWeek); + RunWrongTest(s, c => c.DateTimeOffset.DayOfWeek==wrongDateTimeOffset.DayOfWeek); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.DayOfWeek==wrongMillisecondDateTimeOffset.DayOfWeek); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.DayOfWeek==wrongDateTimeOffset.DayOfWeek); }); } [Test] public void ExtractDateTimeTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.DateTime==firstDateTimeOffset.DateTime); - RunTest(c => c.MillisecondDateTimeOffset.DateTime==firstMillisecondDateTimeOffset.DateTime); - RunTest(c => c.NullableDateTimeOffset.Value.DateTime==nullableDateTimeOffset.DateTime); + RunTest(s, c => c.DateTimeOffset.DateTime==firstDateTimeOffset.DateTime); + RunTest(s, c => c.MillisecondDateTimeOffset.DateTime==firstMillisecondDateTimeOffset.DateTime); + RunTest(s, c => c.NullableDateTimeOffset.Value.DateTime==nullableDateTimeOffset.DateTime); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.DateTime==wrongDateTimeOffset.DateTime); - RunWrongTest(c => c.MillisecondDateTimeOffset.DateTime==wrongMillisecondDateTimeOffset.DateTime); - RunWrongTest(c => c.NullableDateTimeOffset.Value.DateTime==wrongDateTimeOffset.DateTime); + RunWrongTest(s, c => c.DateTimeOffset.DateTime==wrongDateTimeOffset.DateTime); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.DateTime==wrongMillisecondDateTimeOffset.DateTime); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.DateTime==wrongDateTimeOffset.DateTime); }); } [Test] public void ExtractLocalDateTimeTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.LocalDateTime==firstDateTimeOffset.LocalDateTime); - RunTest(c => c.MillisecondDateTimeOffset.LocalDateTime==firstMillisecondDateTimeOffset.LocalDateTime); - RunTest(c => c.NullableDateTimeOffset.Value.LocalDateTime==nullableDateTimeOffset.LocalDateTime); + RunTest(s, c => c.DateTimeOffset.LocalDateTime==firstDateTimeOffset.LocalDateTime); + RunTest(s, c => c.MillisecondDateTimeOffset.LocalDateTime==firstMillisecondDateTimeOffset.LocalDateTime); + RunTest(s, c => c.NullableDateTimeOffset.Value.LocalDateTime==nullableDateTimeOffset.LocalDateTime); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.LocalDateTime==WrongDateTimeOffset.LocalDateTime); - RunWrongTest(c => c.MillisecondDateTimeOffset.LocalDateTime==wrongMillisecondDateTimeOffset.LocalDateTime); - RunWrongTest(c => c.NullableDateTimeOffset.Value.LocalDateTime==wrongDateTimeOffset.LocalDateTime); + RunWrongTest(s, c => c.DateTimeOffset.LocalDateTime==WrongDateTimeOffset.LocalDateTime); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.LocalDateTime==wrongMillisecondDateTimeOffset.LocalDateTime); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.LocalDateTime==wrongDateTimeOffset.LocalDateTime); }); } [Test] public void ExtractUtcDateTimeTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.UtcDateTime==firstDateTimeOffset.UtcDateTime); - RunTest(c => c.MillisecondDateTimeOffset.UtcDateTime==firstMillisecondDateTimeOffset.UtcDateTime); - RunTest(c => c.NullableDateTimeOffset.Value.UtcDateTime==nullableDateTimeOffset.UtcDateTime); + RunTest(s, c => c.DateTimeOffset.UtcDateTime==firstDateTimeOffset.UtcDateTime); + RunTest(s, c => c.MillisecondDateTimeOffset.UtcDateTime==firstMillisecondDateTimeOffset.UtcDateTime); + RunTest(s, c => c.NullableDateTimeOffset.Value.UtcDateTime==nullableDateTimeOffset.UtcDateTime); var wrongDateTimeOffset = TryMoveToLocalTimeZone(WrongDateTimeOffset); var wrongMillisecondDateTimeOffset = TryMoveToLocalTimeZone(WrongMillisecondDateTimeOffset); - RunWrongTest(c => c.DateTimeOffset.UtcDateTime==wrongDateTimeOffset.UtcDateTime); - RunWrongTest(c => c.MillisecondDateTimeOffset.UtcDateTime==wrongMillisecondDateTimeOffset.UtcDateTime); - RunWrongTest(c => c.NullableDateTimeOffset.Value.UtcDateTime==wrongDateTimeOffset.UtcDateTime); + RunWrongTest(s, c => c.DateTimeOffset.UtcDateTime==wrongDateTimeOffset.UtcDateTime); + RunWrongTest(s, c => c.MillisecondDateTimeOffset.UtcDateTime==wrongMillisecondDateTimeOffset.UtcDateTime); + RunWrongTest(s, c => c.NullableDateTimeOffset.Value.UtcDateTime==wrongDateTimeOffset.UtcDateTime); }); } [Test] public void ExtractOffsetTest() { - ExecuteInsideSession(() => { + ExecuteInsideSession((s) => { var firstDateTimeOffset = TryMoveToLocalTimeZone(FirstDateTimeOffset); var firstMillisecondDateTimeOffset = TryMoveToLocalTimeZone(FirstMillisecondDateTimeOffset); var nullableDateTimeOffset = TryMoveToLocalTimeZone(NullableDateTimeOffset); - RunTest(c => c.DateTimeOffset.Offset==firstDateTimeOffset.Offset); - RunTest(c => c.MillisecondDateTimeOffset.Offset==firstMillisecondDateTimeOffset.Offset); - RunTest(c => c.NullableDateTimeOffset.Value.Offset==nullableDateTimeOffset.Offset); + RunTest(s, c => c.DateTimeOffset.Offset==firstDateTimeOffset.Offset); + RunTest(s, c => c.MillisecondDateTimeOffset.Offset==firstMillisecondDateTimeOffset.Offset); + RunTest(s, c => c.NullableDateTimeOffset.Value.Offset==nullableDateTimeOffset.Offset); }); } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs index 5c73276e5..1f26abae0 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Linq.Expressions; using NUnit.Framework; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Services; using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.DateTimeOffsets @@ -184,19 +186,39 @@ private void WherePrivate(Expression> whereExpression, Expr var compiledWhereExpression = whereExpression.Compile(); var compiledOrderByExpression = orderByExpression.Compile(); - var whereLocal = Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression).ToArray(); - var whereByServer = Query.All().Where(whereExpression).OrderBy(orderByExpression).ToArray(); + var session = Session.Current; + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + + var whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression).ToArray(); + var whereByServer = session.Query.All().Where(whereExpression).OrderBy(orderByExpression).ToArray(); Assert.That(whereLocal.Length, Is.Not.EqualTo(0)); Assert.That(whereByServer.Length, Is.Not.EqualTo(0)); Assert.IsTrue(whereLocal.SequenceEqual(whereByServer)); - whereByServer = Query.All().Where(whereExpression).OrderByDescending(orderByExpression).ToArray(); - whereLocal = Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression).ToArray(); + whereByServer = session.Query.All().Where(whereExpression).OrderByDescending(orderByExpression).ToArray(); + whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression).ToArray(); + + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; Assert.That(whereLocal.Length, Is.Not.EqualTo(0)); Assert.That(whereByServer.Length, Is.Not.EqualTo(0)); Assert.IsFalse(whereLocal.SequenceEqual(whereByServer)); } + + private void Events_DbCommandExecuting(object sender, DbCommandEventArgs e) + { + var command = e.Command; + var commandText = command.CommandText; + Console.WriteLine("No Modifications SQL Text:"); + Console.WriteLine(commandText); + var parameters = command.Parameters; + + Console.Write(" Parameters: "); + for (int i = 0, count = parameters.Count; i < count; i++) { + var parameter = parameters[i]; + Console.WriteLine($"{parameter.ParameterName} = {parameter.Value}"); + } + } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs index cd454cfa2..5db11fdd9 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs @@ -42,6 +42,7 @@ protected override void RegisterTypes(DomainConfiguration configuration) configuration.Types.Register(typeof (MillisecondDateTimeOffsetEntity)); configuration.Types.Register(typeof (NullableDateTimeOffsetEntity)); configuration.Types.Register(typeof (DateTimeEntity)); + configuration.Types.Register(typeof(MinMaxDateTimeOffsetEntity)); } protected override void InitializeCustomSettings(DomainConfiguration configuration) @@ -61,7 +62,7 @@ protected override void CheckRequirements() protected override void PopulateEntities(Session session) { - new SingleDateTimeOffsetEntity { + _ = new SingleDateTimeOffsetEntity(session) { DateTimeOffset = FirstDateTimeOffset, MillisecondDateTimeOffset = FirstMillisecondDateTimeOffset, NullableDateTimeOffset = NullableDateTimeOffset @@ -98,50 +99,56 @@ protected override void PopulateEntities(Session session) FirstMillisecondDateTime.Add(new TimeSpan(987, 23, 34, 45)), }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.ToOffset(FirstOffset) }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.ToOffset(SecondOffset) }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.ToOffset(TimeSpan.Zero) }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.Date }; - new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset }; - new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset.ToOffset(FirstOffset) }; - new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset.ToOffset(SecondOffset) }; - new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset.Date }; - new DateTimeOffsetEntity { DateTimeOffset = FirstDateTime }; - new DateTimeOffsetEntity { DateTimeOffset = new DateTimeOffset(FirstDateTime, TimeSpan.Zero) }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.ToOffset(FirstOffset) }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.ToOffset(SecondOffset) }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.ToOffset(TimeSpan.Zero) }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTimeOffset.Date }; + _ = new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset }; + _ = new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset.ToOffset(FirstOffset) }; + _ = new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset.ToOffset(SecondOffset) }; + _ = new DateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset.Date }; + _ = new DateTimeOffsetEntity { DateTimeOffset = FirstDateTime }; + _ = new DateTimeOffsetEntity { DateTimeOffset = new DateTimeOffset(FirstDateTime, TimeSpan.Zero) }; var index = 0; - foreach (var dateTime in dateTimes) - new DateTimeOffsetEntity(dateTime, ++index % 3==0 ? FirstOffset : SecondOffset); - - new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.ToOffset(FirstOffset) }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.ToOffset(SecondOffset) }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.ToOffset(TimeSpan.Zero) }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.Date }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset.ToOffset(FirstOffset) }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset.ToOffset(SecondOffset) }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset.Date }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondDateTime }; - new MillisecondDateTimeOffsetEntity { DateTimeOffset = new DateTimeOffset(SecondDateTime, TimeSpan.Zero) }; + foreach (var dateTime in dateTimes) { + _ = new DateTimeOffsetEntity(dateTime, ++index % 3 == 0 ? FirstOffset : SecondOffset); + } + + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.ToOffset(FirstOffset) }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.ToOffset(SecondOffset) }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.ToOffset(TimeSpan.Zero) }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = FirstMillisecondDateTimeOffset.Date }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset.ToOffset(FirstOffset) }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset.ToOffset(SecondOffset) }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondMillisecondDateTimeOffset.Date }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondDateTimeOffset }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = SecondDateTime }; + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = new DateTimeOffset(SecondDateTime, TimeSpan.Zero) }; index = 0; - foreach (var dateTime in dateTimesWithMilliseconds) - new MillisecondDateTimeOffsetEntity(dateTime, ++index % 3==0 ? FirstOffset : SecondOffset); + foreach (var dateTime in dateTimesWithMilliseconds) { + _ = new MillisecondDateTimeOffsetEntity(dateTime, ++index % 3 == 0 ? FirstOffset : SecondOffset); + } var dateTimeOffset = FirstMillisecondDateTimeOffset.AddYears(10); - for (var i = 0; i < 1000; ++i) - new MillisecondDateTimeOffsetEntity { DateTimeOffset = dateTimeOffset.AddMilliseconds(i) }; + for (var i = 0; i < 1000; ++i) { + _ = new MillisecondDateTimeOffsetEntity { DateTimeOffset = dateTimeOffset.AddMilliseconds(i) }; + } + + foreach (var dateTimeEntity in Query.All()) { + _ = new NullableDateTimeOffsetEntity(dateTimeEntity); + } - foreach (var dateTimeEntity in Query.All()) - new NullableDateTimeOffsetEntity(dateTimeEntity); + _ = new NullableDateTimeOffsetEntity { DateTimeOffset = null }; + _ = new NullableDateTimeOffsetEntity { DateTimeOffset = null }; - new NullableDateTimeOffsetEntity { DateTimeOffset = null }; - new NullableDateTimeOffsetEntity { DateTimeOffset = null }; + _ = new MinMaxDateTimeOffsetEntity(session) { MinValue = DateTimeOffset.MinValue, MaxValue = DateTimeOffset.MaxValue }; } protected DateTimeOffset TryMoveToLocalTimeZone(DateTimeOffset dateTimeOffset) diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs index cd6c8a03d..8ce38ec46 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs @@ -43,6 +43,35 @@ public class SingleDateTimeOffsetEntity : Entity [Field] public DateTimeOffset? NullableDateTimeOffset { get; set; } + + public SingleDateTimeOffsetEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class MinMaxDateTimeOffsetEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public DateTimeOffset MinValue { get; set; } + + [Field] + public DateTimeOffset MaxValue { get; set; } + + [Field] + public DateTimeOffset? NullableMinValue { get; set; } + + [Field] + public DateTimeOffset? NullableMaxValue { get; set; } + + public MinMaxDateTimeOffsetEntity(Session session) + : base(session) + { + } } [HierarchyRoot] @@ -95,6 +124,30 @@ public NullableDateTimeEntity(Session session) } } + [HierarchyRoot] + public class MinMaxDateTimeEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public DateTime MinValue { get; set; } + + [Field] + public DateTime MaxValue { get; set; } + + [Field] + public DateTime? NullableMinValue { get; set; } + + [Field] + public DateTime? NullableMaxValue { get; set; } + + public MinMaxDateTimeEntity(Session session) + : base(session) + { + } + } + [HierarchyRoot] public class DateTimeOffsetEntity : Entity { @@ -279,6 +332,30 @@ public SingleDateOnlyEntity(Session session) } } + [HierarchyRoot] + public class MinMaxDateOnlyEntity : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public DateOnly MinValue { get; set; } + + [Field] + public DateOnly MaxValue { get; set; } + + [Field] + public DateOnly? NullableMinValue { get; set; } + + [Field] + public DateOnly? NullableMaxValue { get; set; } + + public MinMaxDateOnlyEntity(Session session) + : base(session) + { + } + } + [HierarchyRoot] public class TimeOnlyEntity : Entity { From b77aa485bd4f2761101c8e05b94c02bdc3a8837c Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 18:27:52 +0500 Subject: [PATCH 14/48] Required changes for type mapper for correct reading writing values --- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 124 ++++++++++++++++-- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index 69f81b727..3776a085a 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -10,6 +10,7 @@ using System.Security; using Npgsql; using NpgsqlTypes; +using Xtensive.Orm.PostgreSql; using Xtensive.Reflection.PostgreSql; @@ -17,6 +18,8 @@ namespace Xtensive.Sql.Drivers.PostgreSql.v8_0 { internal class TypeMapper : Sql.TypeMapper { + protected readonly bool legacyTimestampBehaviorEnabled; + public override bool IsParameterCastRequired(Type type) { switch (Type.GetTypeCode(type)) { @@ -113,9 +116,11 @@ public override void BindTimeSpan(DbParameter parameter, object value) { var nativeParameter = (NpgsqlParameter) parameter; nativeParameter.NpgsqlDbType = NpgsqlDbType.Interval; - nativeParameter.Value = value != null - ? (object) new NpgsqlTimeSpan((TimeSpan) value) - : DBNull.Value; + nativeParameter.NpgsqlValue = value is null + ? DBNull.Value + : value is TimeSpan timeSpanValue + ? (object) CreateNativeIntervalFromTimeSpan(timeSpanValue) + : throw ValueNotOfTypeError(nameof(WellKnownTypes.TimeSpanType)); } public override void BindGuid(DbParameter parameter, object value) @@ -124,12 +129,48 @@ public override void BindGuid(DbParameter parameter, object value) parameter.Value = value == null ? (object) DBNull.Value : SqlHelper.GuidToString((Guid) value); } + [SecuritySafeCritical] + public override void BindDateTime(DbParameter parameter, object value) + { + if (legacyTimestampBehaviorEnabled) { + base.BindDateTime(parameter, value); + } + else { + var nativeParameter = (NpgsqlParameter) parameter; + // For some reason Npgsql team mapped DbType.DateTime to timestamp WITH timezone + // (which suppose to be pair to DateTimeOffset) and DbType.DateTime2 to timestamp WITHOUT timezone + // in Npgsql 6+, though both types have the same range of values and resolution. + // + // If no explicit type declared it seems to be identified by DateTime value's Kind, + // so now we have to unbox-box value to change kind of value because of this "talanted" person. + nativeParameter.NpgsqlDbType = NpgsqlDbType.Timestamp; + nativeParameter.Value = value is null + ? DBNull.Value + : value is DateTime dtValue + ? (object) DateTime.SpecifyKind(dtValue, DateTimeKind.Unspecified) + : throw ValueNotOfTypeError(nameof(WellKnownTypes.DateTimeType)); + } + } + [SecuritySafeCritical] public override void BindDateTimeOffset(DbParameter parameter, object value) { var nativeParameter = (NpgsqlParameter) parameter; - nativeParameter.NpgsqlDbType = NpgsqlDbType.TimestampTz; - nativeParameter.NpgsqlValue = value ?? DBNull.Value; + if (legacyTimestampBehaviorEnabled) { + nativeParameter.NpgsqlDbType = NpgsqlDbType.TimestampTz; + nativeParameter.NpgsqlValue = value ?? DBNull.Value; + } + else { + nativeParameter.NpgsqlDbType = NpgsqlDbType.TimestampTz; + + // Manual switch to universal time is required by Npgsql from now on, + // Npgsql team "untaught" the library to do it. + nativeParameter.NpgsqlValue = value is null + ? DBNull.Value + : value is DateTimeOffset dateTimeOffset + ? (object) dateTimeOffset.ToUniversalTime() + : throw ValueNotOfTypeError(nameof(WellKnownTypes.DateTimeOffsetType)); + } } public override SqlValueType MapByte(int? length, int? precision, int? scale) @@ -181,9 +222,14 @@ public override object ReadGuid(DbDataReader reader, int index) public override object ReadTimeSpan(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; - return (TimeSpan) nativeReader.GetInterval(index); + var nativeInterval = nativeReader.GetFieldValue(index); + + // support for full-range of Timespans required us to use raw type + // and construct timespan from its' values. + return ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); } + [SecuritySafeCritical] public override object ReadDecimal(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; @@ -195,7 +241,69 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; var value = nativeReader.GetFieldValue(index); - return value; + if (legacyTimestampBehaviorEnabled) { + // Npgsql 4 or older behavior + return value; + } + else { + // Probably the "mastermind" who made parameter conversion before setting value to parameter be required + // also forgot about PostgreSQL's built-in "SET TIME ZONE" feature for session, which affects values of TimeStampTz + // Now applications have to use either local/utc timezone everywhere OR somehow "remember" what they've set in SET TIME ZONE + // for being able to get values in the timezone they've set. (facapalm) + // + // BTW, Npgsql has no API that would provide us current connection timezone so we could apply it to values, + // there is internal setting in NpgsqlConnection but it is null no matter what is set by + // 'SET TIME ZONE' statement :-) + // + // We'll use local time, that's it! SET TIME ZONE will not work! + if (value == DateTimeOffset.MinValue || value == DateTimeOffset.MaxValue) + return value; + return value.ToLocalTime(); + } + } + + protected internal static NpgsqlInterval CreateNativeIntervalFromTimeSpan(in TimeSpan timeSpan) + { + // Previous Npgsql versions used days and time, no months. + // Thought we can write everything as time, we keep days and time format + + var ticks = timeSpan.Ticks; + + var days = timeSpan.Days; + var timeTicks = ticks - (days * TimeSpan.TicksPerDay); +#if NET7_0_OR_GREATER + var microseconds = timeTicks / TimeSpan.TicksPerMicrosecond; +#else + var microseconds = timeTicks / 10L; // same as TimeSpan.TicksPerMicrosecond available in .NET7+ +#endif + // no months! + return new NpgsqlInterval(0, days, microseconds); + } + + protected internal static TimeSpan ResurrectTimeSpanFromNpgsqlInterval(in NpgsqlInterval npgsqlInterval) + { + // We don't write "Months" part of NpgsqlInterval to database + // because days in months is variable measure in PostgreSQL. + // We better use exact number of days. + // But if for some reason, there is Months value > 0 we treat it like each month has 30 days, + // it seems that Npgsql did the same assumption internally. + + var days = (npgsqlInterval.Months != 0) + ? npgsqlInterval.Months * WellKnown.IntervalDaysInMonth + npgsqlInterval.Days + : npgsqlInterval.Days; + + var ticksOfDays = days * TimeSpan.TicksPerDay; +#if NET7_0_OR_GREATER + var overallTicks = ticksOfDays + (npgsqlInterval.Time * TimeSpan.TicksPerMicrosecond); +#else + var overallTicks = ticksOfDays + (npgsqlInterval.Time * 10); //same as TimeSpan.TicksPerMicrosecond available in .NET7+ +#endif + return TimeSpan.FromTicks(overallTicks); + } + + internal protected ArgumentException ValueNotOfTypeError(string typeName) + { + return new ArgumentException($"Value is not of '{typeName}' type."); } // Constructors From bbd4a5980669f90747ae230bcb7f9a485ac6fc81 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 18:29:13 +0500 Subject: [PATCH 15/48] PostgreSql connection: access internal property to keep functionality --- .../Sql.Drivers.PostgreSql/Connection.cs | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs index 6d8e8cccf..61aa03a0a 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2021 Xtensive LLC. +// Copyright (C) 2009-2025 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 @@ -10,11 +10,15 @@ using System.Data.Common; using System.Threading; using System.Threading.Tasks; +using System.Reflection; +using System; namespace Xtensive.Sql.Drivers.PostgreSql { internal class Connection : SqlConnection { + private static readonly Func TransactionIsCompleteAccessor; + private NpgsqlConnection underlyingConnection; private NpgsqlTransaction activeTransaction; @@ -52,9 +56,8 @@ public override void Commit() EnsureTransactionIsActive(); try { - if (!IsTransactionCompleted()) { + if(!IsTransactionCompleted()) ActiveTransaction.Commit(); - } } finally { ActiveTransaction.Dispose(); @@ -67,9 +70,8 @@ public override async Task CommitAsync(CancellationToken token = default) EnsureIsNotDisposed(); EnsureTransactionIsActive(); try { - if (!IsTransactionCompleted()) { + if (!IsTransactionCompleted()) await ActiveTransaction.CommitAsync(token).ConfigureAwait(false); - } } finally { await ActiveTransaction.DisposeAsync().ConfigureAwait(false); @@ -83,9 +85,8 @@ public override void Rollback() EnsureTransactionIsActive(); try { - if (!IsTransactionCompleted()) { + if (!IsTransactionCompleted()) ActiveTransaction.Rollback(); - } } finally { ActiveTransaction.Dispose(); @@ -98,9 +99,8 @@ public override async Task RollbackAsync(CancellationToken token = default) EnsureIsNotDisposed(); EnsureTransactionIsActive(); try { - if (!IsTransactionCompleted()) { + if (!IsTransactionCompleted()) await ActiveTransaction.RollbackAsync(token).ConfigureAwait(false); - } } finally { await ActiveTransaction.DisposeAsync().ConfigureAwait(false); @@ -185,7 +185,7 @@ private async Task ExecuteNonQueryAsync(string commandText, CancellationToken to private bool IsTransactionCompleted() { - return activeTransaction != null && activeTransaction.IsCompleted; + return activeTransaction != null && TransactionIsCompleteAccessor(activeTransaction); } // Constructors @@ -196,5 +196,19 @@ public Connection(SqlDriver driver) { underlyingConnection = new NpgsqlConnection(); } + + static Connection() + { + // We have to use reflection to keep current behavior. + // The prop was public but they changed it to internal though it is read-only + // and didn't harm internal state. + // But it is important for us to know whether active transaction was completed + // to not try to make some actions. + var isCompletedProp = typeof(NpgsqlTransaction) + .GetProperty("IsCompleted", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new NullReferenceException(); + + TransactionIsCompleteAccessor = (Func) Delegate.CreateDelegate(typeof(Func), isCompletedProp.GetMethod); + } } } From a6e8ed363279f145c207f52f29b6828dc37ff65a Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 17 Feb 2025 18:31:38 +0500 Subject: [PATCH 16/48] Add support for infinity/-infinity replacement within Npgsql --- .../Sql.Drivers.PostgreSql/v8_0/Compiler.cs | 424 +++++++++++++++--- .../Sql.Drivers.PostgreSql/v8_0/Translator.cs | 6 +- .../Sql.Drivers.PostgreSql/v9_0/Compiler.cs | 12 +- 3 files changed, 378 insertions(+), 64 deletions(-) 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 94669a69f..ca4262b16 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-2023 Xtensive LLC. +// Copyright (C) 2003-2025 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -36,7 +36,24 @@ internal class Compiler : SqlCompiler private static readonly SqlLiteral ReferenceDateTimeLiteral = SqlDml.Literal(new DateTime(2001, 1, 1)); private static readonly SqlLiteral EpochLiteral = SqlDml.Literal(new DateTime(1970, 1, 1)); private static readonly SqlLiteral ReferenceDateLiteral = SqlDml.Literal(new DateOnly(2001, 1, 1)); - private static readonly SqlLiteral ZeroTimeLiteral = SqlDml.Literal(new TimeOnly(0, 0, 0)); + + private static readonly SqlNative ZeroTimeLiteral = SqlDml.Native("'00:00:00.000000'::time(6)"); + private static readonly SqlNative MaxTimeLiteral = SqlDml.Native("'23:59:59.999999'::time(6)"); + + private static readonly SqlNative DateMinValue = SqlDml.Native("'0001-01-01'::timestamp"); + private static readonly SqlNative DateMaxValue = SqlDml.Native("'9999-12-31'::timestamp"); + + private static readonly SqlNative DateTimeMinValue = SqlDml.Native("'0001-01-01 00:00:00.000000'::timestamp(6)"); + private static readonly SqlNative DateTimeMaxValue = SqlDml.Native("'9999-12-31 23:59:59.999999'::timestamp(6)"); + + private static readonly SqlNative DateTimeOffsetMinValue = SqlDml.Native("'0001-01-01 00:00:00.000000+00:00'::timestamp(6) with time zone"); + private static readonly SqlNative DateTimeOffsetMaxValue = SqlDml.Native("'9999-12-31 23:59:59.999999+00:00'::timestamp(6)"); + + private static readonly SqlNative Infinity = SqlDml.Native("'Infinity'"); + private static readonly SqlNative NegativeInfinity = SqlDml.Native("'-Infinity'"); + + + protected readonly bool infinityAliasForDatesEnabled; /// public override void Visit(SqlDeclareCursor node) @@ -116,7 +133,7 @@ public override void Visit(SqlFunctionCall node) ((node.Arguments[0] / SqlDml.Literal(nanosecondsPerSecond)) * OneSecondInterval).AcceptVisitor(this); return; case SqlFunctionType.IntervalToMilliseconds: - SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this); + VisitIntervalToMilliseconds(node); return; case SqlFunctionType.IntervalToNanoseconds: SqlHelper.IntervalToNanoseconds(node.Arguments[0]).AcceptVisitor(this); @@ -137,25 +154,25 @@ public override void Visit(SqlFunctionCall node) TimeToNanoseconds(node.Arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeTruncate: - (SqlDml.FunctionCall("date_trunc", "day", node.Arguments[0])).AcceptVisitor(this); + DateTimeTruncate(node.Arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeAddMonths: - (node.Arguments[0] + node.Arguments[1] * OneMonthInterval).AcceptVisitor(this); + DateTimeAddXxx(node.Arguments[0], node.Arguments[1] * OneMonthInterval).AcceptVisitor(this); return; case SqlFunctionType.DateTimeAddYears: - (node.Arguments[0] + node.Arguments[1] * OneYearInterval).AcceptVisitor(this); + DateTimeAddXxx(node.Arguments[0], node.Arguments[1] * OneYearInterval).AcceptVisitor(this); return; case SqlFunctionType.DateAddYears: - (node.Arguments[0] + node.Arguments[1] * OneYearInterval).AcceptVisitor(this); + DateAddXxx(node.Arguments[0], node.Arguments[1] * OneYearInterval).AcceptVisitor(this); return; case SqlFunctionType.DateAddMonths: - (node.Arguments[0] + node.Arguments[1] * OneMonthInterval).AcceptVisitor(this); + DateAddXxx(node.Arguments[0], node.Arguments[1] * OneMonthInterval).AcceptVisitor(this); return; case SqlFunctionType.DateAddDays: - (node.Arguments[0] + node.Arguments[1] * OneDayInterval).AcceptVisitor(this); + DateAddXxx(node.Arguments[0], node.Arguments[1] * OneDayInterval).AcceptVisitor(this); return; case SqlFunctionType.DateToString: - DateTimeToStringIso(node.Arguments[0], DateFormat).AcceptVisitor(this); + DateTimeToStringIso(node.Arguments[0], DateFormat, infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.TimeAddHours: (node.Arguments[0] + node.Arguments[1] * OneHourInterval).AcceptVisitor(this); @@ -164,10 +181,10 @@ public override void Visit(SqlFunctionCall node) (node.Arguments[0] + node.Arguments[1] * OneMinuteInterval).AcceptVisitor(this); return; case SqlFunctionType.TimeToString: - DateTimeToStringIso(node.Arguments[0], TimeFormat).AcceptVisitor(this); + DateTimeToStringIso(node.Arguments[0], TimeFormat, false).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToStringIso: - DateTimeToStringIso(node.Arguments[0], DateTimeIsoFormat).AcceptVisitor(this); + DateTimeToStringIso(node.Arguments[0], DateTimeIsoFormat, infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetTimeOfDay: DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); @@ -182,31 +199,31 @@ public override void Visit(SqlFunctionCall node) ConstructDateTimeOffset(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToDateTimeOffset: - DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + DateTimeToDateTimeOffset(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToDateTime: - DateTimeOffsetToDateTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToDateTime(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToDate: - DateTimeToDate(node.Arguments[0]).AcceptVisitor(this); + DateTimeToDate(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateToDateTime: - DateToDateTime(node.Arguments[0]).AcceptVisitor(this); + DateToDateTime(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToTime: - DateTimeToTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeToTime(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.TimeToDateTime: TimeToDateTime(node.Arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToDate: - DateTimeOffsetToDate(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToDate(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateToDateTimeOffset: - DateToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + DateToDateTimeOffset(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToTime: - DateTimeOffsetToTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToTime(node.Arguments[0], infinityAliasForDatesEnabled).AcceptVisitor(this); return; case SqlFunctionType.TimeToDateTimeOffset: TimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); @@ -284,8 +301,23 @@ public override void Visit(SqlCustomFunctionCall node) base.Visit(node); } - private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat) => - SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat); + protected virtual void VisitIntervalToMilliseconds(SqlFunctionCall node) + { + SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this); + } + + private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat, bool infinityEnabled) + { + var conversionExpression = SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat); + if (infinityEnabled) { + var @case = SqlDml.Case(); + @case[dateTime == Infinity] = SqlDml.FunctionCall("TO_CHAR", DateTimeMaxValue, isoFormat); + @case[dateTime == NegativeInfinity] = SqlDml.FunctionCall("TO_CHAR", DateTimeMinValue, isoFormat); + @case.Else = conversionExpression; + return @case; + } + return conversionExpression; + } private static SqlExpression IntervalToIsoString(SqlExpression interval, bool signed) { @@ -359,6 +391,34 @@ private static SqlExpression NpgsqlTypeConstructor(SqlExpression left, SqlExpres SqlDml.Native(")"))))); } + // it might be moved to context as some kind of extension + private readonly HashSet delayedExtractOperations = new(); + + private (SqlExpression min, SqlExpression max) ExtractMinMaxValuesForPart(SqlExtract node) + { + var actualPart = (SqlDateTimeOffsetPart) (node.IsDateTimePart + ? (int) node.DateTimePart + : node.IsDatePart + ? (int) node.DatePart + : node.IsDateTimeOffsetPart + ? (int) node.DateTimeOffsetPart + : throw new ArgumentOutOfRangeException()); + + return actualPart switch { + SqlDateTimeOffsetPart.Year => (SqlDml.Literal(1), SqlDml.Literal(9999)), + SqlDateTimeOffsetPart.Month => (SqlDml.Literal(1), SqlDml.Literal(12)), + SqlDateTimeOffsetPart.Day => (SqlDml.Literal(1), SqlDml.Literal(31)), + SqlDateTimeOffsetPart.DayOfWeek => (SqlDml.Literal(1), SqlDml.Literal(5)), // Monday and Friday + SqlDateTimeOffsetPart.DayOfYear => (SqlDml.Literal(1), SqlDml.Literal(365)), + SqlDateTimeOffsetPart.Hour => (SqlDml.Literal(0), SqlDml.Literal(23)), + SqlDateTimeOffsetPart.Minute or SqlDateTimeOffsetPart.Second => (SqlDml.Literal(0), SqlDml.Literal(59)), + SqlDateTimeOffsetPart.Millisecond => (SqlDml.Literal(0), SqlDml.Literal(999)), + SqlDateTimeOffsetPart.Nanosecond => (SqlDml.Literal(0), SqlDml.Literal(900)), + SqlDateTimeOffsetPart.TimeZoneHour or SqlDateTimeOffsetPart.TimeZoneMinute => (SqlDml.Literal(0), SqlDml.Literal(0)), + _ => throw new ArgumentOutOfRangeException() + }; + } + public override void Visit(SqlExtract node) { if (node.IsDateTimeOffsetPart) { @@ -381,7 +441,62 @@ public override void Visit(SqlExtract node) return; } } - base.Visit(node); + + if (infinityAliasForDatesEnabled + && (node.IsDatePart || node.IsDateTimePart || node.IsDateTimeOffsetPart) + && !delayedExtractOperations.Remove(node)) { + // If DateTime.MinValue => -Infinity and DateTime.MaxValue => Infinity conversion happens + // in Npgsql driver then we have to use SQL case statement to return correct values to the user. + // In this case we use original expression in ELSE part of CASE statement and postpone visiting + + _ = delayedExtractOperations.Add(node); + (SqlExpression min, SqlExpression max) minMaxValues = ExtractMinMaxValuesForPart(node); + var @case = SqlDml.Case(); + @case[node.Operand == Infinity] = minMaxValues.min; + @case[node.Operand == NegativeInfinity] = minMaxValues.max; + @case.Else = node; + @case.AcceptVisitor(this); + } + else { + base.Visit(node); + } + } + + public override void Visit(SqlLiteral node) + { + if (!infinityAliasForDatesEnabled) { + base.Visit(node); + } + else { + // to keep constants and parameters work the same way we have to make this check + var value = node.GetValue(); + var infinityExpression = value switch { + DateTime dtValue => dtValue == DateTime.MinValue + ? NegativeInfinity + : dtValue == DateTime.MaxValue + ? Infinity + : null, + DateOnly dtValue => dtValue == DateOnly.MinValue + ? NegativeInfinity + : dtValue == DateOnly.MaxValue + ? Infinity + : null, + DateTimeOffset dtValue => dtValue == DateTimeOffset.MinValue + ? NegativeInfinity + : dtValue == DateTimeOffset.MaxValue + ? Infinity + : null, + _ => null + }; + + if (infinityExpression is null) { + base.Visit(node); + } + else { + infinityExpression.AcceptVisitor(this); + } + + } } protected virtual SqlExpression ConstructDateTime(IReadOnlyList arguments) @@ -448,36 +563,145 @@ protected virtual SqlExpression TimeToNanoseconds(SqlExpression time) return nPerHour + nPerMinute + nPerSecond + nPerMillisecond; } - protected SqlExpression DateTimeOffsetExtractDate(SqlExpression timestamp) => - SqlDml.FunctionCall("DATE", timestamp); - protected SqlExpression DateTimeOffsetExtractDateTime(SqlExpression timestamp) => - SqlDml.Cast(timestamp, SqlType.DateTime); + protected SqlExpression DateTimeAddXxx(SqlExpression dateTime, SqlExpression addPart) + { + var resultExpression = (dateTime + addPart); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[dateTime == Infinity] = DateTimeMaxValue + addPart; + @case[dateTime == NegativeInfinity] = DateTimeMinValue + addPart; + @case.Else = resultExpression; + return @case; + } + return resultExpression; + } - protected SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression timeStamp) => - GetDateTimeInTimeZone(timeStamp, TimeSpan.Zero); - - protected SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression timestamp) => - SqlDml.Cast(timestamp, SqlType.DateTime); + protected SqlExpression DateTimeTruncate(SqlExpression dateTime) + { + var truncateExpression = SqlDml.FunctionCall("date_trunc", "day", dateTime); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[dateTime == Infinity] = SqlDml.Cast(DateMaxValue, SqlType.DateTime); + @case[dateTime == NegativeInfinity] = SqlDml.Cast(DateMinValue, SqlType.DateTime); + @case.Else = truncateExpression; + return @case; + } + return truncateExpression; + } + + protected SqlExpression DateAddXxx(SqlExpression date, SqlExpression addPart) + { + var resultExpression = (date + addPart); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[date == Infinity] = DateMaxValue + addPart; + @case[date == NegativeInfinity] = DateMinValue + addPart; + @case.Else = resultExpression; + return @case; + } + return (date + addPart); + } + + protected SqlExpression DateTimeOffsetExtractDate(SqlExpression timestamp) + { + var extractExpression = SqlDml.FunctionCall("DATE", timestamp); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[timestamp == Infinity] = DateMaxValue; + @case[timestamp == NegativeInfinity] = DateMinValue; + @case.Else = extractExpression; + return @case; + } + return extractExpression; + } + + protected SqlExpression DateTimeOffsetExtractDateTime(SqlExpression timestamp) + { + var extractExpression = SqlDml.Cast(timestamp, SqlType.DateTime); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[timestamp == Infinity] = DateTimeMaxValue; + @case[timestamp == NegativeInfinity] = DateTimeMinValue; + @case.Else = extractExpression; + return @case; + } + + return extractExpression; + } + + protected SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression timestamp) + { + var extractExpression = GetDateTimeInTimeZone(timestamp, TimeSpan.Zero); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[timestamp == Infinity] = DateTimeMaxValue; + @case[timestamp == NegativeInfinity] = DateTimeMinValue; + @case.Else = extractExpression; + return @case; + } + return extractExpression; + } + + protected SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression timestamp) + { + var extractExpression = SqlDml.Cast(timestamp, SqlType.DateTime); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[timestamp == Infinity] = DateTimeMaxValue; + @case[timestamp == NegativeInfinity] = DateTimeMinValue; + @case.Else = extractExpression; + return @case; + } + + return extractExpression; + } protected void DateTimeOffsetExtractOffset(SqlExtract node) { - using (context.EnterScope(node)) { - AppendTranslatedEntry(node); - translator.Translate(context.Output, node.DateTimeOffsetPart); - AppendTranslated(node, ExtractSection.From); - node.Operand.AcceptVisitor(this); - AppendSpace(); - AppendTranslatedExit(node); - AppendTranslated(SqlNodeType.Multiply); - OneSecondInterval.AcceptVisitor(this); + if (infinityAliasForDatesEnabled && !delayedExtractOperations.Remove(node)) { + // If DateTime.MinValue => -Infinity and DateTime.MaxValue => Infinity conversion happens + // in Npgsql driver then we have to use SQL case statement to return correct values to the user. + // In this case we use original expression in ELSE part of CASE statement and postpone visiting + + _ = delayedExtractOperations.Add(node); + var @case = SqlDml.Case(); + @case[node.Operand == Infinity] = SqlDml.Native("'00:00'"); + @case[node.Operand == NegativeInfinity] = SqlDml.Native("'00:00'"); + @case.Else = node; + @case.AcceptVisitor(this); + } + else { + using (context.EnterScope(node)) { + AppendTranslatedEntry(node); + translator.Translate(context.Output, node.DateTimeOffsetPart); + AppendTranslated(node, ExtractSection.From); + node.Operand.AcceptVisitor(this); + AppendSpace(); + AppendTranslatedExit(node); + AppendTranslated(SqlNodeType.Multiply); + OneSecondInterval.AcceptVisitor(this); + } } } - protected SqlExpression DateTimeOffsetTimeOfDay(SqlExpression timestamp) => - DateTimeOffsetSubstract(timestamp, SqlDml.DateTimeTruncate(timestamp)); + protected SqlExpression DateTimeOffsetTimeOfDay(SqlExpression timestamp) + { + var resultExpression = DateTimeOffsetSubstract(timestamp, SqlDml.DateTimeTruncate(timestamp)); + if (infinityAliasForDatesEnabled) { + var @case = SqlDml.Case(); + @case[timestamp == Infinity] = SqlDml.Cast(MaxTimeLiteral, SqlType.Interval); + @case[timestamp == NegativeInfinity] = SqlDml.Cast(ZeroTimeLiteral, SqlType.Interval); + @case.Else = resultExpression; + return @case; + } + return resultExpression; + } - protected SqlExpression DateTimeOffsetSubstract(SqlExpression timestamp1, SqlExpression timestamp2) => timestamp1 - timestamp2; + protected SqlExpression DateTimeOffsetSubstract(SqlExpression timestamp1, SqlExpression timestamp2) + { + return timestamp1 - timestamp2; + } protected SqlExpression ConstructDateTimeOffset(SqlExpression dateTimeExpression, SqlExpression offsetInMinutes) { @@ -504,32 +728,112 @@ protected SqlExpression GetOffsetAsStringExpression(SqlExpression offsetInMinute return IntervalToIsoString(intervalExpression, true); } - private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) => - SqlDml.Cast(dateTime, SqlType.DateTimeOffset); + private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(dateTime, SqlType.DateTimeOffset); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[dateTime == Infinity] = DateTimeOffsetMaxValue; + @case[dateTime == NegativeInfinity] = DateTimeOffsetMinValue; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } - private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) => - SqlDml.Cast(dateTimeOffset, SqlType.DateTime); + private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(dateTimeOffset, SqlType.DateTime); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[dateTimeOffset == Infinity] = DateTimeMaxValue; + @case[dateTimeOffset == NegativeInfinity] = DateTimeMinValue; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } - private static SqlExpression DateTimeToDate(SqlExpression dateTime) => - SqlDml.Cast(dateTime, SqlType.Date); + private static SqlExpression DateTimeToDate(SqlExpression dateTime, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(dateTime, SqlType.Date); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[dateTime == Infinity] = DateMaxValue; + @case[dateTime == NegativeInfinity] = DateMinValue; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } - private static SqlExpression DateToDateTime(SqlExpression date) => - SqlDml.Cast(date, SqlType.DateTime); + private static SqlExpression DateToDateTime(SqlExpression date, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(date, SqlType.DateTime); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[date == Infinity] = MaxTimeLiteral; + @case[date == NegativeInfinity] = ZeroTimeLiteral; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } - private static SqlExpression DateTimeToTime(SqlExpression dateTime) => - SqlDml.Cast(dateTime, SqlType.Time); + private static SqlExpression DateTimeToTime(SqlExpression dateTime, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(dateTime, SqlType.Time); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[dateTime == Infinity] = MaxTimeLiteral; + @case[dateTime == NegativeInfinity] = ZeroTimeLiteral; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } 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 DateTimeOffsetToDate(SqlExpression dateTimeOffset, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(dateTimeOffset, SqlType.Date); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[dateTimeOffset == Infinity] = DateMaxValue; + @case[dateTimeOffset == NegativeInfinity] = DateMinValue; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } - private static SqlExpression DateToDateTimeOffset(SqlExpression date) => - SqlDml.Cast(date, SqlType.DateTimeOffset); + private static SqlExpression DateToDateTimeOffset(SqlExpression date, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(date, SqlType.DateTimeOffset); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[date == Infinity] = DateTimeOffsetMaxValue; + @case[date == NegativeInfinity] = DateTimeOffsetMinValue; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } - private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset) => - SqlDml.Cast(dateTimeOffset, SqlType.Time); + private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset, bool infinityAliasEnabled) + { + var convertExpression = SqlDml.Cast(dateTimeOffset, SqlType.Time); + if (infinityAliasEnabled) { + var @case = SqlDml.Case(); + @case[dateTimeOffset == Infinity] = MaxTimeLiteral; + @case[dateTimeOffset == NegativeInfinity] = ZeroTimeLiteral; + @case.Else = convertExpression; + return @case; + } + return convertExpression; + } private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => SqlDml.Cast(EpochLiteral + time, SqlType.DateTimeOffset); 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 a61f5ebf6..76139d151 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-2023 Xtensive LLC. +// Copyright (C) 2012-2025 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -404,11 +404,11 @@ public override void Translate(SqlCompilerContext context, SqlExtract node, Extr } switch (section) { case ExtractSection.Entry: - _ = context.Output.AppendOpeningPunctuation(isSecond ? "(trunc(extract(" : "(extract("); + _ = context.Output.AppendOpeningPunctuation(isSecond || isMillisecond ? "(trunc(extract(" : "(extract("); break; case ExtractSection.Exit: _ = context.Output.Append(isMillisecond - ? ")::int8 % 1000)" + ? "))::int8 % 1000)" : isSecond ? ")))" : ")::int8)" ); break; diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs index b5a06437e..fcfe6835e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs @@ -4,13 +4,23 @@ // Created by: Denis Krjuchkov // Created: 2012.06.06 +using Xtensive.Sql.Dml; + namespace Xtensive.Sql.Drivers.PostgreSql.v9_0 { internal class Compiler : v8_4.Compiler { // Constructors - public Compiler(SqlDriver driver) + protected override void VisitIntervalToMilliseconds(SqlFunctionCall node) + { + AppendSpaceIfNecessary(); + _ = context.Output.Append("(EXTRACT(EPOCH FROM ("); + node.Arguments[0].AcceptVisitor(this); + _ = context.Output.Append(")) * 1000)"); + + } + public Compiler(PostgreSql.Driver driver) : base(driver) { From 446a8a2d02b7eea602742a133fdf9fd5af15cb98 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 18 Feb 2025 13:39:09 +0500 Subject: [PATCH 17/48] Another Npgsql exception detected as operation timeout --- Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs index a441b23d5..1c132aeea 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs @@ -100,6 +100,9 @@ private SqlExceptionType ProcessClientSideException(NpgsqlException clientSideEx } } } + if (innerException is TimeoutException timeoutException) { + return SqlExceptionType.OperationTimeout; + } return SqlExceptionType.Unknown; } From 8ab8c8b1123e3138c25f26364799d484664388b7 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 18 Feb 2025 17:16:15 +0500 Subject: [PATCH 18/48] Update Postgresql client library package to version 8.0.6 --- .../Sql.Drivers.PostgreSql/DriverFactory.cs | 3 --- Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj | 2 +- Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs index 4d524b305..5bebae42e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs @@ -47,9 +47,6 @@ protected override string BuildConnectionString(UrlInfo url) builder.Username = url.User; builder.Password = url.Password; } - else { - builder.IntegratedSecurity = true; - } // custom options foreach (var param in url.Params) { diff --git a/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj b/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj index b4bd3f57e..03fd921a6 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj +++ b/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj @@ -25,7 +25,7 @@ - + diff --git a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj index 0b1256f46..595e76199 100644 --- a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj +++ b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj @@ -20,7 +20,7 @@ - + From 06391a9fcfa1f13c9cd2e26172370038b930a8f5 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 19 Feb 2025 14:42:01 +0500 Subject: [PATCH 19/48] Pgsql connection: IsCompleted now can be replace with tx.Connection==null --- .../Sql.Drivers.PostgreSql/Connection.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs index 61aa03a0a..1e12ce0b7 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs @@ -17,8 +17,6 @@ namespace Xtensive.Sql.Drivers.PostgreSql { internal class Connection : SqlConnection { - private static readonly Func TransactionIsCompleteAccessor; - private NpgsqlConnection underlyingConnection; private NpgsqlTransaction activeTransaction; @@ -185,7 +183,7 @@ private async Task ExecuteNonQueryAsync(string commandText, CancellationToken to private bool IsTransactionCompleted() { - return activeTransaction != null && TransactionIsCompleteAccessor(activeTransaction); + return activeTransaction != null && activeTransaction.Connection == null; } // Constructors @@ -196,19 +194,5 @@ public Connection(SqlDriver driver) { underlyingConnection = new NpgsqlConnection(); } - - static Connection() - { - // We have to use reflection to keep current behavior. - // The prop was public but they changed it to internal though it is read-only - // and didn't harm internal state. - // But it is important for us to know whether active transaction was completed - // to not try to make some actions. - var isCompletedProp = typeof(NpgsqlTransaction) - .GetProperty("IsCompleted", BindingFlags.Instance | BindingFlags.NonPublic) - ?? throw new NullReferenceException(); - - TransactionIsCompleteAccessor = (Func) Delegate.CreateDelegate(typeof(Func), isCompletedProp.GetMethod); - } } } From 33c22e4b333a658ce0b2e2d45f17c7d506a0a979 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 19 Feb 2025 17:00:41 +0500 Subject: [PATCH 20/48] Connection implementations: direct access to activeTransaction property --- .../Sql.Drivers.PostgreSql/Connection.cs | 39 +++++++++---------- .../Sql.Drivers.SqlServer/Connection.cs | 10 ++--- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs index 1e12ce0b7..4c8d9441c 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs @@ -52,13 +52,13 @@ public override void Commit() { EnsureIsNotDisposed(); EnsureTransactionIsActive(); - try { - if(!IsTransactionCompleted()) - ActiveTransaction.Commit(); + if(!IsTransactionCompleted()) { + activeTransaction.Commit(); + } } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -68,11 +68,12 @@ public override async Task CommitAsync(CancellationToken token = default) EnsureIsNotDisposed(); EnsureTransactionIsActive(); try { - if (!IsTransactionCompleted()) - await ActiveTransaction.CommitAsync(token).ConfigureAwait(false); + if (!IsTransactionCompleted()) { + await activeTransaction.CommitAsync(token).ConfigureAwait(false); + } } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwait(false); + await activeTransaction.DisposeAsync().ConfigureAwait(false); ClearActiveTransaction(); } } @@ -81,13 +82,13 @@ public override void Rollback() { EnsureIsNotDisposed(); EnsureTransactionIsActive(); - try { - if (!IsTransactionCompleted()) - ActiveTransaction.Rollback(); + if (!IsTransactionCompleted()) { + activeTransaction.Rollback(); + } } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -97,11 +98,12 @@ public override async Task RollbackAsync(CancellationToken token = default) EnsureIsNotDisposed(); EnsureTransactionIsActive(); try { - if (!IsTransactionCompleted()) - await ActiveTransaction.RollbackAsync(token).ConfigureAwait(false); + if (!IsTransactionCompleted()) { + await activeTransaction.RollbackAsync(token).ConfigureAwait(false); + } } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwait(false); + await activeTransaction.DisposeAsync().ConfigureAwait(false); ClearActiveTransaction(); } } @@ -167,7 +169,7 @@ private void ExecuteNonQuery(string commandText) EnsureTransactionIsActive(); using var command = CreateCommand(commandText); - command.ExecuteNonQuery(); + _ = command.ExecuteNonQuery(); } private async Task ExecuteNonQueryAsync(string commandText, CancellationToken token) @@ -177,14 +179,11 @@ private async Task ExecuteNonQueryAsync(string commandText, CancellationToken to var command = CreateCommand(commandText); await using (command.ConfigureAwait(false)) { - await command.ExecuteNonQueryAsync(token).ConfigureAwait(false); + _ = await command.ExecuteNonQueryAsync(token).ConfigureAwait(false); } } - private bool IsTransactionCompleted() - { - return activeTransaction != null && activeTransaction.Connection == null; - } + private bool IsTransactionCompleted() => activeTransaction.Connection == null; // Constructors diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs index c05853c94..e2ffa278f 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs @@ -126,11 +126,11 @@ public override void Rollback() try { if (!IsTransactionZombied()) { - ActiveTransaction.Rollback(); + activeTransaction.Rollback(); } } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -143,11 +143,11 @@ public override async Task RollbackAsync(CancellationToken token = default) try { if (!IsTransactionZombied()) { - await ActiveTransaction.RollbackAsync(token).ConfigureAwait(false); + await activeTransaction.RollbackAsync(token).ConfigureAwait(false); } } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwait(false); + await activeTransaction.DisposeAsync().ConfigureAwait(false); ClearActiveTransaction(); } } @@ -353,7 +353,7 @@ await SqlHelper.NotifyConnectionOpeningFailedAsync(accessors, private bool IsTransactionZombied() { - return ActiveTransaction != null && ActiveTransaction.Connection == null; + return activeTransaction.Connection == null; } // Constructors From 2751df633e099785233f990a2261c65153a9138b Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 20 Feb 2025 12:31:08 +0500 Subject: [PATCH 21/48] Init AppContext switches before any tests --- .../TestConfiguration.cs | 33 +++++++++++++++++-- .../GlobalTestsSetup.cs | 23 +++++++++++++ Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs | 23 +++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 Orm/Xtensive.Orm.Tests.Sql/GlobalTestsSetup.cs create mode 100644 Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs diff --git a/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs b/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs index 835f0088d..4da6c89fa 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2010-2025 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: 2010.02.11 @@ -17,6 +17,9 @@ public sealed class TestConfiguration private const string StorageFileKey = "DO_STORAGE_FILE"; private const string ConfigurationFileKey = "DO_CONFIG_FILE"; + private const string InfinityAliasesKey = "DO_PG_INFINITY_ALIASES"; + private const string LegacyTimestapmKey = "DO_PG_LEGACY_TIMESTAMP"; + private const string DefaultStorage = "default"; private static readonly object InstanceLock = new object(); @@ -53,6 +56,30 @@ public ConnectionInfo GetConnectionInfo(string name) return new ConnectionInfo(items[0], items[1]); } + public void InitAppContextSwitches() + { + if (configuration.TryGetValue(Storage + "cs", out var info)) { + var items = info.Split(new[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries).Select(i => i.Trim()).ToArray(); + if (items.Length != 2) + throw new InvalidOperationException(string.Format("Invalid connection string format: {0}", info)); + var provider = items[0]; + if (provider.Equals(WellKnown.Provider.PostgreSql, StringComparison.OrdinalIgnoreCase)) + InitPostgreSqlSwitches(); + } + } + + private void InitPostgreSqlSwitches() + { + var infinityAliasesValue = GetEnvironmentVariable(InfinityAliasesKey); + if (bool.TryParse(infinityAliasesValue, out var bbb)) { + AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", !bbb); + } + var legacyTimestampsValue = GetEnvironmentVariable(LegacyTimestapmKey); + if (bool.TryParse(legacyTimestampsValue, out var ccc)) { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", ccc); + } + } + private string GetEnvironmentVariable(string key) { return new[] {EnvironmentVariableTarget.Process, EnvironmentVariableTarget.User, EnvironmentVariableTarget.Machine} diff --git a/Orm/Xtensive.Orm.Tests.Sql/GlobalTestsSetup.cs b/Orm/Xtensive.Orm.Tests.Sql/GlobalTestsSetup.cs new file mode 100644 index 000000000..2595d06cd --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/GlobalTestsSetup.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using NUnit.Framework; + +namespace Xtensive.Orm.Tests.Sql +{ + [SetUpFixture] + public class GlobalTestsSetup + { + [OneTimeSetUp] + public void GlobalSetup() + { + TestConfiguration.Instance.InitAppContextSwitches(); + } + + [OneTimeTearDown] + public void GlobalTeardown() + { + } + } +} diff --git a/Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs b/Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs new file mode 100644 index 000000000..47a9a5831 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using NUnit.Framework; + +namespace Xtensive.Orm.Tests +{ + [SetUpFixture] + public class GlobalTestsSetup + { + [OneTimeSetUp] + public void GlobalSetup() + { + TestConfiguration.Instance.InitAppContextSwitches(); + } + + [OneTimeTearDown] + public void GlobalTeardown() + { + } + } +} \ No newline at end of file From 9d75199ca61439347f767615983ee968a8ebd248 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 20 Feb 2025 12:48:29 +0500 Subject: [PATCH 22/48] No explicit tests because of AppContext Switches are initialized in global test setup via TestConfiguration if necessary --- .../PostgreSql/InfinityAliasTest.cs | 14 +------------- ...acyVsCurrentDateTimeOffsetParameterBinding.cs | 14 +------------- .../LegacyVsCurrentDateTimeParameterBinding.cs | 16 +--------------- 3 files changed, 3 insertions(+), 41 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs index 3d1fa464f..1b8dbf09d 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs @@ -25,14 +25,12 @@ public sealed class InfinityAliasTest : SqlTest protected override void CheckRequirements() { - // DO NOT check provider here. - // Require class uses driver creation which casues AppContext switch setup before TestFixtureSetup() method called + Require.ProviderIs(StorageProvider.PostgreSql); } protected override void TestFixtureSetUp() { base.TestFixtureSetUp(); - Require.ProviderIs(StorageProvider.PostgreSql); longTypeMapping = Driver.TypeMappings[typeof(long)]; dateOnlyTypeMapping = Driver.TypeMappings[typeof(DateOnly)]; @@ -78,7 +76,6 @@ public void MinDateTimeSelectByEqualityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MinDateTimeSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); @@ -87,7 +84,6 @@ public void MinDateTimeSelectDatePartInfinityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MinDateTimeSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); @@ -196,7 +192,6 @@ public void MaxDateTimeSelectByEqualityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MaxDateTimeSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); @@ -205,7 +200,6 @@ public void MaxDateTimeSelectDatePartInfinityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MaxDateTimeSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); @@ -313,7 +307,6 @@ public void MinDateOnlyByEqualityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MinDateOnlySelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); @@ -322,7 +315,6 @@ public void MinDateOnlySelectDatePartInfinityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MinDateOnlySelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); @@ -397,7 +389,6 @@ public void MaxDateOnlyByEqualityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MaxDateOnlySelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); @@ -406,7 +397,6 @@ public void MaxDateOnlySelectDatePartInfinityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MaxDateOnlySelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); @@ -481,7 +471,6 @@ public void MinDateTimeOffsetSelectByEqualityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MinDateTimeOffsetSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); @@ -490,7 +479,6 @@ public void MinDateTimeOffsetSelectDatePartInfinityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] public void MinDateTimeOffsetSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs index 90b3d3066..77fd4454b 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs @@ -17,19 +17,11 @@ public sealed class LegacyVsCurrentDateTimeOffsetParameterBinding : SqlTest protected override void CheckRequirements() { - // do not check provider here. - // Require class uses driver creation which casues AppContext switch setup before TestFixtureSetup() method called + Require.ProviderIs(StorageProvider.PostgreSql); } protected override void TestFixtureSetUp() { - // use one or enother - //EnableLegacyTimestampBehavior(); - // or - DisableLegacyTimestampBehavior(); - - Require.ProviderIs(StorageProvider.PostgreSql); - base.TestFixtureSetUp(); longTypeMapping = Driver.TypeMappings[typeof(long)]; @@ -41,7 +33,6 @@ protected override void TestFixtureSetUp() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteUtcValueLegacy() { CheckLegacyTurnedOn(); @@ -62,7 +53,6 @@ public void WriteUtcValueLegacy() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteLocalValueLegacy() { CheckLegacyTurnedOn(); @@ -86,7 +76,6 @@ public void WriteLocalValueLegacy() [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteUtcValue() { CheckLegacyTurnedOff(); @@ -107,7 +96,6 @@ public void WriteUtcValue() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteLocalValue() { CheckLegacyTurnedOff(); diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs index 626ce06a9..b5570bf8f 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs @@ -19,19 +19,11 @@ public sealed class LegacyVsCurrentDateTimeParameterBinding : SqlTest protected override void CheckRequirements() { - // do not check provider here. - // Require class uses driver creation which casues AppContext switch setup before TestFixtureSetup() method called + Require.ProviderIs(StorageProvider.PostgreSql); } protected override void TestFixtureSetUp() { - // use one or enother - EnableLegacyTimestampBehavior(); - // or - //DisableLegacyTimestampBehavior(); - - Require.ProviderIs(StorageProvider.PostgreSql); - base.TestFixtureSetUp(); longTypeMapping = Driver.TypeMappings[typeof(long)]; @@ -43,7 +35,6 @@ protected override void TestFixtureSetUp() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteUtcKindDateTimeValueLegacy() { CheckLegacyTurnedOn(); @@ -65,7 +56,6 @@ public void WriteUtcKindDateTimeValueLegacy() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteLocalKindDateTimeValueLegacy() { CheckLegacyTurnedOn(); @@ -89,7 +79,6 @@ public void WriteLocalKindDateTimeValueLegacy() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteUnspecifiedKindDateTimeValueLegacy() { CheckLegacyTurnedOn(); @@ -114,7 +103,6 @@ public void WriteUnspecifiedKindDateTimeValueLegacy() [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteUtcKindDateTimeValue() { CheckLegacyTurnedOff(); @@ -136,7 +124,6 @@ public void WriteUtcKindDateTimeValue() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteLocalKindDateTimeValue() { CheckLegacyTurnedOff(); @@ -158,7 +145,6 @@ public void WriteLocalKindDateTimeValue() } [Test] - [Explicit("Require manual set of AppContext switch")] public void WriteUnspecifiedKindDateTimeValue() { CheckLegacyTurnedOff(); From f0b9e1a479b36c367b74f6dacba4542e10f756ca Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 24 Feb 2025 16:16:28 +0500 Subject: [PATCH 23/48] PostgreSQL TypeMapper: assign real Min/Max values instead of server version Server-side version has reduced fractions which can cause problems on comparison with .NET values. --- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index 3776a085a..a1c86f446 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -18,6 +18,9 @@ namespace Xtensive.Sql.Drivers.PostgreSql.v8_0 { internal class TypeMapper : Sql.TypeMapper { + // 6 fractions instead of .NET's 7 + private const long DateTimeMaxValueAdjustedTicks = 3155378975999999990; + protected readonly bool legacyTimestampBehaviorEnabled; public override bool IsParameterCastRequired(Type type) @@ -236,11 +239,34 @@ public override object ReadDecimal(DbDataReader reader, int index) return nativeReader.GetDecimal(index); } + public override object ReadDateTime(DbDataReader reader, int index) + { + var value = reader.GetDateTime(index); + if (value.Ticks == 0) + return DateTime.MinValue; + if (value.Ticks == DateTimeMaxValueAdjustedTicks) { + // To not ruin possible comparisons with defined value, + // it is better to return definded value, + // not the 6-digit version from PostgreSQL + return DateTime.MaxValue; + } + return value; + } + [SecuritySafeCritical] public override object ReadDateTimeOffset(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; var value = nativeReader.GetFieldValue(index); + if (value.Ticks == DateTimeMaxValueAdjustedTicks) { + // To not ruin possible comparisons with defined values, + // it is better to return definded value, + // not the 6-fractions version from PostgreSQL + return DateTimeOffset.MaxValue; + } + if (value.Ticks == 0) + return DateTimeOffset.MinValue; + if (legacyTimestampBehaviorEnabled) { // Npgsql 4 or older behavior return value; @@ -256,8 +282,6 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) // 'SET TIME ZONE' statement :-) // // We'll use local time, that's it! SET TIME ZONE will not work! - if (value == DateTimeOffset.MinValue || value == DateTimeOffset.MaxValue) - return value; return value.ToLocalTime(); } } From 818075703c2475187992340f3e305ac44f45a8f8 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 24 Feb 2025 16:18:00 +0500 Subject: [PATCH 24/48] Remove unnecessary parameter SqlNodeCloneContext ctor --- Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs | 2 +- Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs | 2 +- Orm/Xtensive.Orm/Sql/SqlNode.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs index f7a174e5b..1c041a8ca 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs @@ -241,7 +241,7 @@ public static implicit operator SqlExpression(SqlSelect select) public sealed override bool Equals(object obj) => ReferenceEquals(this, obj); - public override SqlExpression Clone() => Clone(new SqlNodeCloneContext(false)); + public override SqlExpression Clone() => Clone(new SqlNodeCloneContext()); internal override abstract SqlExpression Clone(SqlNodeCloneContext context); diff --git a/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs b/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs index a67f025d1..e25a41371 100644 --- a/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs +++ b/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs @@ -28,7 +28,7 @@ public T GetOrAdd(T node, Func factory) where T : return result; } - public SqlNodeCloneContext(bool _) + public SqlNodeCloneContext() { nodeMapping = new(); } diff --git a/Orm/Xtensive.Orm/Sql/SqlNode.cs b/Orm/Xtensive.Orm/Sql/SqlNode.cs index 293f94fc9..2337c70e2 100644 --- a/Orm/Xtensive.Orm/Sql/SqlNode.cs +++ b/Orm/Xtensive.Orm/Sql/SqlNode.cs @@ -24,9 +24,9 @@ public abstract class SqlNode : ISqlNode /// /// A new object that is a copy of this instance. /// - public virtual SqlNode Clone() => Clone(new SqlNodeCloneContext(false)); + public virtual SqlNode Clone() => Clone(new SqlNodeCloneContext()); - object ICloneable.Clone() => Clone(new SqlNodeCloneContext(false)); + object ICloneable.Clone() => Clone(new SqlNodeCloneContext()); internal abstract SqlNode Clone(SqlNodeCloneContext context); From 4fba084840ed2ac9bded4c9365242f56b0199bce Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 24 Feb 2025 16:18:45 +0500 Subject: [PATCH 25/48] Improved InfinityAliasTests --- .../PostgreSql/InfinityAliasTest.cs | 788 +++++++++++++++--- 1 file changed, 668 insertions(+), 120 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs index 1b8dbf09d..adc9969e7 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs @@ -3,7 +3,10 @@ // See the License.txt file in the project root for more information. using System; +using System.Collections.Generic; using NUnit.Framework; +using Xtensive.Sql; +using Xtensive.Sql.Dml; namespace Xtensive.Orm.Tests.Sql.PostgreSql { @@ -18,10 +21,12 @@ public sealed class InfinityAliasTest : SqlTest private const string DateTimeOffsetMinValueTable = "DateTimeOffsetTable1"; private const string DateTimeOffsetMaxValueTable = "DateTimeOffsetTable2"; - private Xtensive.Sql.TypeMapping longTypeMapping; - private Xtensive.Sql.TypeMapping dateOnlyTypeMapping; - private Xtensive.Sql.TypeMapping dateTimeTypeMapping; - private Xtensive.Sql.TypeMapping dateTimeOffsetTypeMapping; + private readonly Dictionary templates = new(); + + private TypeMapping longTypeMapping; + private TypeMapping dateOnlyTypeMapping; + private TypeMapping dateTimeTypeMapping; + private TypeMapping dateTimeOffsetTypeMapping; protected override void CheckRequirements() { @@ -44,10 +49,32 @@ protected override void TestFixtureSetUp() CreateTablesForDateTimeTests(); CreateTablesForDateOnlyTests(); CreateTablesForDateTimeOffsetTests(); + + var schema = ExtractDefaultSchema(); + + templates.Add(DateOnlyMinValueTable, + SqlDml.Select(SqlDml.TableRef(schema.Tables[DateOnlyMinValueTable]))); + + templates.Add(DateOnlyMaxValueTable, + SqlDml.Select(SqlDml.TableRef(schema.Tables[DateOnlyMaxValueTable]))); + + + templates.Add(DateTimeMinValueTable, + SqlDml.Select(SqlDml.TableRef(schema.Tables[DateTimeMinValueTable]))); + + templates.Add(DateTimeMaxValueTable, + SqlDml.Select(SqlDml.TableRef(schema.Tables[DateTimeMaxValueTable]))); + + + templates.Add(DateTimeOffsetMinValueTable, + SqlDml.Select(SqlDml.TableRef(schema.Tables[DateTimeOffsetMinValueTable]))); + + templates.Add(DateTimeOffsetMaxValueTable, + SqlDml.Select(SqlDml.TableRef(schema.Tables[DateTimeOffsetMaxValueTable]))); } [Test] - public void MinDateTimeSelectNoFilterTest() + public void DateTimeMinSelectNoFilterTest() { var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeMinValueTable}\""); using (command) @@ -59,10 +86,25 @@ public void MinDateTimeSelectNoFilterTest() Assert.That(datetimeValue, Is.EqualTo(DateTime.MinValue)); } } + + var select = templates[DateTimeMinValueTable].Clone(new SqlNodeCloneContext()); + select.Columns.Add(select.From.Columns["Id"]); + select.Columns.Add(select.From.Columns["Value"]); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = longTypeMapping.ReadValue(reader, 0); + var datetimeValue = dateTimeTypeMapping.ReadValue(reader, 1); + Assert.That(datetimeValue, Is.EqualTo(DateTime.MinValue)); + } + } } [Test] - public void MinDateTimeSelectByEqualityTest() + public void DateTimeMinSelectByEqualityTest() { var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeMinValueTable}\" WHERE \"Value\" = $1"); var filterP = Connection.CreateParameter(); @@ -76,93 +118,171 @@ public void MinDateTimeSelectByEqualityTest() } [Test] - public void MinDateTimeSelectDatePartInfinityTest() + public void DateTimeMinSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); - TestMinDateTimeSelectDatePart(); + TestMinDateTimeSelectDatePart(true); } [Test] - public void MinDateTimeSelectDatePartDateTest() + public void DateTimeMinSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); - TestMinDateTimeSelectDatePart(); + TestMinDateTimeSelectDatePart(false); } - private void TestMinDateTimeSelectDatePart() + private void TestMinDateTimeSelectDatePart(bool isOn) { - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + var template = templates[DateTimeMinValueTable]; + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Year)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Year, isOn); } } + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Year, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); + using(command) + using(var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Year, isOn); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Month, isOn); + } + } + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Month, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Month)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Month, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Day, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Day, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Day)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Day, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Hour, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Hour, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Hour)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Hour, isOn); } } - command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Minute)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Minute, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Minute, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Minute, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Second, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Second, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.Not.EqualTo(DateTime.MinValue.Second)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MinValue.Second, isOn); } } } [Test] - public void MaxDateTimeSelectNoFilterTest() + public void DateTimeMaxSelectNoFilterTest() { var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeMaxValueTable}\""); using (command) @@ -175,10 +295,26 @@ public void MaxDateTimeSelectNoFilterTest() Assert.That(difference, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(0.001))); } } + + var select = templates[DateTimeMaxValueTable].Clone(new SqlNodeCloneContext()); + select.Columns.Add(select.From.Columns["Id"]); + select.Columns.Add(select.From.Columns["Value"]); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = longTypeMapping.ReadValue(reader, 0); + var datetimeValue = (DateTime) dateTimeTypeMapping.ReadValue(reader, 1); + var difference = (datetimeValue - DateTime.MaxValue).Duration(); + Assert.That(difference, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(0.001))); + } + } } [Test] - public void MaxDateTimeSelectByEqualityTest() + public void DateTimeMaxSelectByEqualityTest() { var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeMaxValueTable}\" WHERE \"Value\" = $1"); var filterP = Connection.CreateParameter(); @@ -192,93 +328,170 @@ public void MaxDateTimeSelectByEqualityTest() } [Test] - public void MaxDateTimeSelectDatePartInfinityTest() + public void DateTimeMaxSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); - TestMaxDateTimeSelectDatePart(); + TestMaxDateTimeSelectDatePart(true); } [Test] - public void MaxDateTimeSelectDatePartDateTest() + public void DateTimeMaxSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); - TestMaxDateTimeSelectDatePart(); + TestMaxDateTimeSelectDatePart(false); } - private void TestMaxDateTimeSelectDatePart() + private void TestMaxDateTimeSelectDatePart(bool isOn) { + var template = templates[DateTimeMaxValueTable]; + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Year, isOn); + } + } + + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Year, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Year)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Year, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Month, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Month, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Month)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Month, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Day, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Day, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Day)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Day, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Hour, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Hour, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Hour)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Hour, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Minute, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Minute, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Minute)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Minute, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Second, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Second, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateTime.MaxValue.Second)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTime.MaxValue.Second, isOn); } } } [Test] - public void MinDateOnlyNoFilterTest() + public void DateOnlyMinNoFilterTest() { var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateOnlyMinValueTable}\""); using (command) @@ -290,10 +503,25 @@ public void MinDateOnlyNoFilterTest() Assert.That(datetimeValue, Is.EqualTo(DateOnly.MinValue)); } } + + var select = templates[DateOnlyMinValueTable].Clone(new SqlNodeCloneContext()); + select.Columns.Add(select.From.Columns["Id"]); + select.Columns.Add(select.From.Columns["Value"]); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = longTypeMapping.ReadValue(reader, 0); + var datetimeValue = dateOnlyTypeMapping.ReadValue(reader, 1); + Assert.That(datetimeValue, Is.EqualTo(DateOnly.MinValue)); + } + } } [Test] - public void MinDateOnlyByEqualityTest() + public void DateOnlyMinByEqualityTest() { var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateOnlyMinValueTable}\" WHERE \"Value\" = $1"); var filterP = Connection.CreateParameter(); @@ -307,60 +535,98 @@ public void MinDateOnlyByEqualityTest() } [Test] - public void MinDateOnlySelectDatePartInfinityTest() + public void DateOnlyMinSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); - TestMinDateOnlySelectDatePart(); + TestMinDateOnlySelectDatePart(true); } [Test] - public void MinDateOnlySelectDatePartDateTest() + public void DateOnlyMinSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); - TestMinDateOnlySelectDatePart(); + TestMinDateOnlySelectDatePart(false); } - private void TestMinDateOnlySelectDatePart() + private void TestMinDateOnlySelectDatePart(bool isOn) { + var template = templates[DateOnlyMinValueTable]; + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MinValue.Year, isOn); + } + } + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDatePart.Year, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateOnly.MinValue.Year)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MinValue.Year, isOn); } } - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateOnly.MinValue.Month)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MinValue.Month, isOn); } } - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDatePart.Month, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MinValue.Month, isOn); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateOnly.MinValue.Day)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MinValue.Day, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDatePart.Day, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MinValue.Day, isOn); } } } [Test] - public void MaxDateOnlyNoFilterTest() + public void DateOnlyMaxNoFilterTest() { var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateOnlyMaxValueTable}\""); using (command) @@ -372,10 +638,25 @@ public void MaxDateOnlyNoFilterTest() Assert.That(datetimeValue, Is.EqualTo(DateOnly.MaxValue)); } } + + var select = templates[DateTimeMaxValueTable].Clone(new SqlNodeCloneContext()); + select.Columns.Add(select.From.Columns["Id"]); + select.Columns.Add(select.From.Columns["Value"]); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = longTypeMapping.ReadValue(reader, 0); + var datetimeValue = dateOnlyTypeMapping.ReadValue(reader, 1); + Assert.That(datetimeValue, Is.EqualTo(DateOnly.MaxValue)); + } + } } [Test] - public void MaxDateOnlyByEqualityTest() + public void DateOnlyMaxByEqualityTest() { var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateOnlyMaxValueTable}\" WHERE \"Value\" = $1"); var filterP = Connection.CreateParameter(); @@ -389,60 +670,111 @@ public void MaxDateOnlyByEqualityTest() } [Test] - public void MaxDateOnlySelectDatePartInfinityTest() + public void DateOnlyMaxSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); - TestMaxDateOnlySelectDatePart(); + TestMaxDateOnlySelectDatePart(true); } [Test] - public void MaxDateOnlySelectDatePartDateTest() + public void DateOnlyMaxSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); - TestMaxDateOnlySelectDatePart(); + TestMaxDateOnlySelectDatePart(false); } - private void TestMaxDateOnlySelectDatePart() + private void TestMaxDateOnlySelectDatePart(bool isOn) { - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); + var template = templates[DateOnlyMaxValueTable]; + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateOnly.MaxValue.Year)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MaxValue.Year, isOn); } } + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDatePart.Year, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MaxValue.Year, isOn); + } + } + + command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MaxValue.Month, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDatePart.Month, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateOnly.MaxValue.Month)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MaxValue.Month, isOn); } } + command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MaxValue.Day, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDatePart.Day, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetDouble(0); - Assert.That(yearPart, Is.EqualTo(DateOnly.MaxValue.Day)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateOnly.MaxValue.Day, isOn); } } } + + + + + + + + + + + + [Test] - public void MinDateTimeOffsetSelectNoFilterTest() + public void DateTimeOffsetMinSelectNoFilterTest() { var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeOffsetMinValueTable}\""); using (command) @@ -450,19 +782,34 @@ public void MinDateTimeOffsetSelectNoFilterTest() while (reader.Read()) { var id = reader.GetInt64(0); - var datetimeValue = reader.GetDateTime(1); - Assert.That(datetimeValue, Is.EqualTo(DateTimeOffset.MinValue.DateTime)); + var dateTimeOffsetValue = (DateTimeOffset) reader.GetFieldValue(1); + Assert.That(dateTimeOffsetValue, Is.EqualTo(DateTimeOffset.MinValue)); + } + } + + var select = templates[DateTimeOffsetMinValueTable].Clone(new SqlNodeCloneContext()); + select.Columns.Add(select.From.Columns["Id"]); + select.Columns.Add(select.From.Columns["Value"]); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = longTypeMapping.ReadValue(reader, 0); + var dateTimeOffsetValue = dateTimeOffsetTypeMapping.ReadValue(reader, 1); + Assert.That(dateTimeOffsetValue, Is.EqualTo(DateTimeOffset.MinValue)); } } } [Test] - public void MinDateTimeOffsetSelectByEqualityTest() + public void DateTimeOffsetMinSelectByEqualityTest() { var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeOffsetMinValueTable}\" WHERE \"Value\" = $1"); var filterP = Connection.CreateParameter(); dateTimeOffsetTypeMapping.BindValue(filterP, DateTimeOffset.MinValue); - command.Parameters.Add(filterP); + _ = command.Parameters.Add(filterP); using (command) { var count = (long) command.ExecuteScalar(); @@ -471,93 +818,173 @@ public void MinDateTimeOffsetSelectByEqualityTest() } [Test] - public void MinDateTimeOffsetSelectDatePartInfinityTest() + public void DateTimeOffsetMinSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); - TestMinDateTimeOffsetSelectDatePart(); + TestMinDateTimeOffsetSelectDatePart(true); } [Test] - public void MinDateTimeOffsetSelectDatePartDateTest() + public void DateTimeOffsetMinSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); - TestMinDateTimeOffsetSelectDatePart(); + TestMinDateTimeOffsetSelectDatePart(false); } - private void TestMinDateTimeOffsetSelectDatePart() + private void TestMinDateTimeOffsetSelectDatePart(bool isOn) { + var template = templates[DateTimeOffsetMinValueTable]; + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTimeOffset.MinValue.Year, isOn); + } + } + + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Year, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Year)); + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTimeOffset.MinValue.Year, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTimeOffset.MinValue.Month, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Month, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Month)); + var partValue = reader.GetDouble(0); + CheckPart(partValue, DateTimeOffset.MinValue.Month, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, DateTimeOffset.MinValue.Day, isOn); + } + } + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Day, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Day)); + var partValue = reader.GetDouble(0); + CheckPart(partValue, DateTimeOffset.MinValue.Day, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + CheckPartNative(partValue, 4, isOn); + } + } + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Hour, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Hour)); + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + CheckPart(partValue, (isOn) ? DateTimeOffset.MinValue.Hour : 4, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + CheckPartNative(partValue, 2, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Minute, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Minute)); + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + CheckPart(partValue, (isOn) ? DateTimeOffset.MinValue.Minute : 2, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + CheckPartNative(partValue, 33, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Second, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MinValue.Second)); + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + CheckPart(partValue, (isOn) ? DateTimeOffset.MinValue.Second : 33, isOn); } } } [Test] - public void MaxDateTimeOffsetSelectNoFilterTest() + public void DateTimeOffsetMaxSelectNoFilterTest() { var command = Connection.CreateCommand($"SELECT \"Id\", \"Value\" FROM public.\"{DateTimeOffsetMaxValueTable}\""); using (command) @@ -565,20 +992,36 @@ public void MaxDateTimeOffsetSelectNoFilterTest() while (reader.Read()) { var id = reader.GetInt64(0); - var datetimeValue = reader.GetDateTime(1); - var difference = (datetimeValue - DateTimeOffset.MaxValue).Duration(); + var dateTimeOffsetValue = (DateTimeOffset) reader.GetFieldValue(1); + var difference = (dateTimeOffsetValue - DateTimeOffset.MaxValue).Duration(); + Assert.That(difference, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(0.001))); + } + } + + var select = templates[DateTimeOffsetMaxValueTable].Clone(new SqlNodeCloneContext()); + select.Columns.Add(select.From.Columns["Id"]); + select.Columns.Add(select.From.Columns["Value"]); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var id = longTypeMapping.ReadValue(reader, 0); + var dateTimeOffsetValue = (DateTimeOffset) dateTimeOffsetTypeMapping.ReadValue(reader, 1); + var difference = (dateTimeOffsetValue - DateTimeOffset.MaxValue).Duration(); Assert.That(difference, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(0.001))); } } } [Test] - public void MaxDateTimeOffsetSelectByEqualityTest() + public void DateTimeOffsetMaxSelectByEqualityTest() { var command = Connection.CreateCommand($"SELECT Count(*) FROM public.\"{DateTimeOffsetMaxValueTable}\" WHERE \"Value\" = $1"); var filterP = Connection.CreateParameter(); dateTimeOffsetTypeMapping.BindValue(filterP, DateTimeOffset.MaxValue); - command.Parameters.Add(filterP); + _ = command.Parameters.Add(filterP); using (command) { var count = (long) command.ExecuteScalar(); @@ -587,92 +1030,197 @@ public void MaxDateTimeOffsetSelectByEqualityTest() } [Test] - [Explicit("Require manual set of AppContext switch")] - public void MaxDateTimeOffsetSelectDatePartInfinityTest() + public void DateTimeOffsetMaxSelectDatePartInfinityTest() { CheckIfInfinityAliasTurnedOn(); - TestMaxDateTimeOffsetSelectDatePart(); + TestMaxDateTimeOffsetSelectDatePart(true); } [Test] - [Explicit("Require manual set of AppContext switch")] - public void MaxDateTimeOffsetSelectDatePartDateTest() + public void DateTimeOffsetMaxSelectDatePartDateTest() { CheckIfInfinityAliasTurnedOff(); - TestMaxDateTimeOffsetSelectDatePart(); + TestMaxDateTimeOffsetSelectDatePart(false); } - private void TestMaxDateTimeOffsetSelectDatePart() + private void TestMaxDateTimeOffsetSelectDatePart(bool isOn) { + var template = templates[DateTimeOffsetMaxValueTable]; + var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // There is overflow of year because of PostgreSQL time zone functionality + CheckPartNative(partValue, DateTimeOffset.MaxValue.Year + 1, isOn); + } + } + + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Year, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Year)); + var partValue = reader.GetDouble(0); + // There is overflow of year because of PostgreSQL time zone functionality + CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Year : DateTimeOffset.MaxValue.Year + 1, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // there is value overflow to 01 + CheckPartNative(partValue, DateTimeOffset.MinValue.Month, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Month, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Month)); + var partValue = reader.GetDouble(0); + // there is value overflow to 01 in case of no aliases + CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Month : DateTimeOffset.MinValue.Month, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // there is value overflow to 01 + CheckPartNative(partValue, DateTime.MinValue.Day, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Day, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Day)); + var partValue = reader.GetDouble(0); + // there is value overflow to 01 in case of no aliases + CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Day : DateTimeOffset.MinValue.Day, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); - using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Hour)); + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + CheckPartNative(partValue, 4, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Hour, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Hour : 4, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + CheckPartNative(partValue, 59, isOn); + } + } + + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Minute, select.From.Columns["Value"])); + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Minute)); + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + CheckPart(partValue, DateTimeOffset.MaxValue.Minute, isOn); } } command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + CheckPartNative(partValue, DateTimeOffset.MaxValue.Second, isOn); + } + } + select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Second, select.From.Columns["Value"])); + + command = Connection.CreateCommand(select); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var yearPart = reader.GetInt64(0); - Assert.That(yearPart, Is.EqualTo(DateTimeOffset.MaxValue.Second)); + var partValue = reader.GetDouble(0); + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + CheckPart(partValue, DateTimeOffset.MaxValue.Second, isOn); } } } + + private void CheckPartNative(double partValue, int refPartValue, bool aliasesOn) + { + if (aliasesOn) { + Assert.That(partValue, Is.EqualTo(double.PositiveInfinity).Or.EqualTo(double.NegativeInfinity)); + } + else { + Assert.That((int) partValue, Is.EqualTo(refPartValue)); + } + } + + private void CheckPart(double partValue, int refPartValue, bool aliasesOn) + { + if (aliasesOn) { + Assert.That(partValue, Is.Not.EqualTo(double.PositiveInfinity).And.Not.EqualTo(double.NegativeInfinity)); + Assert.That(partValue, Is.EqualTo(refPartValue)); + } + else { + Assert.That((int) partValue, Is.EqualTo(refPartValue)); + } + } + #region Create structure and populate data private void CreateTablesForDateTimeTests() { From e88b982f693ead1cc74a53040a01f9699c982847 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 24 Feb 2025 16:26:40 +0500 Subject: [PATCH 26/48] Linq Dates operations tests improved - Some tests became Postgre-only - removed temporary logging of commands --- .../DateTimeAndDateTimeOffset/BaseTest.cs | 17 -- .../DateOnly/DateOnlyToStringTest.cs | 3 +- .../DateOnly/OperationsTest.cs | 5 +- .../DateOnly/PartsExtractionTest.cs | 8 +- .../DateTime/DateTimeToIsoTest.cs | 3 +- .../DateTime/OperationsTest.cs | 24 +-- .../DateTime/PartsExtractionTest.cs | 14 +- .../DateTimeOffset/OperationsTest.cs | 17 +- .../DateTimeOffset/PartsExtractionTest.cs | 147 +++++++++++++++++- .../DateTimeOffset/WhereTest.cs | 18 --- 10 files changed, 202 insertions(+), 54 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs index 15e3beb19..dbb8671b4 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/BaseTest.cs @@ -75,9 +75,7 @@ protected void RunTest(Expression> filter, int rightCount = 1) protected void RunTest(Session session, Expression> filter, int rightCount = 1) where T : Entity { - session.Events.DbCommandExecuting += Events_DbCommandExecuting; var count = session.Query.All().Count(filter); - session.Events.DbCommandExecuting -= Events_DbCommandExecuting; Assert.AreEqual(rightCount, count); } @@ -92,20 +90,5 @@ protected void RunWrongTest(Session session, Expression> filter { RunTest(session, filter, 0); } - - private void Events_DbCommandExecuting(object sender, DbCommandEventArgs e) - { - var command = e.Command; - var commandText = command.CommandText; - Console.WriteLine("No Modifications SQL Text:"); - Console.WriteLine(commandText); - var parameters = command.Parameters; - - Console.Write(" Parameters: "); - for (int i = 0, count = parameters.Count; i < count; i++) { - var parameter = parameters[i]; - Console.WriteLine($"{parameter.ParameterName} = {parameter.Value}"); - } - } } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs index 6fb699db2..1afe0fa97 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Xtensive LLC. +// Copyright (C) 2023-2025 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -22,6 +22,7 @@ public void ToStringTest() [Test] public void MinMaxToStringTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.ToString("o") == DateOnly.MinValue.ToString("o")); RunTest(s, c => c.MaxValue.ToString("o") == DateOnly.MaxValue.ToString("o")); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs index cbc71efea..72c3bf12b 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/OperationsTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Xtensive LLC. +// Copyright (C) 2023-2025 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -25,6 +25,7 @@ public void AddYearsTest() [Test] public void AddYearsToMinMaxValuesTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddYears(1) == DateOnly.MinValue.AddYears(1)); RunTest(s, c => c.MaxValue.AddYears(-33) == DateOnly.MaxValue.AddYears(-33)); @@ -49,6 +50,7 @@ public void AddMonthsTest() [Test] public void AddMonthsToMinMaxValues() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMonths(1) == DateOnly.MinValue.AddMonths(1)); RunTest(s, c => c.MaxValue.AddMonths(-33) == DateOnly.MaxValue.AddMonths(-33)); @@ -73,6 +75,7 @@ public void AddDaysTest() [Test] public void AddDaysToMinMaxValues() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddDays(1) == DateOnly.MinValue.AddDays(1)); RunTest(s, c => c.MaxValue.AddDays(-33) == DateOnly.MaxValue.AddDays(-33)); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs index 584acf15f..5a84218b6 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/PartsExtractionTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Xtensive LLC. +// Copyright (C) 2023-2025 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -26,6 +26,7 @@ public void ExtractYearTest() [Test] public void MinMaxExtractYearTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Year == DateTime.MinValue.Year); RunTest(s, c => c.MaxValue.Year == DateTime.MaxValue.Year); @@ -57,6 +58,7 @@ public void ExtractMonthTest() [Test] public void MinMaxExtractMonthTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Month == DateTime.MinValue.Month); RunTest(s, c => c.MaxValue.Month == DateTime.MaxValue.Month); @@ -81,6 +83,7 @@ public void ExtractDayTest() [Test] public void MinMaxExtractDayTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Day == DateTime.MinValue.Day); RunTest(s, c => c.MaxValue.Day == DateTime.MaxValue.Day); @@ -105,6 +108,7 @@ public void ExtractDayOfYearTest() [Test] public void MinMaxExtractDayOfYearTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.DayOfYear == DateTime.MinValue.DayOfYear); RunTest(s, c => c.MaxValue.DayOfYear == DateTime.MaxValue.DayOfYear); @@ -117,6 +121,7 @@ public void MinMaxExtractDayOfYearTest() [Test] public void ExtractDayOfWeekTest() { + ExecuteInsideSession((s) => { RunTest(s, c => c.DateOnly.DayOfWeek == FirstDateOnly.DayOfWeek); RunTest(s, c => c.NullableDateOnly.Value.DayOfWeek == NullableDateOnly.DayOfWeek); @@ -129,6 +134,7 @@ public void ExtractDayOfWeekTest() [Test] public void MinMaxExtractDayOfWeekTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.DayOfWeek == DateTime.MinValue.DayOfWeek); RunTest(s, c => c.MaxValue.DayOfWeek == DateTime.MaxValue.DayOfWeek); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs index 86f966301..8367f3982 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2023 Xtensive LLC. +// Copyright (C) 2016-2025 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 @@ -25,6 +25,7 @@ public void ToIsoStringTest() [Test] public void MinMaxValuesToIsoStringTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.ToString("s") == DateTime.MinValue.ToString("s")); RunTest(s, c => c.MaxValue.ToString("s") == DateTime.MaxValue.ToString("s")); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs index 48fb3cb40..660b6c6f6 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2021 Xtensive LLC. +// Copyright (C) 2016-2025 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 @@ -29,7 +29,7 @@ public void AddYearsTest() [Test] public void MinMaxValueAddYearsTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddYears(5) == DateTime.MinValue.AddYears(5)); RunTest(s, c => c.MaxValue.AddYears(-5) == DateTime.MaxValue.AddYears(-5)); @@ -56,7 +56,7 @@ public void AddMonthsTest() [Test] public void MinMaxValueAddMonthsTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMonths(5) == DateTime.MinValue.AddMonths(5)); RunTest(s, c => c.MaxValue.AddMonths(-5) == DateTime.MaxValue.AddMonths(-5)); @@ -83,7 +83,7 @@ public void AddDaysTest() [Test] public void MinMaxValueAddDaysTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddDays(5) == DateTime.MinValue.AddDays(5)); RunTest(s, c => c.MaxValue.AddDays(-5) == DateTime.MaxValue.AddDays(-5)); @@ -110,7 +110,7 @@ public void AddHoursTest() [Test] public void MinMaxValueAddHoursTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddHours(5) == DateTime.MinValue.AddHours(5)); RunTest(s, c => c.MaxValue.AddHours(-5) == DateTime.MaxValue.AddHours(-5)); @@ -137,7 +137,7 @@ public void AddMinutesTest() [Test] public void MinMaxValueAddMinutesTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMinutes(5) == DateTime.MinValue.AddMinutes(5)); RunTest(s, c => c.MaxValue.AddMinutes(-5) == DateTime.MaxValue.AddMinutes(-5)); @@ -164,7 +164,7 @@ public void AddSecondsTest() [Test] public void MinMaxValueAddSecondsTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddSeconds(5) == DateTime.MinValue.AddSeconds(5)); RunTest(s, c => c.MaxValue.AddSeconds(-5) == DateTime.MaxValue.AddSeconds(-5)); @@ -187,7 +187,7 @@ public void AddMillisecondsTest() [Test] public void MinMaxValueAddMillisecondsTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMilliseconds(5) == DateTime.MinValue.AddMilliseconds(5)); RunTest(s, c => c.MaxValue.AddMilliseconds(-5) == DateTime.MaxValue.AddMilliseconds(-5)); @@ -214,6 +214,7 @@ public void AddTimeSpanTest() [Test] public void MinValueAddTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Add(FirstOffset) == DateTime.MinValue.Add(FirstOffset)); RunWrongTest(s, c => c.MinValue.Add(FirstOffset) == DateTime.MinValue.Add(WrongOffset)); @@ -237,6 +238,7 @@ public void SubtractTimeSpanTest() [Test] public void MaxValueSubtractTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue.Subtract(FirstOffset) == DateTime.MaxValue.Subtract(FirstOffset)); RunWrongTest(s, c => c.MaxValue.Subtract(FirstOffset) == DateTime.MaxValue.Subtract(WrongOffset)); @@ -261,7 +263,7 @@ public void SubtractDateTimeTest() [Test] public void MaxValueSubtractDateTimeTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue.Subtract(SecondDateTime) == DateTime.MaxValue.Subtract(SecondDateTime)); RunWrongTest(s, c => c.MaxValue.Subtract(SecondDateTime) == DateTime.MaxValue.Subtract(WrongDateTime)); @@ -285,6 +287,7 @@ public void PlusTimeSpanTest() [Test] public void MinValuePlusTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue + FirstOffset == DateTime.MinValue + FirstOffset); RunWrongTest(s, c => c.MinValue + FirstOffset == DateTime.MinValue + WrongOffset); @@ -308,6 +311,7 @@ public void MinusTimeSpanTest() [Test] public void MaxValueMinusTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue - FirstOffset == DateTime.MaxValue - FirstOffset); RunWrongTest(s, c => c.MaxValue - FirstOffset == DateTime.MaxValue - WrongOffset); @@ -332,7 +336,7 @@ public void MinusDateTimeTest() [Test] public void MaxValueMinusDateTimeTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue - SecondDateTime == DateTime.MaxValue - SecondDateTime); RunWrongTest(s, c => c.MaxValue - SecondDateTime == DateTime.MaxValue - WrongDateTime); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs index 4f221c786..b203cb14f 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-2023 Xtensive LLC. +// Copyright (C) 2016-2025 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 @@ -29,6 +29,7 @@ public void ExtractYearTest() [Test] public void MinMaxValueExtractYearTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Year == DateTime.MinValue.Year); RunTest(s, c => c.MaxValue.Year == DateTime.MaxValue.Year); @@ -55,6 +56,7 @@ public void ExtractMonthTest() [Test] public void MinMaxValueExtractMonthTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Month == DateTime.MinValue.Month); RunTest(s, c => c.MaxValue.Month == DateTime.MaxValue.Month); @@ -81,6 +83,7 @@ public void ExtractDayTest() [Test] public void MinMaxValueExtractDayTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Day == DateTime.MinValue.Day); RunTest(s, c => c.MaxValue.Day == DateTime.MaxValue.Day); @@ -107,6 +110,7 @@ public void ExtractHourTest() [Test] public void MinMaxValueExtractHourTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Hour == DateTime.MinValue.Hour); RunTest(s, c => c.MaxValue.Hour == DateTime.MaxValue.Hour); @@ -134,6 +138,7 @@ public void ExtractMinuteTest() [Test] public void MinMaxValueExtractMinuteTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Minute == DateTime.MinValue.Minute); RunTest(s, c => c.MaxValue.Minute == DateTime.MaxValue.Minute); @@ -160,6 +165,7 @@ public void ExtractSecondTest() [Test] public void MinMaxValueExtractSecondTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Second == DateTime.MinValue.Second); RunTest(s, c => c.MaxValue.Second == DateTime.MaxValue.Second); @@ -182,7 +188,7 @@ public void ExtractMillisecondTest() [Test] public void MinMaxValueExtractMillisecondTest() { - Require.ProviderIsNot(StorageProvider.MySql); + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { var minAdjusted = DateTime.MinValue.AdjustDateTimeForCurrentProvider(); @@ -223,6 +229,7 @@ public void ExtractDateTest() [Test] public void MinMaxValueExtractDateTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Date == DateTime.MinValue.Date); RunTest(s, c => c.MaxValue.Date == DateTime.MaxValue.Date); @@ -265,6 +272,7 @@ public void ExtractTimeOfDayTest() [Test] public void MinMaxValueExtractTimeOfDayTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.TimeOfDay == DateTime.MinValue.TimeOfDay); RunTest(s, c => c.MaxValue.TimeOfDay == DateTime.MaxValue.TimeOfDay); @@ -291,6 +299,7 @@ public void ExtractDayOfYearTest() [Test] public void MinMaxValueExtractDayOfYearTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.DayOfYear == DateTime.MinValue.DayOfYear); RunTest(s, c => c.MaxValue.DayOfYear == DateTime.MaxValue.DayOfYear); @@ -317,6 +326,7 @@ public void ExtractDayOfWeekTest() [Test] public void MinMaxValueExtractDayOfWeekTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.DayOfWeek == DateTime.MinValue.DayOfWeek); RunTest(s, c => c.MaxValue.DayOfWeek == DateTime.MaxValue.DayOfWeek); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs index 6acaf6f55..652ce7622 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2020 Xtensive LLC. +// Copyright (C) 2016-2025 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 @@ -29,6 +29,7 @@ public void AddYearsTest() [Test] public void MinMaxValueAddYearsTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddYears(1) == DateTimeOffset.MinValue.AddYears(1)); RunTest(s, c => c.MaxValue.AddYears(-2) == DateTimeOffset.MaxValue.AddYears(-2)); @@ -55,6 +56,7 @@ public void AddMonthsTest() [Test] public void MinMaxValueAddMonthsTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMonths(1) == DateTimeOffset.MinValue.AddMonths(1)); RunTest(s, c => c.MaxValue.AddMonths(-2) == DateTimeOffset.MaxValue.AddMonths(-2)); @@ -81,6 +83,7 @@ public void AddDaysTest() [Test] public void MinMaxValueAddDaysTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddDays(1) == DateTimeOffset.MinValue.AddDays(1)); RunTest(s, c => c.MaxValue.AddDays(-2) == DateTimeOffset.MaxValue.AddDays(-2)); @@ -107,6 +110,7 @@ public void AddHoursTest() [Test] public void MinMaxValueAddHoursTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddHours(1) == DateTimeOffset.MinValue.AddHours(1)); RunTest(s, c => c.MaxValue.AddHours(-2) == DateTimeOffset.MaxValue.AddHours(-2)); @@ -133,6 +137,7 @@ public void AddMinutesTest() [Test] public void MinMaxValueAddMinutesTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMinutes(1) == DateTimeOffset.MinValue.AddMinutes(1)); RunTest(s, c => c.MaxValue.AddMinutes(-2) == DateTimeOffset.MaxValue.AddMinutes(-2)); @@ -159,6 +164,7 @@ public void AddSecondsTest() [Test] public void MinMaxValueAddSecondsTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddSeconds(1) == DateTimeOffset.MinValue.AddSeconds(1)); RunTest(s, c => c.MaxValue.AddSeconds(-2) == DateTimeOffset.MaxValue.AddSeconds(-2)); @@ -180,6 +186,7 @@ public void AddMillisecondsTest() [Test] public void MinMaxValueAddMillisecondsTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.AddMilliseconds(1) == DateTimeOffset.MinValue.AddMilliseconds(1)); RunTest(s, c => c.MaxValue.AddMilliseconds(-2) == DateTimeOffset.MaxValue.AddMilliseconds(-2)); @@ -206,6 +213,7 @@ public void AddTimeSpanTest() [Test] public void MinValueAddTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Add(FirstOffset) == DateTimeOffset.MinValue.Add(FirstOffset)); RunWrongTest(s, c => c.MinValue.Add(FirstOffset) == FirstDateTimeOffset.Add(WrongOffset)); @@ -229,6 +237,7 @@ public void SubtractTimeSpanTest() [Test] public void MaxValueSubtractTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue.Subtract(FirstOffset) == DateTimeOffset.MaxValue.Subtract(FirstOffset)); RunWrongTest(s, c => c.MaxValue.Subtract(FirstOffset) == FirstDateTimeOffset.Subtract(WrongOffset)); @@ -252,6 +261,7 @@ public void SubtractDateTimeTest() [Test] public void MaxValueSubtractDateTimeTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue.Subtract(SecondDateTime) == DateTimeOffset.MaxValue.Subtract(SecondDateTime)); RunWrongTest(s, c => c.MaxValue.Subtract(SecondDateTime) == FirstDateTimeOffset.Subtract(WrongDateTime)); @@ -275,6 +285,7 @@ public void SubstractDateTimeOffsetAndIntervalUsageTest() [Test] public void MaxValueSubstractDateTimeOffsetAndIntervalUsageTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue.Subtract(SecondDateTimeOffset) == DateTimeOffset.MaxValue.Subtract(SecondDateTimeOffset)); RunTest(s, c => c.MaxValue.Subtract(SecondDateTimeOffset).TotalMilliseconds == DateTimeOffset.MaxValue.Subtract(SecondDateTimeOffset).TotalMilliseconds); @@ -301,6 +312,7 @@ public void PlusTimeSpanTest() [Test] public void MinValuePlusTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue + FirstOffset == DateTimeOffset.MinValue + FirstOffset); @@ -325,6 +337,7 @@ public void MinusTimeSpanTest() [Test] public void MaxValueMinusTimeSpanTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue - FirstOffset == DateTimeOffset.MaxValue - FirstOffset); RunWrongTest(s, c => c.MaxValue - FirstOffset == DateTimeOffset.MaxValue - WrongOffset); @@ -348,6 +361,7 @@ public void MinusDateTimeTest() [Test] public void MaxValueMinusDateTimeTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue - SecondDateTime == DateTimeOffset.MaxValue - SecondDateTime); RunWrongTest(s, c => c.MaxValue - SecondDateTime == DateTimeOffset.MaxValue - WrongDateTime); @@ -371,6 +385,7 @@ public void MinusDateTimeOffsetAndIntervalUsageTest() [Test] public void MaxValueMinusDateTimeOffsetAndIntervalUsageTest() { + Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MaxValue - FirstDateTimeOffset == DateTimeOffset.MaxValue - FirstDateTimeOffset); RunTest(s, c => (c.MaxValue - FirstDateTimeOffset).TotalMilliseconds == (DateTimeOffset.MaxValue - FirstDateTimeOffset).TotalMilliseconds); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs index 2f31c0808..592c892b6 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2022 Xtensive LLC. +// Copyright (C) 2016-2025 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 @@ -25,7 +25,20 @@ public void ExtractYearTest() RunWrongTest(s, c => c.NullableDateTimeOffset.Value.Year==WrongDateTimeOffset.Year); }); } - + + [Test] + public void MinMaxValueExtractYearTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Year == DateTimeOffset.MinValue.Year); + RunTest(s, c => c.MaxValue.Year == DateTimeOffset.MaxValue.Year); + + RunWrongTest(s, c => c.MinValue.Year == DateTimeOffset.MinValue.AddYears(1).Year); + RunWrongTest(s, c => c.MaxValue.Year == DateTimeOffset.MaxValue.AddYears(-1).Year); + }); + } + [Test] public void ExtractMonthTest() { @@ -40,6 +53,19 @@ public void ExtractMonthTest() }); } + [Test] + public void MinMaxValueExtractMonthTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Month == DateTimeOffset.MinValue.Month); + RunTest(s, c => c.MaxValue.Month == DateTimeOffset.MaxValue.Month); + + RunWrongTest(s, c => c.MinValue.Month == DateTimeOffset.MinValue.AddMonths(1).Month); + RunWrongTest(s, c => c.MaxValue.Month == DateTimeOffset.MaxValue.AddMonths(-1).Month); + }); + } + [Test] public void ExtractDayTest() { @@ -60,6 +86,19 @@ public void ExtractDayTest() }); } + [Test] + public void MinMaxValueExtractDayTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Day == DateTimeOffset.MinValue.Day); + RunTest(s, c => c.MaxValue.Day == DateTimeOffset.MaxValue.Day); + + RunWrongTest(s, c => c.MinValue.Day == DateTimeOffset.MinValue.AddDays(1).Day); + RunWrongTest(s, c => c.MaxValue.Day == DateTimeOffset.MaxValue.AddDays(-1).Day); + }); + } + [Test] public void ExtractHourTest() { @@ -80,6 +119,19 @@ public void ExtractHourTest() }); } + [Test] + public void MinMaxValueExtractHourTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Hour == DateTimeOffset.MinValue.Hour); + RunTest(s, c => c.MaxValue.Hour == DateTimeOffset.MaxValue.Hour); + + RunWrongTest(s, c => c.MinValue.Hour == DateTimeOffset.MinValue.AddHours(1).Hour); + RunWrongTest(s, c => c.MaxValue.Hour == DateTimeOffset.MaxValue.AddHours(-1).Hour); + }); + } + [Test] public void ExtractMinuteTest() { @@ -99,6 +151,19 @@ public void ExtractMinuteTest() }); } + [Test] + public void MinMaxValueExtractMinuteTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Minute == DateTimeOffset.MinValue.Minute); + RunTest(s, c => c.MaxValue.Minute == DateTimeOffset.MaxValue.Minute); + + RunWrongTest(s, c => c.MinValue.Minute == DateTimeOffset.MinValue.AddMinutes(1).Minute); + RunWrongTest(s, c => c.MaxValue.Minute == DateTimeOffset.MaxValue.AddMinutes(-1).Minute); + }); + } + [Test] public void ExtractSecondTest() { @@ -118,6 +183,19 @@ public void ExtractSecondTest() }); } + [Test] + public void MinMaxValueExtractSecondTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Second == DateTimeOffset.MinValue.Second); + RunTest(s, c => c.MaxValue.Second == DateTimeOffset.MaxValue.Second); + + RunWrongTest(s, c => c.MinValue.Second == DateTimeOffset.MinValue.AddSeconds(10).Second); + RunWrongTest(s, c => c.MaxValue.Second == DateTimeOffset.MaxValue.AddSeconds(-10).Second); + }); + } + [Test] public void ExtractMillisecondTest() { @@ -149,6 +227,19 @@ public void ExtractDateTest() }); } + [Test] + public void MinMaxValueExtractDateTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Date == DateTimeOffset.MinValue.Date); + RunTest(s, c => c.MaxValue.Date == DateTimeOffset.MaxValue.Date); + + RunWrongTest(s, c => c.MinValue.Date == DateTimeOffset.MinValue.AddDays(1).Date); + RunWrongTest(s, c => c.MaxValue.Date == DateTimeOffset.MaxValue.AddDays(-1).Date); + }); + } + [Test] [TestCase("2018-10-30 12:15:32.123 +05:10")] [TestCase("2018-10-30 12:15:32.1234 +05:10")] @@ -177,6 +268,19 @@ public void ExtractTimeOfDayTest() }); } + [Test] + public void MinMaxValueExtractTimeOfDayTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.TimeOfDay == DateTimeOffset.MinValue.TimeOfDay); + RunTest(s, c => c.MaxValue.TimeOfDay == DateTimeOffset.MaxValue.TimeOfDay); + + RunWrongTest(s, c => c.MinValue.TimeOfDay == DateTimeOffset.MinValue.AddMinutes(10).TimeOfDay); + RunWrongTest(s, c => c.MaxValue.TimeOfDay == DateTimeOffset.MaxValue.AddMinutes(-10).TimeOfDay); + }); + } + [Test] public void ExtractTimeOfDayWithMillisecondsTest() { @@ -234,6 +338,19 @@ public void ExtractDayOfYearTest() }); } + [Test] + public void MinMaxValueExtractDayOfYearTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfYear == DateTimeOffset.MinValue.DayOfYear); + RunTest(s, c => c.MaxValue.DayOfYear == DateTimeOffset.MaxValue.DayOfYear); + + RunWrongTest(s, c => c.MinValue.DayOfYear == DateTimeOffset.MinValue.AddDays(1).DayOfYear); + RunWrongTest(s, c => c.MaxValue.DayOfYear == DateTimeOffset.MaxValue.AddDays(-1).DayOfYear); + }); + } + [Test] public void ExtractDayOfWeekTest() { @@ -253,6 +370,19 @@ public void ExtractDayOfWeekTest() }); } + [Test] + public void MinMaxValueExtractDayOfWeekTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfWeek == DateTimeOffset.MinValue.DayOfWeek); + RunTest(s, c => c.MaxValue.DayOfWeek == DateTimeOffset.MaxValue.DayOfWeek); + + RunWrongTest(s, c => c.MinValue.DayOfWeek == DateTimeOffset.MinValue.AddDays(1).DayOfWeek); + RunWrongTest(s, c => c.MaxValue.DayOfWeek == DateTimeOffset.MaxValue.AddDays(-1).DayOfWeek); + }); + } + [Test] public void ExtractDateTimeTest() { @@ -272,6 +402,19 @@ public void ExtractDateTimeTest() }); } + [Test] + public void MinMaxValueExtractDateTimeTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DateTime == DateTimeOffset.MinValue.DateTime); + RunTest(s, c => c.MaxValue.DateTime == DateTimeOffset.MaxValue.DateTime); + + RunWrongTest(s, c => c.MinValue.DateTime == DateTimeOffset.MinValue.AddDays(1).DateTime); + RunWrongTest(s, c => c.MaxValue.DateTime == DateTimeOffset.MaxValue.AddDays(-1).DateTime); + }); + } + [Test] public void ExtractLocalDateTimeTest() { diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs index 1f26abae0..200baabdb 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/WhereTest.cs @@ -187,7 +187,6 @@ private void WherePrivate(Expression> whereExpression, Expr var compiledOrderByExpression = orderByExpression.Compile(); var session = Session.Current; - session.Events.DbCommandExecuting += Events_DbCommandExecuting; var whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression).ToArray(); var whereByServer = session.Query.All().Where(whereExpression).OrderBy(orderByExpression).ToArray(); @@ -199,26 +198,9 @@ private void WherePrivate(Expression> whereExpression, Expr whereByServer = session.Query.All().Where(whereExpression).OrderByDescending(orderByExpression).ToArray(); whereLocal = session.Query.All().ToArray().Where(compiledWhereExpression).OrderBy(compiledOrderByExpression).ToArray(); - session.Events.DbCommandExecuting -= Events_DbCommandExecuting; - Assert.That(whereLocal.Length, Is.Not.EqualTo(0)); Assert.That(whereByServer.Length, Is.Not.EqualTo(0)); Assert.IsFalse(whereLocal.SequenceEqual(whereByServer)); } - - private void Events_DbCommandExecuting(object sender, DbCommandEventArgs e) - { - var command = e.Command; - var commandText = command.CommandText; - Console.WriteLine("No Modifications SQL Text:"); - Console.WriteLine(commandText); - var parameters = command.Parameters; - - Console.Write(" Parameters: "); - for (int i = 0, count = parameters.Count; i < count; i++) { - var parameter = parameters[i]; - Console.WriteLine($"{parameter.ParameterName} = {parameter.Value}"); - } - } } } \ No newline at end of file From 8fcc5348a45d6cbcd7ff1af9515597b3b651b368 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 27 Feb 2025 13:14:20 +0500 Subject: [PATCH 27/48] PostgreSql: Apply connection timezone to datetimeoffset values on reading --- .../Sql.Drivers.PostgreSql/DriverFactory.cs | 53 +++++++- .../PostgreServerInfo.cs | 13 ++ .../PostgreSqlHelper.cs | 71 +++++++++++ .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 74 ++++-------- .../PostgreSql/PostgreSqlHelperTest.cs | 114 ++++++++++++++++++ 5 files changed, 268 insertions(+), 57 deletions(-) create mode 100644 Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs create mode 100644 Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs index 5bebae42e..e4f4932db 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs @@ -13,6 +13,7 @@ using Xtensive.Orm; using Xtensive.Sql.Info; using Xtensive.Sql.Drivers.PostgreSql.Resources; +using System.Collections.Generic; namespace Xtensive.Sql.Drivers.PostgreSql { @@ -66,8 +67,9 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf else OpenConnectionFast(connection, configuration, false).GetAwaiter().GetResult(); var version = GetVersion(configuration, connection); + var serverTimezones = GetServerTimeZones(connection, false).GetAwaiter().GetResult(); var defaultSchema = GetDefaultSchema(connection); - return CreateDriverInstance(connectionString, version, defaultSchema); + return CreateDriverInstance(connectionString, version, defaultSchema, serverTimezones, connection.Timezone); } /// @@ -85,8 +87,9 @@ protected override async Task CreateDriverAsync( else await OpenConnectionFast(connection, configuration, true, token).ConfigureAwait(false); var version = GetVersion(configuration, connection); + var serverTimezones = await GetServerTimeZones(connection, true, token).ConfigureAwait(false); var defaultSchema = await GetDefaultSchemaAsync(connection, token: token).ConfigureAwait(false); - return CreateDriverInstance(connectionString, version, defaultSchema); + return CreateDriverInstance(connectionString, version, defaultSchema, serverTimezones, connection.Timezone); } } @@ -99,7 +102,8 @@ private static Version GetVersion(SqlDriverConfiguration configuration, NpgsqlCo } private static SqlDriver CreateDriverInstance( - string connectionString, Version version, DefaultSchemaInfo defaultSchema) + string connectionString, Version version, DefaultSchemaInfo defaultSchema, + Dictionary timezones, string defaultTimeZone) { var coreServerInfo = new CoreServerInfo { ServerVersion = version, @@ -111,7 +115,9 @@ private static SqlDriver CreateDriverInstance( var pgsqlServerInfo = new PostgreServerInfo() { InfinityAliasForDatesEnabled = InfinityAliasForDatesEnabled, - LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled + LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled, + ServerTimeZones = timezones, + DefaultTimeZone = defaultTimeZone }; if (version.Major < 8 || (version.Major == 8 && version.Minor < 3)) { @@ -198,6 +204,45 @@ await SqlHelper.NotifyConnectionInitializingAsync(accessors, } } + private static async ValueTask> GetServerTimeZones(NpgsqlConnection connection, bool isAsync, CancellationToken token = default) + { + var resultZones = new Dictionary(); + + var command = connection.CreateCommand(); + command.CommandText = "SELECT \"name\", \"abbrev\", \"utc_offset\", \"is_dst\" FROM pg_timezone_names"; + if (isAsync) { + await using(command) + await using(var reader = await command.ExecuteReaderAsync()) { + while(await reader.ReadAsync()) { + ReadTimezoneRow(reader, resultZones); + } + } + } + else { + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + ReadTimezoneRow(reader, resultZones); + } + } + } + return resultZones; + + + static void ReadTimezoneRow(NpgsqlDataReader reader, Dictionary zones) + { + var name = reader.GetString(0); + var abbrev = reader.GetString(1); + var utcOffset = PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(reader.GetFieldValue(2)); + + _ = zones.TryAdd(name, utcOffset); + //flatten results for search convinience + if (!string.IsNullOrEmpty(abbrev)) { + _ = zones.TryAdd(abbrev, utcOffset); + } + } + } + private static bool SetOrGetExistingDisableInfinityAliasForDatesSwitch(bool valueToSet) => GetSwitchValueOrSet(Orm.PostgreSql.WellKnown.DateTimeToInfinityConversionSwitchName, valueToSet); diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs index e01709876..2e4cbc71f 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs @@ -2,6 +2,9 @@ // 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; + namespace Xtensive.Sql.Drivers.PostgreSql { /// @@ -22,5 +25,15 @@ internal sealed class PostgreServerInfo /// The setting has effect on parameter binding and also value reading from DbDataReader. /// public bool LegacyTimestampBehavior { get; init; } + + /// + /// Contains server timezone names and their base Utc offset (including abbreviations). + /// + public IReadOnlyDictionary ServerTimeZones { get; init; } + + /// + /// Gets time zone of connection after connection initialization script was executed. + /// + public string DefaultTimeZone { get; init; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs new file mode 100644 index 000000000..9deb1d534 --- /dev/null +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2025 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 Npgsql; +using NpgsqlTypes; +using Xtensive.Orm.PostgreSql; + +namespace Xtensive.Sql.Drivers.PostgreSql +{ + internal static class PostgreSqlHelper + { + internal static NpgsqlInterval CreateNativeIntervalFromTimeSpan(in TimeSpan timeSpan) + { + // Previous Npgsql versions used days and time, no months. + // Thought we can write everything as time, we keep days and time format + + var ticks = timeSpan.Ticks; + + var days = timeSpan.Days; + var timeTicks = ticks - (days * TimeSpan.TicksPerDay); +#if NET7_0_OR_GREATER + var microseconds = timeTicks / TimeSpan.TicksPerMicrosecond; +#else + var microseconds = timeTicks / 10L; // same as TimeSpan.TicksPerMicrosecond available in .NET7+ +#endif + // no months! + return new NpgsqlInterval(0, days, microseconds); + } + + internal static TimeSpan ResurrectTimeSpanFromNpgsqlInterval(in NpgsqlInterval npgsqlInterval) + { + // We don't write "Months" part of NpgsqlInterval to database + // because days in months is variable measure in PostgreSQL. + // We better use exact number of days. + // But if for some reason, there is Months value > 0 we treat it like each month has 30 days, + // it seems that Npgsql did the same assumption internally. + + var days = (npgsqlInterval.Months != 0) + ? npgsqlInterval.Months * WellKnown.IntervalDaysInMonth + npgsqlInterval.Days + : npgsqlInterval.Days; + + var ticksOfDays = days * TimeSpan.TicksPerDay; +#if NET7_0_OR_GREATER + var overallTicks = ticksOfDays + (npgsqlInterval.Time * TimeSpan.TicksPerMicrosecond); +#else + var overallTicks = ticksOfDays + (npgsqlInterval.Time * 10); //same as TimeSpan.TicksPerMicrosecond available in .NET7+ +#endif + return TimeSpan.FromTicks(overallTicks); + } + + /// + /// Checks if timezone is declared in POSIX format (example <+07>-07 ) + /// and returns number between '<' and '>' as timezone. + /// + /// Timezone in possible POSIX format + /// Timezone shift declared in oritinal POSIX format as timezone or original value. + internal static string TryGetZoneFromPosix(string timezone) + { + if (timezone.StartsWith('<')) { + // if POSIX format + var closing = timezone.IndexOf('>'); + var result = timezone.Substring(1, closing - 1); + return result; + } + return timezone; + } + } +} diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index a1c86f446..259f53266 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -5,6 +5,7 @@ // Created: 2009.06.23 using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Security; @@ -22,6 +23,7 @@ internal class TypeMapper : Sql.TypeMapper private const long DateTimeMaxValueAdjustedTicks = 3155378975999999990; protected readonly bool legacyTimestampBehaviorEnabled; + protected readonly TimeSpan? defaultTimeZone; public override bool IsParameterCastRequired(Type type) { @@ -122,7 +124,7 @@ public override void BindTimeSpan(DbParameter parameter, object value) nativeParameter.NpgsqlValue = value is null ? DBNull.Value : value is TimeSpan timeSpanValue - ? (object) CreateNativeIntervalFromTimeSpan(timeSpanValue) + ? (object) PostgreSqlHelper.CreateNativeIntervalFromTimeSpan(timeSpanValue) : throw ValueNotOfTypeError(nameof(WellKnownTypes.TimeSpanType)); } @@ -229,7 +231,7 @@ public override object ReadTimeSpan(DbDataReader reader, int index) // support for full-range of Timespans required us to use raw type // and construct timespan from its' values. - return ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); + return PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); } [SecuritySafeCritical] @@ -272,59 +274,19 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) return value; } else { - // Probably the "mastermind" who made parameter conversion before setting value to parameter be required - // also forgot about PostgreSQL's built-in "SET TIME ZONE" feature for session, which affects values of TimeStampTz - // Now applications have to use either local/utc timezone everywhere OR somehow "remember" what they've set in SET TIME ZONE - // for being able to get values in the timezone they've set. (facapalm) - // - // BTW, Npgsql has no API that would provide us current connection timezone so we could apply it to values, - // there is internal setting in NpgsqlConnection but it is null no matter what is set by - // 'SET TIME ZONE' statement :-) - // - // We'll use local time, that's it! SET TIME ZONE will not work! - return value.ToLocalTime(); + // Here we try to apply connection timezone. + // To not get it from internal connection of DbDataReader + // we assume that time zone switch happens (if happens) + // in DomainConfiguration.ConnectionInitializationSql and + // we cache time zone of native connection after the script + // has been executed. + + return (defaultTimeZone.HasValue) + ? value.ToOffset(defaultTimeZone.Value) + : value.ToLocalTime(); } } - protected internal static NpgsqlInterval CreateNativeIntervalFromTimeSpan(in TimeSpan timeSpan) - { - // Previous Npgsql versions used days and time, no months. - // Thought we can write everything as time, we keep days and time format - - var ticks = timeSpan.Ticks; - - var days = timeSpan.Days; - var timeTicks = ticks - (days * TimeSpan.TicksPerDay); -#if NET7_0_OR_GREATER - var microseconds = timeTicks / TimeSpan.TicksPerMicrosecond; -#else - var microseconds = timeTicks / 10L; // same as TimeSpan.TicksPerMicrosecond available in .NET7+ -#endif - // no months! - return new NpgsqlInterval(0, days, microseconds); - } - - protected internal static TimeSpan ResurrectTimeSpanFromNpgsqlInterval(in NpgsqlInterval npgsqlInterval) - { - // We don't write "Months" part of NpgsqlInterval to database - // because days in months is variable measure in PostgreSQL. - // We better use exact number of days. - // But if for some reason, there is Months value > 0 we treat it like each month has 30 days, - // it seems that Npgsql did the same assumption internally. - - var days = (npgsqlInterval.Months != 0) - ? npgsqlInterval.Months * WellKnown.IntervalDaysInMonth + npgsqlInterval.Days - : npgsqlInterval.Days; - - var ticksOfDays = days * TimeSpan.TicksPerDay; -#if NET7_0_OR_GREATER - var overallTicks = ticksOfDays + (npgsqlInterval.Time * TimeSpan.TicksPerMicrosecond); -#else - var overallTicks = ticksOfDays + (npgsqlInterval.Time * 10); //same as TimeSpan.TicksPerMicrosecond available in .NET7+ -#endif - return TimeSpan.FromTicks(overallTicks); - } - internal protected ArgumentException ValueNotOfTypeError(string typeName) { return new ArgumentException($"Value is not of '{typeName}' type."); @@ -335,7 +297,13 @@ internal protected ArgumentException ValueNotOfTypeError(string typeName) public TypeMapper(PostgreSql.Driver driver) : base(driver) { - legacyTimestampBehaviorEnabled = driver.PostgreServerInfo.LegacyTimestampBehavior; + var postgreServerInfo = driver.PostgreServerInfo; + legacyTimestampBehaviorEnabled = postgreServerInfo.LegacyTimestampBehavior; + defaultTimeZone = postgreServerInfo.ServerTimeZones.TryGetValue( + PostgreSqlHelper.TryGetZoneFromPosix(postgreServerInfo.DefaultTimeZone), out var offset) + ? offset + : null; + ; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs new file mode 100644 index 000000000..0398a68b4 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Npgsql; +using NUnit.Framework; +using Xtensive.Sql.Drivers.PostgreSql; + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + [TestFixture] + public sealed class PostgreSqlHelperTest : SqlTest + { + private IReadOnlyDictionary serverTimeZones; + + private string[] testTimezones; + + public static TimeSpan[] Intervals + { + get => new[] { + TimeSpan.FromDays(66).Add(TimeSpan.FromHours(4)).Add(TimeSpan.FromMinutes(45)).Add(TimeSpan.FromSeconds(36)), + TimeSpan.FromDays(32).Add(TimeSpan.FromHours(2)).Add(TimeSpan.FromMinutes(44)).Add(TimeSpan.FromSeconds(35)), + TimeSpan.FromDays(16).Add(TimeSpan.FromHours(3)).Add(TimeSpan.FromMinutes(43)).Add(TimeSpan.FromSeconds(34)), + TimeSpan.FromDays(3).Add(TimeSpan.FromHours(1)).Add(TimeSpan.FromMinutes(42)).Add(TimeSpan.FromSeconds(33)), + TimeSpan.FromHours(25).Add(TimeSpan.FromMinutes(15)).Add(TimeSpan.FromSeconds(44)), + TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(14)).Add(TimeSpan.FromSeconds(43)), + TimeSpan.FromHours(19).Add(TimeSpan.FromMinutes(13)).Add(TimeSpan.FromSeconds(42)), + TimeSpan.FromHours(4).Add(TimeSpan.FromMinutes(12)).Add(TimeSpan.FromSeconds(41)), + TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(11)).Add(TimeSpan.FromSeconds(40)), + TimeSpan.FromMinutes(65).Add(TimeSpan.FromSeconds(48)), + TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(47)), + TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(46)), + TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(45)), + TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(44)), + TimeSpan.FromSeconds(44).Add(TimeSpan.FromMilliseconds(445)), + TimeSpan.FromSeconds(44).Add(TimeSpan.FromMilliseconds(400)), + TimeSpan.FromSeconds(44).Add(TimeSpan.FromMilliseconds(332)), + TimeSpan.FromSeconds(44).Add(TimeSpan.FromMilliseconds(248)), + TimeSpan.FromSeconds(44).Add(TimeSpan.FromMilliseconds(183)), + TimeSpan.FromMilliseconds(444), + TimeSpan.FromMilliseconds(402), + TimeSpan.FromMilliseconds(333), + TimeSpan.FromMilliseconds(249), + TimeSpan.FromMilliseconds(181) + }; + } + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + var nativeDriver = (Xtensive.Sql.Drivers.PostgreSql.Driver) Driver; + + serverTimeZones = nativeDriver.PostgreServerInfo.ServerTimeZones; + testTimezones = serverTimeZones.Keys.Union(new[] { + "<+02>-02", + "<+2>-2", + "<+05>-05", + "<+5>-5", + "<+07>-07", + "<+7>-7", + "<-02>+02", + "<-2>+2", + "<-05>+05", + "<-5>+5", + "<-07>+07", + "<-7>+7" + } ).ToArray(); + + Connection.Close(); + } + + [Test] + public void TimeZoneRecognitionTest() + { + foreach(var timezone in testTimezones) { + + if (timezone.StartsWith('<')) { + if (timezone.Contains('0')) { + + + Assert.That(serverTimeZones.TryGetValue(timezone, out var result1), Is.False); + Assert.That(serverTimeZones.TryGetValue(PostgreSqlHelper.TryGetZoneFromPosix(timezone), out var result2), Is.True); + Assert.That(result2, Is.EqualTo(TimeSpan.FromHours(2)) + .Or.EqualTo(TimeSpan.FromHours(5)) + .Or.EqualTo(TimeSpan.FromHours(7)) + .Or.EqualTo(TimeSpan.FromHours(-2)) + .Or.EqualTo(TimeSpan.FromHours(-5)) + .Or.EqualTo(TimeSpan.FromHours(-7))); + } + else { + Assert.That(serverTimeZones.TryGetValue(timezone, out var result1), Is.False); + Assert.That(serverTimeZones.TryGetValue(PostgreSqlHelper.TryGetZoneFromPosix(timezone), out var result2), Is.False); + } + + } + else { + Assert.That(serverTimeZones.TryGetValue(timezone, out var result1), Is.True); + Assert.That(serverTimeZones.TryGetValue(PostgreSqlHelper.TryGetZoneFromPosix(timezone), out var result2), Is.True); + Assert.That(result1, Is.EqualTo(result2)); + } + } + } + + [Test] + [TestCaseSource(nameof(Intervals))] + public void TimeSpanToIntervalConversionTest(TimeSpan testValue) + { + var nativeInterval = PostgreSqlHelper.CreateNativeIntervalFromTimeSpan(testValue); + var backToTimeSpan = PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); + Assert.That(backToTimeSpan, Is.EqualTo(testValue)); + } + } +} From f7edd118c1d60ed810266d0fa5b150356be0658f Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 27 Feb 2025 13:31:09 +0500 Subject: [PATCH 28/48] Tests improved --- .../PostgreSql/InfinityAliasTest.cs | 779 +++--------------- .../DateTimeOffset/PartsExtractionTest.cs | 239 +++++- 2 files changed, 330 insertions(+), 688 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs index adc9969e7..c01bd1c8f 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs @@ -10,6 +10,7 @@ namespace Xtensive.Orm.Tests.Sql.PostgreSql { + [TestFixture] public sealed class InfinityAliasTest : SqlTest { private const string DateOnlyMinValueTable = "DateOnlyTable1"; @@ -37,6 +38,11 @@ protected override void TestFixtureSetUp() { base.TestFixtureSetUp(); + var localZone = DateTimeOffset.Now.ToLocalTime().Offset; + var localZoneString = ((localZone < TimeSpan.Zero) ? "-" : "+") + localZone.ToString(@"hh\:mm"); + var initConnectionCommand = Connection.CreateCommand($"SET TIME ZONE INTERVAL '{localZoneString}' HOUR TO MINUTE"); + _ = initConnectionCommand.ExecuteNonQuery(); + longTypeMapping = Driver.TypeMappings[typeof(long)]; dateOnlyTypeMapping = Driver.TypeMappings[typeof(DateOnly)]; dateTimeTypeMapping = Driver.TypeMappings[typeof(DateTime)]; @@ -135,149 +141,23 @@ public void DateTimeMinSelectDatePartDateTest() private void TestMinDateTimeSelectDatePart(bool isOn) { - var template = templates[DateTimeMinValueTable]; - - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Year, isOn); - } - } - - var select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Year, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using(command) - using(var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Year, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Month, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Month, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Month, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Day, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Day, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Day, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Hour, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Hour, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Hour, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Minute, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Minute, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Minute, isOn); - } - } + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Year, + DateTime.MinValue.Year, DateTime.MinValue.Year, isOn); + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Month, + DateTime.MinValue.Month, DateTime.MinValue.Month, isOn); - command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Second, isOn); - } - } + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Day, + DateTime.MinValue.Day, DateTime.MinValue.Day, isOn); - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Second, select.From.Columns["Value"])); + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Hour, + DateTime.MinValue.Hour, DateTime.MinValue.Hour, isOn); - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Minute, + DateTime.MinValue.Minute, DateTime.MinValue.Minute, isOn); - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MinValue.Second, isOn); - } - } + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Second, + DateTime.MinValue.Second, DateTime.MinValue.Second, isOn); } @@ -345,148 +225,23 @@ public void DateTimeMaxSelectDatePartDateTest() private void TestMaxDateTimeSelectDatePart(bool isOn) { - var template = templates[DateTimeMaxValueTable]; + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Year, + DateTime.MaxValue.Year, DateTime.MaxValue.Year, isOn); - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Month, + DateTime.MaxValue.Month, DateTime.MaxValue.Month, isOn); - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Year, isOn); - } - } + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Day, + DateTime.MaxValue.Day, DateTime.MaxValue.Day, isOn); - var select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Year, select.From.Columns["Value"])); + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Hour, + DateTime.MaxValue.Hour, DateTime.MaxValue.Hour, isOn); - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Minute, + DateTime.MaxValue.Minute, DateTime.MaxValue.Minute, isOn); - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Year, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Month, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Month, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Month, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Day, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Day, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Day, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Hour, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Hour, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Hour, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Minute, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Minute, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Minute, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Second, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimePart.Second, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTime.MaxValue.Second, isOn); - } - } + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Second, + DateTime.MaxValue.Second, DateTime.MaxValue.Second, isOn); } @@ -552,76 +307,14 @@ public void DateOnlyMinSelectDatePartDateTest() private void TestMinDateOnlySelectDatePart(bool isOn) { - var template = templates[DateOnlyMinValueTable]; - - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MinValue.Year, isOn); - } - } - - var select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDatePart.Year, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MinValue.Year, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MinValue.Month, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDatePart.Month, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MinValue.Month, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { + TestDatePartExtraction(DateOnlyMinValueTable, SqlDatePart.Year, + DateOnly.MinValue.Year, DateOnly.MinValue.Year, isOn); - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MinValue.Day, isOn); - } - } + TestDatePartExtraction(DateOnlyMinValueTable, SqlDatePart.Month, + DateOnly.MinValue.Month, DateOnly.MinValue.Month, isOn); - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDatePart.Day, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MinValue.Day, isOn); - } - } + TestDatePartExtraction(DateOnlyMinValueTable, SqlDatePart.Day, + DateOnly.MinValue.Day, DateOnly.MinValue.Day, isOn); } @@ -687,92 +380,17 @@ public void DateOnlyMaxSelectDatePartDateTest() private void TestMaxDateOnlySelectDatePart(bool isOn) { - var template = templates[DateOnlyMaxValueTable]; - - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MaxValue.Year, isOn); - } - } - - var select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDatePart.Year, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MaxValue.Year, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MaxValue.Month, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDatePart.Month, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MaxValue.Month, isOn); - } - } - - - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateOnlyMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MaxValue.Day, isOn); - } - } + TestDatePartExtraction(DateOnlyMaxValueTable, SqlDatePart.Year, + DateOnly.MaxValue.Year, DateOnly.MaxValue.Year, isOn); - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDatePart.Day, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { + TestDatePartExtraction(DateOnlyMaxValueTable, SqlDatePart.Month, + DateOnly.MaxValue.Month, DateOnly.MaxValue.Month, isOn); - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateOnly.MaxValue.Day, isOn); - } - } + TestDatePartExtraction(DateOnlyMaxValueTable, SqlDatePart.Day, + DateOnly.MaxValue.Day, DateOnly.MaxValue.Day, isOn); } - - - - - - - - - - - - [Test] public void DateTimeOffsetMinSelectNoFilterTest() { @@ -835,151 +453,32 @@ public void DateTimeOffsetMinSelectDatePartDateTest() private void TestMinDateTimeOffsetSelectDatePart(bool isOn) { - var template = templates[DateTimeOffsetMinValueTable]; - - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTimeOffset.MinValue.Year, isOn); - } - } - - var select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Year, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTimeOffset.MinValue.Year, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTimeOffset.MinValue.Month, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Month, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPart(partValue, DateTimeOffset.MinValue.Month, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, DateTimeOffset.MinValue.Day, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Day, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPart(partValue, DateTimeOffset.MinValue.Day, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone - CheckPartNative(partValue, 4, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Hour, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone - CheckPart(partValue, (isOn) ? DateTimeOffset.MinValue.Hour : 4, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone - CheckPartNative(partValue, 2, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Minute, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone - CheckPart(partValue, (isOn) ? DateTimeOffset.MinValue.Minute : 2, isOn); - } - } - - command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeOffsetMinValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone - CheckPartNative(partValue, 33, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Second, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone - CheckPart(partValue, (isOn) ? DateTimeOffset.MinValue.Second : 33, isOn); - } - } + TestDateTimeOffsetPartExtraction(DateTimeOffsetMinValueTable, SqlDateTimeOffsetPart.Year, + DateTimeOffset.MinValue.Year, + DateTimeOffset.MinValue.Year, + isOn); + TestDateTimeOffsetPartExtraction(DateTimeOffsetMinValueTable, SqlDateTimeOffsetPart.Month, + DateTimeOffset.MinValue.Month, + DateTimeOffset.MinValue.Month, + isOn); + TestDateTimeOffsetPartExtraction(DateTimeOffsetMinValueTable, SqlDateTimeOffsetPart.Day, + DateTimeOffset.MinValue.Day, + DateTimeOffset.MinValue.Day, + isOn); + + // timezone for DateTimeOffset.MinValue value in postgre is set to 04:02:33, at least when instance is in UTC+5 timezone + TestDateTimeOffsetPartExtraction(DateTimeOffsetMinValueTable, SqlDateTimeOffsetPart.Hour, + 5, + isOn ? DateTimeOffset.MinValue.Hour : 5, + isOn); + TestDateTimeOffsetPartExtraction(DateTimeOffsetMinValueTable, SqlDateTimeOffsetPart.Minute, + DateTimeOffset.MinValue.Minute, + DateTimeOffset.MinValue.Minute, + isOn); + TestDateTimeOffsetPartExtraction(DateTimeOffsetMinValueTable, SqlDateTimeOffsetPart.Second, + DateTimeOffset.MinValue.Second, + DateTimeOffset.MinValue.Second, + isOn); } @@ -1047,46 +546,54 @@ public void DateTimeOffsetMaxSelectDatePartDateTest() private void TestMaxDateTimeOffsetSelectDatePart(bool isOn) { - var template = templates[DateTimeOffsetMaxValueTable]; - - var command = Connection.CreateCommand($"SELECT EXTRACT (YEAR FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { + // There is overflow of year because of PostgreSQL time zone functionality + TestDateTimeOffsetPartExtraction(DateTimeOffsetMaxValueTable, SqlDateTimeOffsetPart.Year, + DateTimeOffset.MaxValue.Year + 1, + (isOn) ? DateTimeOffset.MaxValue.Year : DateTimeOffset.MaxValue.Year + 1, + isOn); - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // There is overflow of year because of PostgreSQL time zone functionality - CheckPartNative(partValue, DateTimeOffset.MaxValue.Year + 1, isOn); - } - } + // there is value overflow to 01 in case of no aliases + TestDateTimeOffsetPartExtraction(DateTimeOffsetMaxValueTable, SqlDateTimeOffsetPart.Month, + 1, + (isOn) ? DateTimeOffset.MaxValue.Month : 1, + isOn); + // there is value overflow to 01 in case of no aliases + TestDateTimeOffsetPartExtraction(DateTimeOffsetMaxValueTable, SqlDateTimeOffsetPart.Day, + 1, + (isOn) ? DateTimeOffset.MaxValue.Day : 1, + isOn); - var select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Year, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { + // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone + TestDateTimeOffsetPartExtraction(DateTimeOffsetMaxValueTable, SqlDateTimeOffsetPart.Hour, + 4, + (isOn) ? DateTimeOffset.MaxValue.Hour : 4, + isOn); + TestDateTimeOffsetPartExtraction(DateTimeOffsetMaxValueTable, SqlDateTimeOffsetPart.Minute, + DateTimeOffset.MaxValue.Minute, + DateTimeOffset.MaxValue.Minute, + isOn); + TestDateTimeOffsetPartExtraction(DateTimeOffsetMaxValueTable, SqlDateTimeOffsetPart.Second, + DateTimeOffset.MaxValue.Second, + DateTimeOffset.MaxValue.Second, + isOn); + } - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // There is overflow of year because of PostgreSQL time zone functionality - CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Year : DateTimeOffset.MaxValue.Year + 1, isOn); - } - } + private void TestDatePartExtraction(string table, SqlDatePart part, int expectedValueNative, int expectedValueDml, bool aliasesEnabled) + { + var template = templates[table]; - command = Connection.CreateCommand($"SELECT EXTRACT (MONTH FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + var command = Connection.CreateCommand($"SELECT EXTRACT ({part.ToString().ToUpperInvariant()} FROM \"Value\") FROM public.\"{table}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { var partValue = reader.GetDouble(0); - // there is value overflow to 01 - CheckPartNative(partValue, DateTimeOffset.MinValue.Month, isOn); + CheckPartNative(partValue, expectedValueNative, aliasesEnabled); } } - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Month, select.From.Columns["Value"])); + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(part, select.From.Columns["Value"])); command = Connection.CreateCommand(select); using (command) @@ -1094,24 +601,27 @@ private void TestMaxDateTimeOffsetSelectDatePart(bool isOn) while (reader.Read()) { var partValue = reader.GetDouble(0); - // there is value overflow to 01 in case of no aliases - CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Month : DateTimeOffset.MinValue.Month, isOn); + CheckPart(partValue, expectedValueDml, aliasesEnabled); } } + } + + private void TestDateTimePartExtraction(string table, SqlDateTimePart part, int expectedValueNative, int expectedValueDml, bool aliasesEnabled) + { + var template = templates[table]; - command = Connection.CreateCommand($"SELECT EXTRACT (DAY FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + var command = Connection.CreateCommand($"SELECT EXTRACT ({part.ToString().ToUpperInvariant()} FROM \"Value\") FROM public.\"{table}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { var partValue = reader.GetDouble(0); - // there is value overflow to 01 - CheckPartNative(partValue, DateTime.MinValue.Day, isOn); + CheckPartNative(partValue, expectedValueNative, aliasesEnabled); } } - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Day, select.From.Columns["Value"])); + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(part, select.From.Columns["Value"])); command = Connection.CreateCommand(select); using (command) @@ -1119,48 +629,27 @@ private void TestMaxDateTimeOffsetSelectDatePart(bool isOn) while (reader.Read()) { var partValue = reader.GetDouble(0); - // there is value overflow to 01 in case of no aliases - CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Day : DateTimeOffset.MinValue.Day, isOn); + CheckPart(partValue, expectedValueDml, aliasesEnabled); } } + } - command = Connection.CreateCommand($"SELECT EXTRACT (HOUR FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone - CheckPartNative(partValue, 4, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Hour, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); - using (command) - using (var reader = command.ExecuteReader()) { - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone - CheckPart(partValue, (isOn) ? DateTimeOffset.MaxValue.Hour : 4, isOn); - } - } + private void TestDateTimeOffsetPartExtraction(string table, SqlDateTimeOffsetPart part, int expectedValueNative, int expectedValueDml, bool aliasesEnabled) + { + var template = templates[table]; - command = Connection.CreateCommand($"SELECT EXTRACT (MINUTE FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); + var command = Connection.CreateCommand($"SELECT EXTRACT ({part.ToString().ToUpperInvariant()} FROM \"Value\") FROM public.\"{table}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone - CheckPartNative(partValue, 59, isOn); + CheckPartNative(partValue, expectedValueNative, aliasesEnabled); } } - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Minute, select.From.Columns["Value"])); + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(part, select.From.Columns["Value"])); command = Connection.CreateCommand(select); using (command) @@ -1168,33 +657,17 @@ private void TestMaxDateTimeOffsetSelectDatePart(bool isOn) while (reader.Read()) { var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone - CheckPart(partValue, DateTimeOffset.MaxValue.Minute, isOn); + CheckPart(partValue, expectedValueDml, aliasesEnabled); } } - command = Connection.CreateCommand($"SELECT EXTRACT (SECOND FROM \"Value\") FROM public.\"{DateTimeOffsetMaxValueTable}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone - CheckPartNative(partValue, DateTimeOffset.MaxValue.Second, isOn); - } - } - - select = template.Clone(new SqlNodeCloneContext()); - select.Columns.Add(SqlDml.Extract(SqlDateTimeOffsetPart.Second, select.From.Columns["Value"])); - - command = Connection.CreateCommand(select); + command = Connection.CreateCommand($"SELECT EXTRACT (TIMEZONE FROM \"Value\") FROM public.\"{table}\""); using (command) using (var reader = command.ExecuteReader()) { while (reader.Read()) { var partValue = reader.GetDouble(0); - // timezone for DateTimeOffset.MaxValue value in postgre is set to 04:59:59.999999, at least when instance is in UTC+5 timezone - CheckPart(partValue, DateTimeOffset.MaxValue.Second, isOn); + Console.WriteLine($"TIMEZONE: {partValue}"); } } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs index 592c892b6..49d9d079e 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs @@ -27,14 +27,21 @@ public void ExtractYearTest() } [Test] - public void MinMaxValueExtractYearTest() + public void ExtractYearMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Year == DateTimeOffset.MinValue.Year); - RunTest(s, c => c.MaxValue.Year == DateTimeOffset.MaxValue.Year); - RunWrongTest(s, c => c.MinValue.Year == DateTimeOffset.MinValue.AddYears(1).Year); + }); + } + + [Test] + public void ExtractYearMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Year == DateTimeOffset.MaxValue.Year + 1); RunWrongTest(s, c => c.MaxValue.Year == DateTimeOffset.MaxValue.AddYears(-1).Year); }); } @@ -54,14 +61,21 @@ public void ExtractMonthTest() } [Test] - public void MinMaxValueExtractMonthTest() + public void ExtractMonthMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Month == DateTimeOffset.MinValue.Month); - RunTest(s, c => c.MaxValue.Month == DateTimeOffset.MaxValue.Month); - RunWrongTest(s, c => c.MinValue.Month == DateTimeOffset.MinValue.AddMonths(1).Month); + }); + } + + [Test] + public void ExtractMonthMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Month == DateTimeOffset.MaxValue.Month - 11); RunWrongTest(s, c => c.MaxValue.Month == DateTimeOffset.MaxValue.AddMonths(-1).Month); }); } @@ -87,14 +101,22 @@ public void ExtractDayTest() } [Test] - public void MinMaxValueExtractDayTest() + public void ExtractDayMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Day == DateTimeOffset.MinValue.Day); - RunTest(s, c => c.MaxValue.Day == DateTimeOffset.MaxValue.Day); - RunWrongTest(s, c => c.MinValue.Day == DateTimeOffset.MinValue.AddDays(1).Day); + }); + } + + [Test] + public void ExtractDayMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + // year overflow happens on server side because of timezone + RunTest(s, c => c.MaxValue.Day == 1); RunWrongTest(s, c => c.MaxValue.Day == DateTimeOffset.MaxValue.AddDays(-1).Day); }); } @@ -120,14 +142,104 @@ public void ExtractHourTest() } [Test] - public void MinMaxValueExtractHourTest() + public void ExtractHourMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { - RunTest(s, c => c.MinValue.Hour == DateTimeOffset.MinValue.Hour); - RunTest(s, c => c.MaxValue.Hour == DateTimeOffset.MaxValue.Hour); + var service = s.Services.Get(); + + var command = service.CreateCommand(); + command.CommandText = $"SELECT COUNT(*) FROM public.\"MinMaxDateTimeOffsetEntity\" WHERE EXTRACT (HOUR FROM \"MinValue\") = 4"; + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var rowCount = reader.GetInt32(0); + Console.WriteLine($"Rows with HOUR 4 : {rowCount}"); + } + } + + command = service.CreateCommand(); + command.CommandText = $"SELECT COUNT(*) FROM public.\"MinMaxDateTimeOffsetEntity\" WHERE EXTRACT (HOUR FROM \"MinValue\") = 5"; + + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var rowCount = reader.GetInt32(0); + Console.WriteLine($"Rows with HOUR 5 : {rowCount}"); + } + } + + command = service.CreateCommand(); + command.CommandText = $"SELECT (EXTRACT (TIMEZONE FROM \"MinValue\"))::integer FROM public.\"MinMaxDateTimeOffsetEntity\""; + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var timezoneInSeconds = reader.GetDouble(0); + Console.WriteLine($"Timezone : {TimeSpan.FromSeconds(timezoneInSeconds)}"); + } + } + }); + + + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Hour == 5); RunWrongTest(s, c => c.MinValue.Hour == DateTimeOffset.MinValue.AddHours(1).Hour); + }); + } + + [Test] + public void ExtractHourMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + + ExecuteInsideSession((s) => { + var service = s.Services.Get(); + + var command = service.CreateCommand(); + command.CommandText = $"SELECT COUNT(*) FROM public.\"MinMaxDateTimeOffsetEntity\" WHERE EXTRACT (HOUR FROM \"MaxValue\") = 4"; + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var rowCount = reader.GetInt32(0); + Console.WriteLine($"Rows with HOUR 4 : {rowCount}"); + } + } + + command = service.CreateCommand(); + command.CommandText = $"SELECT COUNT(*) FROM public.\"MinMaxDateTimeOffsetEntity\" WHERE EXTRACT (HOUR FROM \"MaxValue\") = 5"; + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var rowCount = reader.GetInt32(0); + Console.WriteLine($"Rows with HOUR 5 : {rowCount}"); + } + } + + command = service.CreateCommand(); + command.CommandText = $"SELECT (EXTRACT (TIMEZONE FROM \"MaxValue\"))::integer FROM public.\"MinMaxDateTimeOffsetEntity\""; + + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + var timezoneInSeconds = reader.GetDouble(0); + Console.WriteLine($"Timezone : {TimeSpan.FromSeconds(timezoneInSeconds)}"); + } + } + }); + + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Hour == 4); RunWrongTest(s, c => c.MaxValue.Hour == DateTimeOffset.MaxValue.AddHours(-1).Hour); }); } @@ -152,14 +264,21 @@ public void ExtractMinuteTest() } [Test] - public void MinMaxValueExtractMinuteTest() + public void ExtractMinuteMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Minute == DateTimeOffset.MinValue.Minute); - RunTest(s, c => c.MaxValue.Minute == DateTimeOffset.MaxValue.Minute); - RunWrongTest(s, c => c.MinValue.Minute == DateTimeOffset.MinValue.AddMinutes(1).Minute); + }); + } + + [Test] + public void ExtractMinuteMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Minute == DateTimeOffset.MaxValue.Minute); RunWrongTest(s, c => c.MaxValue.Minute == DateTimeOffset.MaxValue.AddMinutes(-1).Minute); }); } @@ -184,14 +303,21 @@ public void ExtractSecondTest() } [Test] - public void MinMaxValueExtractSecondTest() + public void ExtractSecondMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Second == DateTimeOffset.MinValue.Second); - RunTest(s, c => c.MaxValue.Second == DateTimeOffset.MaxValue.Second); - RunWrongTest(s, c => c.MinValue.Second == DateTimeOffset.MinValue.AddSeconds(10).Second); + }); + } + + [Test] + public void ExtractSecondMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.Second == DateTimeOffset.MaxValue.Second); RunWrongTest(s, c => c.MaxValue.Second == DateTimeOffset.MaxValue.AddSeconds(-10).Second); }); } @@ -228,14 +354,23 @@ public void ExtractDateTest() } [Test] - public void MinMaxValueExtractDateTest() + public void ExtractDateMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.Date == DateTimeOffset.MinValue.Date); - RunTest(s, c => c.MaxValue.Date == DateTimeOffset.MaxValue.Date); - RunWrongTest(s, c => c.MinValue.Date == DateTimeOffset.MinValue.AddDays(1).Date); + }); + } + + [Test] + public void ExtractDateMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + // overflow of year from 9999-12-31 to 10000-01-01 because of how postgre works with timezones + // can't validate + RunWrongTest(s, c => c.MaxValue.DateTime == DateTimeOffset.MaxValue.DateTime); RunWrongTest(s, c => c.MaxValue.Date == DateTimeOffset.MaxValue.AddDays(-1).Date); }); } @@ -269,14 +404,24 @@ public void ExtractTimeOfDayTest() } [Test] - public void MinMaxValueExtractTimeOfDayTest() + public void ExtractTimeOfDayMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { - RunTest(s, c => c.MinValue.TimeOfDay == DateTimeOffset.MinValue.TimeOfDay); - RunTest(s, c => c.MaxValue.TimeOfDay == DateTimeOffset.MaxValue.TimeOfDay); - + var minValueAdjusted = TryMoveToLocalTimeZone(DateTimeOffset.MinValue); + RunTest(s, c => c.MinValue.TimeOfDay == minValueAdjusted.TimeOfDay); RunWrongTest(s, c => c.MinValue.TimeOfDay == DateTimeOffset.MinValue.AddMinutes(10).TimeOfDay); + }); + } + + [Test] + public void ExtractTimeOfDayMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + // year overflow on server side + var adjustedMaxValue = TryMoveToLocalTimeZone(DateTimeOffset.MaxValue.AddYears(-1).AddTicks(-9)); + RunTest(s, c => c.MaxValue.TimeOfDay == adjustedMaxValue.TimeOfDay); RunWrongTest(s, c => c.MaxValue.TimeOfDay == DateTimeOffset.MaxValue.AddMinutes(-10).TimeOfDay); }); } @@ -339,14 +484,21 @@ public void ExtractDayOfYearTest() } [Test] - public void MinMaxValueExtractDayOfYearTest() + public void ExtractDayOfYearMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.DayOfYear == DateTimeOffset.MinValue.DayOfYear); - RunTest(s, c => c.MaxValue.DayOfYear == DateTimeOffset.MaxValue.DayOfYear); - RunWrongTest(s, c => c.MinValue.DayOfYear == DateTimeOffset.MinValue.AddDays(1).DayOfYear); + }); + } + + [Test] + public void ExtractDayOfYearMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.DayOfYear == 1); RunWrongTest(s, c => c.MaxValue.DayOfYear == DateTimeOffset.MaxValue.AddDays(-1).DayOfYear); }); } @@ -371,14 +523,21 @@ public void ExtractDayOfWeekTest() } [Test] - public void MinMaxValueExtractDayOfWeekTest() + public void ExtractDayOfWeekMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { RunTest(s, c => c.MinValue.DayOfWeek == DateTimeOffset.MinValue.DayOfWeek); - RunTest(s, c => c.MaxValue.DayOfWeek == DateTimeOffset.MaxValue.DayOfWeek); - RunWrongTest(s, c => c.MinValue.DayOfWeek == DateTimeOffset.MinValue.AddDays(1).DayOfWeek); + }); + } + + [Test] + public void ExtractDayOfWeekMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue.DayOfWeek == DateTimeOffset.MaxValue.DayOfWeek + 1); RunWrongTest(s, c => c.MaxValue.DayOfWeek == DateTimeOffset.MaxValue.AddDays(-1).DayOfWeek); }); } @@ -403,14 +562,24 @@ public void ExtractDateTimeTest() } [Test] - public void MinMaxValueExtractDateTimeTest() + public void ExtractDateTimeMinValueTest() { Require.ProviderIs(StorageProvider.PostgreSql); ExecuteInsideSession((s) => { - RunTest(s, c => c.MinValue.DateTime == DateTimeOffset.MinValue.DateTime); - RunTest(s, c => c.MaxValue.DateTime == DateTimeOffset.MaxValue.DateTime); - + var minValueAdjusted = TryMoveToLocalTimeZone(DateTimeOffset.MinValue); + RunTest(s, c => c.MinValue.DateTime == minValueAdjusted.DateTime); RunWrongTest(s, c => c.MinValue.DateTime == DateTimeOffset.MinValue.AddDays(1).DateTime); + }); + } + + [Test] + public void ExtractDateTimeMaxValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + // overflow of year from 9999-12-31 to 10000-01-01 because of how postgre works with timezones + // can't validate + RunWrongTest(s, c => c.MaxValue.DateTime == DateTimeOffset.MaxValue.DateTime); RunWrongTest(s, c => c.MaxValue.DateTime == DateTimeOffset.MaxValue.AddDays(-1).DateTime); }); } From b9f3956522df9e9cc0cdc03042a53bba9ea7f51f Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 27 Feb 2025 19:39:40 +0500 Subject: [PATCH 29/48] StorageProviderVersion extended + TestConfiguration better names --- .../StorageProviderVersion.cs | 6 ++++++ Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs b/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs index c45858ca4..7bab8284b 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/StorageProviderVersion.cs @@ -31,9 +31,15 @@ public static class StorageProviderVersion public static Version PostgreSql90 = new Version(9, 0); public static Version PostgreSql91 = new Version(9, 1); public static Version PostgreSql92 = new Version(9, 2); + public static Version PostgreSql96 = new Version(9, 6); 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 PostgreSql130 = new Version(13, 0); + public static Version PostgreSql140 = new Version(14, 0); + public static Version PostgreSql150 = new Version(15, 0); + public static Version PostgreSql160 = new Version(16, 0); + public static Version PostgreSql170 = new Version(17, 0); public static Version MySql55 = new Version(5, 5); public static Version MySql56 = new Version(5, 6); diff --git a/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs b/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs index 4da6c89fa..227e05126 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs @@ -71,12 +71,12 @@ public void InitAppContextSwitches() private void InitPostgreSqlSwitches() { var infinityAliasesValue = GetEnvironmentVariable(InfinityAliasesKey); - if (bool.TryParse(infinityAliasesValue, out var bbb)) { - AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", !bbb); + if (bool.TryParse(infinityAliasesValue, out var switch1Value)) { + AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", !switch1Value); } var legacyTimestampsValue = GetEnvironmentVariable(LegacyTimestapmKey); - if (bool.TryParse(legacyTimestampsValue, out var ccc)) { - AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", ccc); + if (bool.TryParse(legacyTimestampsValue, out var switch2Value)) { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", switch2Value); } } From 57ee1a596eb9d978142fb198f6274873bc772e96 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 27 Feb 2025 19:40:39 +0500 Subject: [PATCH 30/48] Fixed mismatch of +/-Infinity replacements when part extracted --- .../Sql.Drivers.PostgreSql/v8_0/Compiler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ca4262b16..c459d958a 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 @@ -452,8 +452,8 @@ public override void Visit(SqlExtract node) _ = delayedExtractOperations.Add(node); (SqlExpression min, SqlExpression max) minMaxValues = ExtractMinMaxValuesForPart(node); var @case = SqlDml.Case(); - @case[node.Operand == Infinity] = minMaxValues.min; - @case[node.Operand == NegativeInfinity] = minMaxValues.max; + @case[node.Operand == Infinity] = minMaxValues.max; + @case[node.Operand == NegativeInfinity] = minMaxValues.min; @case.Else = node; @case.AcceptVisitor(this); } From fe73869fd6e987b9edbc388be2e896393d10e4dd Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 27 Feb 2025 20:39:10 +0500 Subject: [PATCH 31/48] Postgre: Native support of reading/binding DateOnly values --- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index 259f53266..ee94c6560 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -134,6 +134,13 @@ public override void BindGuid(DbParameter parameter, object value) parameter.Value = value == null ? (object) DBNull.Value : SqlHelper.GuidToString((Guid) value); } + [SecuritySafeCritical] + public override void BindDateOnly(DbParameter parameter, object value) + { + parameter.DbType = DbType.Date; + parameter.Value = value != null ? (DateOnly) value : DBNull.Value; + } + [SecuritySafeCritical] public override void BindDateTime(DbParameter parameter, object value) { @@ -241,6 +248,11 @@ public override object ReadDecimal(DbDataReader reader, int index) return nativeReader.GetDecimal(index); } + public override object ReadDateOnly(DbDataReader reader, int index) + { + return reader.GetFieldValue(index); + } + public override object ReadDateTime(DbDataReader reader, int index) { var value = reader.GetDateTime(index); From daffaadb383348b70c9ddd926385229b1e83d2d1 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 27 Feb 2025 20:41:02 +0500 Subject: [PATCH 32/48] Improved checks for min/max values When infinity aliases enabled Npgsql makes correct replacement, otherwise we replace "max value" from server with true max value --- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index ee94c6560..25f7424f2 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -256,9 +256,10 @@ public override object ReadDateOnly(DbDataReader reader, int index) public override object ReadDateTime(DbDataReader reader, int index) { var value = reader.GetDateTime(index); - if (value.Ticks == 0) - return DateTime.MinValue; + if (value == DateTime.MinValue || value == DateTime.MaxValue) + return value; if (value.Ticks == DateTimeMaxValueAdjustedTicks) { + // When Infinity aliases are disabled. // To not ruin possible comparisons with defined value, // it is better to return definded value, // not the 6-digit version from PostgreSQL @@ -272,14 +273,15 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; var value = nativeReader.GetFieldValue(index); - if (value.Ticks == DateTimeMaxValueAdjustedTicks) { + if (value.Ticks == DateTimeMaxValueAdjustedTicks ) { + // When Infinity aliases are disabled. // To not ruin possible comparisons with defined values, // it is better to return definded value, // not the 6-fractions version from PostgreSQL return DateTimeOffset.MaxValue; } - if (value.Ticks == 0) - return DateTimeOffset.MinValue; + if (value == DateTimeOffset.MaxValue || value == DateTimeOffset.MaxValue) + return value; if (legacyTimestampBehaviorEnabled) { // Npgsql 4 or older behavior From 4d349c3385ee3da1b9c67e490e89c192487092e3 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 3 Mar 2025 10:41:37 +0500 Subject: [PATCH 33/48] InfinityAliasTest: Handles extraction non-Year part from infinity value Some parts are extracted from infinity as null value and some parts as double.Infinity --- .../PostgreSql/InfinityAliasTest.cs | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs index c01bd1c8f..a3a321719 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs @@ -327,12 +327,12 @@ public void DateOnlyMaxNoFilterTest() while (reader.Read()) { var id = reader.GetInt64(0); - var datetimeValue = DateOnly.FromDateTime(reader.GetDateTime(1)); + var datetimeValue = reader.GetFieldValue(1); Assert.That(datetimeValue, Is.EqualTo(DateOnly.MaxValue)); } } - var select = templates[DateTimeMaxValueTable].Clone(new SqlNodeCloneContext()); + var select = templates[DateOnlyMaxValueTable].Clone(new SqlNodeCloneContext()); select.Columns.Add(select.From.Columns["Id"]); select.Columns.Add(select.From.Columns["Value"]); @@ -587,8 +587,25 @@ private void TestDatePartExtraction(string table, SqlDatePart part, int expected using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, expectedValueNative, aliasesEnabled); + if (aliasesEnabled && part != SqlDatePart.Year) { + // year from +-infinity -> +-infinity + // month from +-infinity -> null (or 0 in case of versions older that 9.6) + if (Driver.CoreServerInfo.ServerVersion >= StorageProviderVersion.PostgreSql96) { + Assert.That(reader.IsDBNull(0)); + } + else { + var partValue = reader.GetDouble(0); + Assert.That(partValue, Is.Zero); + } + } + if (Driver.CoreServerInfo.ServerVersion < StorageProviderVersion.PostgreSql96) { + var partValue = reader.GetDouble(0); + Assert.That(partValue, Is.Zero); + } + else { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, expectedValueNative, aliasesEnabled); + } } } @@ -615,8 +632,24 @@ private void TestDateTimePartExtraction(string table, SqlDateTimePart part, int using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, expectedValueNative, aliasesEnabled); + if (aliasesEnabled && part != SqlDateTimePart.Year) { + // year from +-infinity -> +-infinity + // month from +-infinity -> null (or 0 in case of versions older that 9.6) + if (Driver.CoreServerInfo.ServerVersion >= StorageProviderVersion.PostgreSql96) + Assert.That(reader.IsDBNull(0)); + else { + var partValue = reader.GetDouble(0); + Assert.That(partValue, Is.Zero); + } + } + if (Driver.CoreServerInfo.ServerVersion < StorageProviderVersion.PostgreSql96) { + var partValue = reader.GetDouble(0); + Assert.That(partValue, Is.Zero); + } + else { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, expectedValueNative, aliasesEnabled); + } } } @@ -643,8 +676,24 @@ private void TestDateTimeOffsetPartExtraction(string table, SqlDateTimeOffsetPar using (var reader = command.ExecuteReader()) { while (reader.Read()) { - var partValue = reader.GetDouble(0); - CheckPartNative(partValue, expectedValueNative, aliasesEnabled); + if (aliasesEnabled && part != SqlDateTimeOffsetPart.Year) { + // year from +-infinity -> +-infinity + // month from +-infinity -> null (or 0 in case of versions older that 9.6) + if (Driver.CoreServerInfo.ServerVersion >= StorageProviderVersion.PostgreSql96 ) + Assert.That(reader.IsDBNull(0)); + else { + var partValue = reader.GetDouble(0); + Assert.That(partValue, Is.Zero); + } + } + if (Driver.CoreServerInfo.ServerVersion < StorageProviderVersion.PostgreSql96) { + var partValue = reader.GetDouble(0); + Assert.That(partValue, Is.Zero); + } + else { + var partValue = reader.GetDouble(0); + CheckPartNative(partValue, expectedValueNative, aliasesEnabled); + } } } @@ -660,16 +709,6 @@ private void TestDateTimeOffsetPartExtraction(string table, SqlDateTimeOffsetPar CheckPart(partValue, expectedValueDml, aliasesEnabled); } } - - command = Connection.CreateCommand($"SELECT EXTRACT (TIMEZONE FROM \"Value\") FROM public.\"{table}\""); - using (command) - using (var reader = command.ExecuteReader()) { - - while (reader.Read()) { - var partValue = reader.GetDouble(0); - Console.WriteLine($"TIMEZONE: {partValue}"); - } - } } From 251996930c1844b97a2b7b4766dc5080a73b6a77 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 3 Mar 2025 11:31:36 +0500 Subject: [PATCH 34/48] Postgresql TypeMapper changes - TimeSpan reading assumes some values as MinValue/MaxValue, otherwise writing and then reading of MinValue/MaxValue will not be the same because of different number of fractional points in Postgre and .NET. We loose 18 particular values for comfort of comparison (we loose them anyway because of PG resolution). The odds of having MaxValue - 1-to-7 ticks or MinValue + 1-to-8 ticks as true values are extremely low. - some comments improved --- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index 25f7424f2..90a0860f8 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -20,7 +20,11 @@ namespace Xtensive.Sql.Drivers.PostgreSql.v8_0 internal class TypeMapper : Sql.TypeMapper { // 6 fractions instead of .NET's 7 - private const long DateTimeMaxValueAdjustedTicks = 3155378975999999990; + private const long DateTimeMaxValueAdjustedTicks = 3155378975999999990; + + // 6 fractions instead of .NET's 7 + private const long TimeSpanMinValueAdjustedTicks = -9223372036854775800; + private const long TimeSpanMaxValueAdjustedTicks = 9223372036854775800; protected readonly bool legacyTimestampBehaviorEnabled; protected readonly TimeSpan? defaultTimeZone; @@ -236,9 +240,17 @@ public override object ReadTimeSpan(DbDataReader reader, int index) var nativeReader = (NpgsqlDataReader) reader; var nativeInterval = nativeReader.GetFieldValue(index); - // support for full-range of Timespans required us to use raw type + // support for full-range of TimeSpans requires us to use raw type // and construct timespan from its' values. - return PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); + var result = PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); + + // for confinience of comparison in .NET we lose 7th fractional point and treat several + // .Net values as one, Min or Max value. + if (result == TimeSpan.MinValue || result.Ticks == TimeSpanMinValueAdjustedTicks) + return TimeSpan.MinValue; + if (result == TimeSpan.MaxValue || result.Ticks == TimeSpanMaxValueAdjustedTicks) + return TimeSpan.MaxValue; + return result; } [SecuritySafeCritical] @@ -259,10 +271,10 @@ public override object ReadDateTime(DbDataReader reader, int index) if (value == DateTime.MinValue || value == DateTime.MaxValue) return value; if (value.Ticks == DateTimeMaxValueAdjustedTicks) { - // When Infinity aliases are disabled. + // Applied when Infinity aliases are disabled. // To not ruin possible comparisons with defined value, // it is better to return definded value, - // not the 6-digit version from PostgreSQL + // not the 6-digit version from PostgreSQL. return DateTime.MaxValue; } return value; @@ -273,11 +285,11 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; var value = nativeReader.GetFieldValue(index); - if (value.Ticks == DateTimeMaxValueAdjustedTicks ) { - // When Infinity aliases are disabled. + if (value.Ticks == DateTimeMaxValueAdjustedTicks) { + // Applied when Infinity aliases are disabled. // To not ruin possible comparisons with defined values, // it is better to return definded value, - // not the 6-fractions version from PostgreSQL + // not the 6-fractions version from PostgreSQL. return DateTimeOffset.MaxValue; } if (value == DateTimeOffset.MaxValue || value == DateTimeOffset.MaxValue) @@ -288,7 +300,7 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) return value; } else { - // Here we try to apply connection timezone. + // Here, we try to apply connection timezone to the values we read. // To not get it from internal connection of DbDataReader // we assume that time zone switch happens (if happens) // in DomainConfiguration.ConnectionInitializationSql and From b62b2e9c244e26d3528daaae0d571c366f1d8add Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 4 Mar 2025 12:52:53 +0500 Subject: [PATCH 35/48] Postgresql compiler changes - operations based on server-side predefined constant values, not on defined results on client-side, in long term it is better and more reliable - small code improvements - fix of IntervalToMilliseconds for 9.0+ versions which lacked trunc operation --- .../Sql.Drivers.PostgreSql/v8_0/Compiler.cs | 349 +++++++----------- .../Sql.Drivers.PostgreSql/v9_0/Compiler.cs | 4 +- 2 files changed, 133 insertions(+), 220 deletions(-) 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 c459d958a..d1dccc2b9 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 @@ -47,9 +47,9 @@ internal class Compiler : SqlCompiler private static readonly SqlNative DateTimeMaxValue = SqlDml.Native("'9999-12-31 23:59:59.999999'::timestamp(6)"); private static readonly SqlNative DateTimeOffsetMinValue = SqlDml.Native("'0001-01-01 00:00:00.000000+00:00'::timestamp(6) with time zone"); - private static readonly SqlNative DateTimeOffsetMaxValue = SqlDml.Native("'9999-12-31 23:59:59.999999+00:00'::timestamp(6)"); + private static readonly SqlNative DateTimeOffsetMaxValue = SqlDml.Native("'9999-12-31 23:59:59.999999+00:00'::timestamp(6) with time zone"); - private static readonly SqlNative Infinity = SqlDml.Native("'Infinity'"); + private static readonly SqlNative PositiveInfinity = SqlDml.Native("'Infinity'"); private static readonly SqlNative NegativeInfinity = SqlDml.Native("'-Infinity'"); @@ -308,15 +308,10 @@ protected virtual void VisitIntervalToMilliseconds(SqlFunctionCall node) private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat, bool infinityEnabled) { - var conversionExpression = SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat); - if (infinityEnabled) { - var @case = SqlDml.Case(); - @case[dateTime == Infinity] = SqlDml.FunctionCall("TO_CHAR", DateTimeMaxValue, isoFormat); - @case[dateTime == NegativeInfinity] = SqlDml.FunctionCall("TO_CHAR", DateTimeMinValue, isoFormat); - @case.Else = conversionExpression; - return @case; - } - return conversionExpression; + var operand = infinityEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.FunctionCall("TO_CHAR", operand, isoFormat); } private static SqlExpression IntervalToIsoString(SqlExpression interval, bool signed) @@ -391,34 +386,6 @@ private static SqlExpression NpgsqlTypeConstructor(SqlExpression left, SqlExpres SqlDml.Native(")"))))); } - // it might be moved to context as some kind of extension - private readonly HashSet delayedExtractOperations = new(); - - private (SqlExpression min, SqlExpression max) ExtractMinMaxValuesForPart(SqlExtract node) - { - var actualPart = (SqlDateTimeOffsetPart) (node.IsDateTimePart - ? (int) node.DateTimePart - : node.IsDatePart - ? (int) node.DatePart - : node.IsDateTimeOffsetPart - ? (int) node.DateTimeOffsetPart - : throw new ArgumentOutOfRangeException()); - - return actualPart switch { - SqlDateTimeOffsetPart.Year => (SqlDml.Literal(1), SqlDml.Literal(9999)), - SqlDateTimeOffsetPart.Month => (SqlDml.Literal(1), SqlDml.Literal(12)), - SqlDateTimeOffsetPart.Day => (SqlDml.Literal(1), SqlDml.Literal(31)), - SqlDateTimeOffsetPart.DayOfWeek => (SqlDml.Literal(1), SqlDml.Literal(5)), // Monday and Friday - SqlDateTimeOffsetPart.DayOfYear => (SqlDml.Literal(1), SqlDml.Literal(365)), - SqlDateTimeOffsetPart.Hour => (SqlDml.Literal(0), SqlDml.Literal(23)), - SqlDateTimeOffsetPart.Minute or SqlDateTimeOffsetPart.Second => (SqlDml.Literal(0), SqlDml.Literal(59)), - SqlDateTimeOffsetPart.Millisecond => (SqlDml.Literal(0), SqlDml.Literal(999)), - SqlDateTimeOffsetPart.Nanosecond => (SqlDml.Literal(0), SqlDml.Literal(900)), - SqlDateTimeOffsetPart.TimeZoneHour or SqlDateTimeOffsetPart.TimeZoneMinute => (SqlDml.Literal(0), SqlDml.Literal(0)), - _ => throw new ArgumentOutOfRangeException() - }; - } - public override void Visit(SqlExtract node) { if (node.IsDateTimeOffsetPart) { @@ -442,23 +409,46 @@ public override void Visit(SqlExtract node) } } - if (infinityAliasForDatesEnabled - && (node.IsDatePart || node.IsDateTimePart || node.IsDateTimeOffsetPart) - && !delayedExtractOperations.Remove(node)) { - // If DateTime.MinValue => -Infinity and DateTime.MaxValue => Infinity conversion happens - // in Npgsql driver then we have to use SQL case statement to return correct values to the user. - // In this case we use original expression in ELSE part of CASE statement and postpone visiting - - _ = delayedExtractOperations.Add(node); - (SqlExpression min, SqlExpression max) minMaxValues = ExtractMinMaxValuesForPart(node); - var @case = SqlDml.Case(); - @case[node.Operand == Infinity] = minMaxValues.max; - @case[node.Operand == NegativeInfinity] = minMaxValues.min; - @case.Else = node; - @case.AcceptVisitor(this); + using (context.EnterScope(node)) { + AppendTranslatedEntry(node); + if (node.IsDateTimePart) { + translator.Translate(context.Output, node.DateTimePart); + } + else if (node.IsIntervalPart) { + translator.Translate(context.Output, node.IntervalPart); + } + else if (node.IsDatePart) { + translator.Translate(context.Output, node.DatePart); + } + else if (node.IsTimePart) { + translator.Translate(context.Output, node.TimePart); + } + else { + translator.Translate(context.Output, node.DateTimeOffsetPart); + } + AppendTranslated(node, ExtractSection.From); + if (infinityAliasForDatesEnabled && (node.IsDatePart || node.IsDateTimePart || node.IsDateTimeOffsetPart)) { + var minMaxValues = GetMinMaxValuesForPart(node); + CreateInfinityCheckExpression(node.Operand, minMaxValues.max, minMaxValues.min) + .AcceptVisitor(this); + } + else { + node.Operand.AcceptVisitor(this); + } + AppendTranslatedExit(node); } - else { - base.Visit(node); + + + (SqlExpression min, SqlExpression max) GetMinMaxValuesForPart(SqlExtract node) + { + if (node.IsDateTimePart) + return (DateTimeMinValue, DateTimeMaxValue); + if (node.IsDatePart) + return (DateMinValue, DateMaxValue); + if (node.IsDateTimeOffsetPart) + return (DateTimeOffsetMinValue, DateTimeOffsetMaxValue); + + throw new ArgumentOutOfRangeException("Can't define min and max values for given extract statement"); } } @@ -474,17 +464,17 @@ public override void Visit(SqlLiteral node) DateTime dtValue => dtValue == DateTime.MinValue ? NegativeInfinity : dtValue == DateTime.MaxValue - ? Infinity + ? PositiveInfinity : null, DateOnly dtValue => dtValue == DateOnly.MinValue ? NegativeInfinity : dtValue == DateOnly.MaxValue - ? Infinity + ? PositiveInfinity : null, DateTimeOffset dtValue => dtValue == DateTimeOffset.MinValue ? NegativeInfinity : dtValue == DateTimeOffset.MaxValue - ? Infinity + ? PositiveInfinity : null, _ => null }; @@ -566,122 +556,74 @@ protected virtual SqlExpression TimeToNanoseconds(SqlExpression time) protected SqlExpression DateTimeAddXxx(SqlExpression dateTime, SqlExpression addPart) { - var resultExpression = (dateTime + addPart); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[dateTime == Infinity] = DateTimeMaxValue + addPart; - @case[dateTime == NegativeInfinity] = DateTimeMinValue + addPart; - @case.Else = resultExpression; - return @case; - } - return resultExpression; + var operand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return (operand + addPart); } protected SqlExpression DateTimeTruncate(SqlExpression dateTime) { - var truncateExpression = SqlDml.FunctionCall("date_trunc", "day", dateTime); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[dateTime == Infinity] = SqlDml.Cast(DateMaxValue, SqlType.DateTime); - @case[dateTime == NegativeInfinity] = SqlDml.Cast(DateMinValue, SqlType.DateTime); - @case.Else = truncateExpression; - return @case; - } - return truncateExpression; + var operand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.FunctionCall("date_trunc", "day", operand); } protected SqlExpression DateAddXxx(SqlExpression date, SqlExpression addPart) { - var resultExpression = (date + addPart); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[date == Infinity] = DateMaxValue + addPart; - @case[date == NegativeInfinity] = DateMinValue + addPart; - @case.Else = resultExpression; - return @case; - } - return (date + addPart); + var operand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(date, DateMaxValue, DateMinValue) + : date; + return (operand + addPart); } protected SqlExpression DateTimeOffsetExtractDate(SqlExpression timestamp) { - var extractExpression = SqlDml.FunctionCall("DATE", timestamp); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[timestamp == Infinity] = DateMaxValue; - @case[timestamp == NegativeInfinity] = DateMinValue; - @case.Else = extractExpression; - return @case; - } - return extractExpression; + var extractOperand = (infinityAliasForDatesEnabled) + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return SqlDml.FunctionCall("DATE", timestamp); } protected SqlExpression DateTimeOffsetExtractDateTime(SqlExpression timestamp) { - var extractExpression = SqlDml.Cast(timestamp, SqlType.DateTime); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[timestamp == Infinity] = DateTimeMaxValue; - @case[timestamp == NegativeInfinity] = DateTimeMinValue; - @case.Else = extractExpression; - return @case; - } - - return extractExpression; + return DateTimeOffsetToDateTime(timestamp, infinityAliasForDatesEnabled); } protected SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression timestamp) { - var extractExpression = GetDateTimeInTimeZone(timestamp, TimeSpan.Zero); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[timestamp == Infinity] = DateTimeMaxValue; - @case[timestamp == NegativeInfinity] = DateTimeMinValue; - @case.Else = extractExpression; - return @case; - } - return extractExpression; + var convertOperand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return GetDateTimeInTimeZone(convertOperand, TimeSpan.Zero); } protected SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression timestamp) { - var extractExpression = SqlDml.Cast(timestamp, SqlType.DateTime); - if (infinityAliasForDatesEnabled) { - var @case = SqlDml.Case(); - @case[timestamp == Infinity] = DateTimeMaxValue; - @case[timestamp == NegativeInfinity] = DateTimeMinValue; - @case.Else = extractExpression; - return @case; - } - - return extractExpression; + var extractOperand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return SqlDml.Cast(extractOperand, SqlType.DateTime); } protected void DateTimeOffsetExtractOffset(SqlExtract node) { - if (infinityAliasForDatesEnabled && !delayedExtractOperations.Remove(node)) { - // If DateTime.MinValue => -Infinity and DateTime.MaxValue => Infinity conversion happens - // in Npgsql driver then we have to use SQL case statement to return correct values to the user. - // In this case we use original expression in ELSE part of CASE statement and postpone visiting - - _ = delayedExtractOperations.Add(node); - var @case = SqlDml.Case(); - @case[node.Operand == Infinity] = SqlDml.Native("'00:00'"); - @case[node.Operand == NegativeInfinity] = SqlDml.Native("'00:00'"); - @case.Else = node; - @case.AcceptVisitor(this); - } - else { - using (context.EnterScope(node)) { - AppendTranslatedEntry(node); - translator.Translate(context.Output, node.DateTimeOffsetPart); - AppendTranslated(node, ExtractSection.From); + using (context.EnterScope(node)) { + AppendTranslatedEntry(node); + translator.Translate(context.Output, node.DateTimeOffsetPart); + AppendTranslated(node, ExtractSection.From); + if (infinityAliasForDatesEnabled) { + CreateInfinityCheckExpression(node.Operand, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + .AcceptVisitor(this); + } + else { node.Operand.AcceptVisitor(this); - AppendSpace(); - AppendTranslatedExit(node); - AppendTranslated(SqlNodeType.Multiply); - OneSecondInterval.AcceptVisitor(this); } + AppendSpace(); + AppendTranslatedExit(node); + AppendTranslated(SqlNodeType.Multiply); + OneSecondInterval.AcceptVisitor(this); } } @@ -690,8 +632,8 @@ protected SqlExpression DateTimeOffsetTimeOfDay(SqlExpression timestamp) var resultExpression = DateTimeOffsetSubstract(timestamp, SqlDml.DateTimeTruncate(timestamp)); if (infinityAliasForDatesEnabled) { var @case = SqlDml.Case(); - @case[timestamp == Infinity] = SqlDml.Cast(MaxTimeLiteral, SqlType.Interval); - @case[timestamp == NegativeInfinity] = SqlDml.Cast(ZeroTimeLiteral, SqlType.Interval); + @case[timestamp == PositiveInfinity] = DateTimeOffsetSubstract(DateTimeOffsetMaxValue, SqlDml.DateTimeTruncate(DateTimeOffsetMaxValue)); + @case[timestamp == NegativeInfinity] = DateTimeOffsetSubstract(DateTimeOffsetMinValue, SqlDml.DateTimeTruncate(DateTimeOffsetMinValue)); @case.Else = resultExpression; return @case; } @@ -730,67 +672,42 @@ protected SqlExpression GetOffsetAsStringExpression(SqlExpression offsetInMinute private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(dateTime, SqlType.DateTimeOffset); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[dateTime == Infinity] = DateTimeOffsetMaxValue; - @case[dateTime == NegativeInfinity] = DateTimeOffsetMinValue; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.Cast(convertOperand, SqlType.DateTimeOffset); } private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(dateTimeOffset, SqlType.DateTime); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[dateTimeOffset == Infinity] = DateTimeMaxValue; - @case[dateTimeOffset == NegativeInfinity] = DateTimeMinValue; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTimeOffset, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : dateTimeOffset; + return SqlDml.Cast(convertOperand, SqlType.DateTime); } private static SqlExpression DateTimeToDate(SqlExpression dateTime, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(dateTime, SqlType.Date); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[dateTime == Infinity] = DateMaxValue; - @case[dateTime == NegativeInfinity] = DateMinValue; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.Cast(convertOperand, SqlType.Date); } private static SqlExpression DateToDateTime(SqlExpression date, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(date, SqlType.DateTime); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[date == Infinity] = MaxTimeLiteral; - @case[date == NegativeInfinity] = ZeroTimeLiteral; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(date, DateMaxValue, DateMinValue) + : date; + return SqlDml.Cast(convertOperand, SqlType.DateTime); } private static SqlExpression DateTimeToTime(SqlExpression dateTime, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(dateTime, SqlType.Time); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[dateTime == Infinity] = MaxTimeLiteral; - @case[dateTime == NegativeInfinity] = ZeroTimeLiteral; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.Cast(convertOperand, SqlType.Time); } private static SqlExpression TimeToDateTime(SqlExpression time) => @@ -798,41 +715,37 @@ private static SqlExpression TimeToDateTime(SqlExpression time) => private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(dateTimeOffset, SqlType.Date); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[dateTimeOffset == Infinity] = DateMaxValue; - @case[dateTimeOffset == NegativeInfinity] = DateMinValue; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTimeOffset, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : dateTimeOffset; + return SqlDml.Cast(convertOperand, SqlType.Date); } private static SqlExpression DateToDateTimeOffset(SqlExpression date, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(date, SqlType.DateTimeOffset); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[date == Infinity] = DateTimeOffsetMaxValue; - @case[date == NegativeInfinity] = DateTimeOffsetMinValue; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(date, DateMaxValue, DateMinValue) + : date; + return SqlDml.Cast(convertOperand, SqlType.DateTimeOffset); } private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset, bool infinityAliasEnabled) { - var convertExpression = SqlDml.Cast(dateTimeOffset, SqlType.Time); - if (infinityAliasEnabled) { - var @case = SqlDml.Case(); - @case[dateTimeOffset == Infinity] = MaxTimeLiteral; - @case[dateTimeOffset == NegativeInfinity] = ZeroTimeLiteral; - @case.Else = convertExpression; - return @case; - } - return convertExpression; + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTimeOffset, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : dateTimeOffset; + return SqlDml.Cast(convertOperand, SqlType.Time); + } + + private static SqlCase CreateInfinityCheckExpression(SqlExpression baseExpression, + SqlExpression ifPositiveInfinity, SqlExpression ifNegativeInfinity) + { + var @case = SqlDml.Case(); + @case[baseExpression == PositiveInfinity] = ifPositiveInfinity; + @case[baseExpression == NegativeInfinity] = ifNegativeInfinity; + @case.Else = baseExpression; + + return @case; } private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs index fcfe6835e..a44527781 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs @@ -15,9 +15,9 @@ internal class Compiler : v8_4.Compiler protected override void VisitIntervalToMilliseconds(SqlFunctionCall node) { AppendSpaceIfNecessary(); - _ = context.Output.Append("(EXTRACT(EPOCH FROM ("); + _ = context.Output.Append("(TRUNC(EXTRACT(EPOCH FROM ("); node.Arguments[0].AcceptVisitor(this); - _ = context.Output.Append(")) * 1000)"); + _ = context.Output.Append(")) * 1000))"); } From df67eacd2784a148d16bcd0d7e2f547dd2b94f76 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 4 Mar 2025 12:53:30 +0500 Subject: [PATCH 36/48] Add IntervalToMilliseconds test --- .../PostgreSql/IntervalToMillisecondsTest.cs | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs new file mode 100644 index 000000000..7a9d84d83 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs @@ -0,0 +1,198 @@ +// Copyright (C) 2025 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 NUnit.Framework; +using Xtensive.Sql; +using Xtensive.Sql.Dml; + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + public sealed class IntervalToMillisecondsTest : SqlTest + { + private const string IdColumnName = "Id"; + private const string ValueColumnName = "Value"; + private const string TableName = "IntervalToMsTest"; + + private TypeMapping longMapping; + private TypeMapping timeSpanMapping; + private TypeMapping doubleMapping; + + private SqlSelect selectQuery; + + private static TimeSpan[] TestValues + { + get => new[] { + TimeSpan.MinValue, + TimeSpan.MaxValue, + TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)), + TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)), + TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)), + TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)), + TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)), + TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)), + TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)), + TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)), + TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))), + TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))), + TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))), + TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))), + TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))), + TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))), + TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))), + TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))), + TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))), + TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))), + TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))), + TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))), + TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))), + TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), + + TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)).Negate(), + TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)).Negate(), + TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)).Negate(), + TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)).Negate(), + TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)).Negate(), + TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)).Negate(), + TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)).Negate(), + TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)).Negate(), + TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))).Negate(), + TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))).Negate(), + TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))).Negate(), + TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))).Negate(), + TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))).Negate(), + TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(), + TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(), + TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(), + TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(), + TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(), + TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(), + TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(), + TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(), + TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), + TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate() + }; + } + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + + longMapping = Driver.TypeMappings[typeof(long)]; + timeSpanMapping = Driver.TypeMappings[typeof(TimeSpan)]; + doubleMapping = Driver.TypeMappings[typeof(double)]; + + var dropTableCommand = Connection + .CreateCommand( + $"DROP TABLE IF EXISTS \"{TableName}\";"); + using (dropTableCommand) { + _ = dropTableCommand.ExecuteNonQuery(); + } + + var createTableCommand = Connection + .CreateCommand( + $"CREATE TABLE IF NOT EXISTS \"{TableName}\" (\"{IdColumnName}\" bigint CONSTRAINT PK_{TableName} PRIMARY KEY, \"{ValueColumnName}\" interval);"); + using (createTableCommand) { + _ = createTableCommand.ExecuteNonQuery(); + } + + var schema = ExtractDefaultSchema(); + var tableRef = SqlDml.TableRef(schema.Tables[TableName]); + var selectTotalMsQuery = SqlDml.Select(tableRef); + selectTotalMsQuery.Columns.Add(tableRef[IdColumnName], "id"); + selectTotalMsQuery.Columns.Add(tableRef[ValueColumnName], "timespan"); + selectTotalMsQuery.Columns.Add(SqlDml.IntervalToMilliseconds(tableRef[ValueColumnName]), "totalMs"); + selectTotalMsQuery.Where = tableRef[IdColumnName] == SqlDml.ParameterRef("pId"); + selectQuery = selectTotalMsQuery; + } + + protected override void TestFixtureTearDown() + { + longMapping = null; + timeSpanMapping = null; + doubleMapping = null; + selectQuery = null; + + base.TestFixtureTearDown(); + } + + + [Test] + [TestCaseSource(nameof(TestValues))] + public void MainTest(TimeSpan testCase) + { + TestValue(testCase); + } + + + private void TestValue(TimeSpan testCase) + { + InsertValue(testCase.Ticks, testCase); + var rowFromDb = SelectValue(testCase.Ticks); + var trueTotalMilliseconds = testCase.TotalMilliseconds; + var databaseValueTotalMilliseconds = rowFromDb.Item2.TotalMilliseconds; + var extractedTotalMilliseconds = rowFromDb.Item3; + + Assert.That(databaseValueTotalMilliseconds, Is.EqualTo(trueTotalMilliseconds)); + Assert.That(extractedTotalMilliseconds, Is.EqualTo(trueTotalMilliseconds)); + } + + private void InsertValue(long id, TimeSpan testCase) + { + var command = Connection.CreateCommand($"INSERT INTO \"{TableName}\"(\"{IdColumnName}\", \"{ValueColumnName}\") VALUES (@pId, @pValue)"); + var pId = Connection.CreateParameter(); + pId.ParameterName = "pId"; + longMapping.BindValue(pId, id); + _ = command.Parameters.Add(pId); + + var pValue = Connection.CreateParameter(); + pValue.ParameterName = "pValue"; + timeSpanMapping.BindValue(pValue, testCase); + _ = command.Parameters.Add(pValue); + using (command) { + _ = command.ExecuteNonQuery(); + } + } + + private (long, TimeSpan, double) SelectValue(long id) + { + var command = Connection.CreateCommand(selectQuery); + var pId = Connection.CreateParameter(); + pId.ParameterName = "pId"; + longMapping.BindValue(pId, id); + _ = command.Parameters.Add(pId); + + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var idFromDb = (long) longMapping.ReadValue(reader, 0); + var valueFromDb = (TimeSpan) timeSpanMapping.ReadValue(reader, 1); + var totalMs = (double) doubleMapping.ReadValue(reader, 2); + return (idFromDb, valueFromDb, totalMs); + } + } + + return default; + } + } +} From aeae65a9969bfb4902ee637d2f4e2e3ae4bb6524 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 4 Mar 2025 12:53:51 +0500 Subject: [PATCH 37/48] Correct fields place --- .../PostgreSql/NpgsqlIntervalMappingTest.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs index 76a5248a6..cd92e0a24 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs @@ -15,6 +15,9 @@ internal class NpgsqlIntervalMappingTest : SqlTest private const string ValueColumnName = "Value"; private const string TableName = "NpgsqlIntervalTest"; + private TypeMapping longMapping; + private TypeMapping timeSpanMapping; + #region Test case sources private static TimeSpan[] SecondsCases { @@ -199,10 +202,6 @@ private static TimeSpan[] MultipartValuesSource } #endregion - private TypeMapping longMapping; - private TypeMapping timeSpanMapping; - - protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); protected override void TestFixtureSetUp() @@ -313,10 +312,9 @@ private void InsertValue(long id, TimeSpan testCase) longMapping.BindValue(pId, id); _ = command.Parameters.Add(pId); - TimeSpan result = default; using (command) using (var reader = command.ExecuteReader()) { - while (reader.Read() || result == default) { + while (reader.Read()) { var idFromDb = (long) longMapping.ReadValue(reader, 0); var valueFromDb = (TimeSpan) timeSpanMapping.ReadValue(reader, 1); return (idFromDb, valueFromDb); From 26178419975bd7932140e5c636f0be8e6e5738eb Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 6 Mar 2025 11:03:36 +0500 Subject: [PATCH 38/48] Handle change of result type of Extract in Posgre for TotalMilliseconds --- .../Sql.Drivers.PostgreSql/v9_0/Compiler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs index a44527781..85437780b 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs @@ -17,8 +17,7 @@ protected override void VisitIntervalToMilliseconds(SqlFunctionCall node) AppendSpaceIfNecessary(); _ = context.Output.Append("(TRUNC(EXTRACT(EPOCH FROM ("); node.Arguments[0].AcceptVisitor(this); - _ = context.Output.Append(")) * 1000))"); - + _ = context.Output.Append("))::double precision * 1000))"); } public Compiler(PostgreSql.Driver driver) From 540a9c5b5ebc6ebf3870bd2fb7fb83ff9ada7bf0 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 6 Mar 2025 11:40:56 +0500 Subject: [PATCH 39/48] Optimize test values --- .../PostgreSql/IntervalToMillisecondsTest.cs | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs index 7a9d84d83..83a6050ce 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs @@ -26,32 +26,23 @@ private static TimeSpan[] TestValues get => new[] { TimeSpan.MinValue, TimeSpan.MaxValue, + TimeSpan.FromTicks(2519506068549999999), + TimeSpan.FromTicks(2519506068549999999).Negate(), + TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)), TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)), - TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)), - TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)), - TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)), - TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)), TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)), TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)), TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))), TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))), - TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))), - TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))), TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))), TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))), - TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))), - TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))), - TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))), - TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))), - TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))), TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))), TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))), TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), - TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))), @@ -60,35 +51,23 @@ private static TimeSpan[] TestValues TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)).Negate(), TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)).Negate(), - TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)).Negate(), - TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)).Negate(), - TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)).Negate(), - TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)).Negate(), TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)).Negate(), TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)).Negate(), TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))).Negate(), TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))).Negate(), - TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))).Negate(), - TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))).Negate(), TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))).Negate(), TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(), - TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(), - TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(), - TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(), - TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(), - TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(), TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(), TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(), TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), - TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), - TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate() + TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(), }; } From b9a1654e1c2ec587938c1e1f3028c25ca6c3b240 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 21 Apr 2025 17:34:47 +0500 Subject: [PATCH 40/48] PostgreSqlHelper: Change getting timezone --- .../PostgreSqlHelper.cs | 85 ++++++++-- .../PostgreSql/PostgreSqlHelperTest.cs | 160 +++++++++++++----- 2 files changed, 188 insertions(+), 57 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs index 9deb1d534..8a58f739f 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs @@ -3,8 +3,8 @@ // See the License.txt file in the project root for more information. using System; -using System.Collections.Generic; -using Npgsql; +using System.Globalization; +using System.Text.RegularExpressions; using NpgsqlTypes; using Xtensive.Orm.PostgreSql; @@ -52,20 +52,81 @@ internal static TimeSpan ResurrectTimeSpanFromNpgsqlInterval(in NpgsqlInterval n } /// - /// Checks if timezone is declared in POSIX format (example <+07>-07 ) - /// and returns number between '<' and '>' as timezone. + /// Gets system time zone info for server time zone, if such zone exists. /// - /// Timezone in possible POSIX format - /// Timezone shift declared in oritinal POSIX format as timezone or original value. - internal static string TryGetZoneFromPosix(string timezone) + /// Time zone from connection + /// Instance of if such found, or . + /// Server timezone offset can't be recognized. + public static TimeZoneInfo GetTimeZoneInfoForServerTimeZone(string connectionTimezone) { - if (timezone.StartsWith('<')) { - // if POSIX format - var closing = timezone.IndexOf('>'); - var result = timezone.Substring(1, closing - 1); + if (string.IsNullOrEmpty(connectionTimezone)) { + return null; + } + + // Try to get zone as is, conversion from IANA format identifier + // happens inside the TimeZoneInfo.FindSystemTimeZoneById(). + // Postgres uses IANA timezone format identifier, not windows one. + if (TryFindSystemTimeZoneById(connectionTimezone, out var result)) { return result; } - return timezone; + + // If zone was set as certain offset, then it will be returned to us in form of + // POSIX offset, e.g. '<+03>-03' for UTC+03 or '<+1030>-1030' for UTC+10:30 + if (Regex.IsMatch(connectionTimezone, "^<[+|-]\\d{2,4}>[-|+]\\d{2,4}$")) { + var closingBracketIndex = connectionTimezone.IndexOf('>'); + var utcOffset = connectionTimezone.Substring(1, closingBracketIndex - 1); + + var utcOffsetString = utcOffset.Length switch { + 3 => utcOffset, + 5 => utcOffset.Insert(3, ":"), + _ => string.Empty + }; + + //Here, we rely on server validation of zone existance for the offset required by user + + var utcIdentifier = $"UTC{utcOffsetString}"; + + if (utcIdentifier.Length == 3) + throw new ArgumentException($"Server connection time zone '{connectionTimezone}' cannot be recongized."); + + if (TryFindSystemTimeZoneById(utcIdentifier, out var utcTimeZone)) { + return utcTimeZone; + } + else { + var parsingCulture = CultureInfo.InvariantCulture; + TimeSpan baseOffset; + if (utcOffsetString.StartsWith("-")) { + if (!TimeSpan.TryParseExact(utcOffsetString, "\\-hh\\:mm", parsingCulture, TimeSpanStyles.AssumeNegative, out baseOffset)) + if(!TimeSpan.TryParseExact(utcOffsetString, "\\-hh", parsingCulture, TimeSpanStyles.AssumeNegative, out baseOffset)) + throw new ArgumentException($"Server connection time zone '{connectionTimezone}' cannot be recongized."); + } + else { + if (!TimeSpan.TryParseExact(utcOffsetString, "\\+hh\\:mm", parsingCulture, TimeSpanStyles.None, out baseOffset)) + if (!TimeSpan.TryParseExact(utcOffsetString, "\\+hh", parsingCulture, TimeSpanStyles.None, out baseOffset)) + throw new ArgumentException($"Server connection time zone '{connectionTimezone}' cannot be recongized."); + } + + return TimeZoneInfo.CreateCustomTimeZone(utcIdentifier, baseOffset, "Coordinated Universal Time" + utcOffsetString, utcIdentifier); + } + } + + return null; + } + + private static bool TryFindSystemTimeZoneById(string id, out TimeZoneInfo timeZoneInfo) + { +#if NET8_0_OR_GREATER + return TimeZoneInfo.TryFindSystemTimeZoneById(id, out timeZoneInfo); +#else + try { + timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(id); + return true; + } + catch { + timeZoneInfo = null; + return false; + } +#endif } } } diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs index 0398a68b4..834398b0d 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs @@ -1,18 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; -using Npgsql; using NUnit.Framework; using Xtensive.Sql.Drivers.PostgreSql; +using PostgreSqlDriver = Xtensive.Sql.Drivers.PostgreSql.Driver; namespace Xtensive.Orm.Tests.Sql.PostgreSql { [TestFixture] public sealed class PostgreSqlHelperTest : SqlTest { - private IReadOnlyDictionary serverTimeZones; - - private string[] testTimezones; + private string[] timezoneIdsWithWinAnalogue; + private string[] timezoneIdsWithoutWinAnalogue; public static TimeSpan[] Intervals { @@ -44,61 +43,73 @@ public static TimeSpan[] Intervals }; } - protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); - - protected override void TestFixtureSetUp() + public static string[] PosixOffsetFormatValues { - base.TestFixtureSetUp(); - var nativeDriver = (Xtensive.Sql.Drivers.PostgreSql.Driver) Driver; - - serverTimeZones = nativeDriver.PostgreServerInfo.ServerTimeZones; - testTimezones = serverTimeZones.Keys.Union(new[] { + get => new[] { "<+02>-02", - "<+2>-2", "<+05>-05", - "<+5>-5", "<+07>-07", - "<+7>-7", "<-02>+02", - "<-2>+2", "<-05>+05", - "<-5>+5", "<-07>+07", - "<-7>+7" - } ).ToArray(); + "<-0730>+0730" + }; + } + + public static string[] PseudoPosixOffsetFormatValues + { + get => new[] { + "<+2>-2", + "<+5>-5", + "<+7>-7", + "<-2>+2", + "<-5>+5", + "<-7>+7", + "not-ulalala" + }; + } + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + + LoadServerTimeZones(Connection, out timezoneIdsWithWinAnalogue, out timezoneIdsWithoutWinAnalogue); Connection.Close(); } [Test] - public void TimeZoneRecognitionTest() + [TestCaseSource(nameof(PosixOffsetFormatValues))] + public void PosixOffsetRecognitionTest(string offset) { - foreach(var timezone in testTimezones) { - - if (timezone.StartsWith('<')) { - if (timezone.Contains('0')) { - - - Assert.That(serverTimeZones.TryGetValue(timezone, out var result1), Is.False); - Assert.That(serverTimeZones.TryGetValue(PostgreSqlHelper.TryGetZoneFromPosix(timezone), out var result2), Is.True); - Assert.That(result2, Is.EqualTo(TimeSpan.FromHours(2)) - .Or.EqualTo(TimeSpan.FromHours(5)) - .Or.EqualTo(TimeSpan.FromHours(7)) - .Or.EqualTo(TimeSpan.FromHours(-2)) - .Or.EqualTo(TimeSpan.FromHours(-5)) - .Or.EqualTo(TimeSpan.FromHours(-7))); - } - else { - Assert.That(serverTimeZones.TryGetValue(timezone, out var result1), Is.False); - Assert.That(serverTimeZones.TryGetValue(PostgreSqlHelper.TryGetZoneFromPosix(timezone), out var result2), Is.False); - } + var systemTimezone = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(offset); + Assert.That(systemTimezone, Is.Not.Null); + Assert.That(systemTimezone.Id.Contains("UTC")); + } - } - else { - Assert.That(serverTimeZones.TryGetValue(timezone, out var result1), Is.True); - Assert.That(serverTimeZones.TryGetValue(PostgreSqlHelper.TryGetZoneFromPosix(timezone), out var result2), Is.True); - Assert.That(result1, Is.EqualTo(result2)); - } + [Test] + [TestCaseSource(nameof(PseudoPosixOffsetFormatValues))] + public void PseudoPosixOffsetRecognitionTest(string offset) + { + var systemTimezone = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(offset); + Assert.That(systemTimezone, Is.Null); + } + + [Test] + public void ResolvableTimeZonesTest() + { + foreach (var tz in timezoneIdsWithWinAnalogue) { + Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Not.Null); + } + } + + [Test] + public void UnresolvableTimeZonesTest() + { + foreach(var tz in timezoneIdsWithoutWinAnalogue) { + Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Null); } } @@ -110,5 +121,64 @@ public void TimeSpanToIntervalConversionTest(TimeSpan testValue) var backToTimeSpan = PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(nativeInterval); Assert.That(backToTimeSpan, Is.EqualTo(testValue)); } + + + private static void LoadServerTimeZones(Xtensive.Sql.SqlConnection connection, + out string[] timezoneIdsWithWinAnalogue, + out string[] timezoneIdsWithoutWinAnalogue) + { + var timezoneIdsWithWinAnalogueList = new List(); + var timezoneIdsWithoutWinAnalogueList = new List(); + + var existing = new HashSet(); + var serverTimeZoneAbbrevs = new HashSet(); + using (var command = connection.CreateCommand("SELECT \"name\", \"abbrev\" FROM pg_catalog.pg_timezone_names")) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var name = reader.GetString(0); + var abbrev = reader.GetString(1); + + if (name.Equals("ZULU", StringComparison.OrdinalIgnoreCase) + || abbrev.Equals("ZULU", StringComparison.OrdinalIgnoreCase)) + continue; + + if (TimeZoneInfo.TryConvertIanaIdToWindowsId(name, out var winAnalogue)) + timezoneIdsWithWinAnalogueList.Add(name); + else + timezoneIdsWithoutWinAnalogueList.Add(name); + + if (abbrev[0] != '-' && abbrev[0] != '+' && existing.Add(abbrev)) { + if (TimeZoneInfo.TryConvertIanaIdToWindowsId(abbrev, out var winAnalogue1)) + timezoneIdsWithWinAnalogueList.Add(abbrev); + else + timezoneIdsWithoutWinAnalogueList.Add(abbrev); + } + } + } + + using (var command = connection.CreateCommand("SELECT \"abbrev\" FROM pg_catalog.pg_timezone_abbrevs")) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var abbrev = reader.GetString(0); + + if (abbrev.Equals("ZULU", StringComparison.OrdinalIgnoreCase) || !existing.Add(abbrev)) + continue; + + if (TimeZoneInfo.TryConvertIanaIdToWindowsId(abbrev, out var winAnalogue)) + timezoneIdsWithWinAnalogueList.Add(abbrev); + else + timezoneIdsWithoutWinAnalogueList.Add(abbrev); + + if (abbrev[0] != '-' && abbrev[0] != '+' && existing.Add(abbrev)) { + if (TimeZoneInfo.TryConvertIanaIdToWindowsId(abbrev, out var winAnalogue1)) + timezoneIdsWithWinAnalogueList.Add(abbrev); + else + timezoneIdsWithoutWinAnalogueList.Add(abbrev); + } + } + } + timezoneIdsWithoutWinAnalogue = timezoneIdsWithoutWinAnalogueList.ToArray(); + timezoneIdsWithWinAnalogue = timezoneIdsWithWinAnalogueList.ToArray(); + } } } From a228af6e9cfda98b1198d9e9ab173da68e8de33f Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 21 Apr 2025 17:40:49 +0500 Subject: [PATCH 41/48] PostgreSql: TimeZoneInfo as DefaultTimeZone no loading data from server --- .../Sql.Drivers.PostgreSql/DriverFactory.cs | 53 +++---------------- .../PostgreServerInfo.cs | 12 ++--- .../Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs | 40 ++++++-------- 3 files changed, 29 insertions(+), 76 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs index e4f4932db..da9f3ca70 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs @@ -13,7 +13,6 @@ using Xtensive.Orm; using Xtensive.Sql.Info; using Xtensive.Sql.Drivers.PostgreSql.Resources; -using System.Collections.Generic; namespace Xtensive.Sql.Drivers.PostgreSql { @@ -67,9 +66,9 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf else OpenConnectionFast(connection, configuration, false).GetAwaiter().GetResult(); var version = GetVersion(configuration, connection); - var serverTimezones = GetServerTimeZones(connection, false).GetAwaiter().GetResult(); var defaultSchema = GetDefaultSchema(connection); - return CreateDriverInstance(connectionString, version, defaultSchema, serverTimezones, connection.Timezone); + var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone); + return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo); } /// @@ -87,9 +86,9 @@ protected override async Task CreateDriverAsync( else await OpenConnectionFast(connection, configuration, true, token).ConfigureAwait(false); var version = GetVersion(configuration, connection); - var serverTimezones = await GetServerTimeZones(connection, true, token).ConfigureAwait(false); var defaultSchema = await GetDefaultSchemaAsync(connection, token: token).ConfigureAwait(false); - return CreateDriverInstance(connectionString, version, defaultSchema, serverTimezones, connection.Timezone); + var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone); + return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo); } } @@ -103,7 +102,7 @@ private static Version GetVersion(SqlDriverConfiguration configuration, NpgsqlCo private static SqlDriver CreateDriverInstance( string connectionString, Version version, DefaultSchemaInfo defaultSchema, - Dictionary timezones, string defaultTimeZone) + TimeZoneInfo defaultTimeZone) { var coreServerInfo = new CoreServerInfo { ServerVersion = version, @@ -116,7 +115,6 @@ private static SqlDriver CreateDriverInstance( var pgsqlServerInfo = new PostgreServerInfo() { InfinityAliasForDatesEnabled = InfinityAliasForDatesEnabled, LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled, - ServerTimeZones = timezones, DefaultTimeZone = defaultTimeZone }; @@ -204,44 +202,7 @@ await SqlHelper.NotifyConnectionInitializingAsync(accessors, } } - private static async ValueTask> GetServerTimeZones(NpgsqlConnection connection, bool isAsync, CancellationToken token = default) - { - var resultZones = new Dictionary(); - - var command = connection.CreateCommand(); - command.CommandText = "SELECT \"name\", \"abbrev\", \"utc_offset\", \"is_dst\" FROM pg_timezone_names"; - if (isAsync) { - await using(command) - await using(var reader = await command.ExecuteReaderAsync()) { - while(await reader.ReadAsync()) { - ReadTimezoneRow(reader, resultZones); - } - } - } - else { - using (command) - using (var reader = command.ExecuteReader()) { - while (reader.Read()) { - ReadTimezoneRow(reader, resultZones); - } - } - } - return resultZones; - - - static void ReadTimezoneRow(NpgsqlDataReader reader, Dictionary zones) - { - var name = reader.GetString(0); - var abbrev = reader.GetString(1); - var utcOffset = PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(reader.GetFieldValue(2)); - - _ = zones.TryAdd(name, utcOffset); - //flatten results for search convinience - if (!string.IsNullOrEmpty(abbrev)) { - _ = zones.TryAdd(abbrev, utcOffset); - } - } - } + #region Helpers private static bool SetOrGetExistingDisableInfinityAliasForDatesSwitch(bool valueToSet) => GetSwitchValueOrSet(Orm.PostgreSql.WellKnown.DateTimeToInfinityConversionSwitchName, valueToSet); @@ -260,6 +221,8 @@ private static bool GetSwitchValueOrSet(string switchName, bool valueToSet) } } + #endregion + static DriverFactory() { // Starging from Npgsql 6.0 they broke compatibility by forcefully replacing diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs index 2e4cbc71f..d241ce8c0 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace Xtensive.Sql.Drivers.PostgreSql { @@ -27,13 +28,10 @@ internal sealed class PostgreServerInfo public bool LegacyTimestampBehavior { get; init; } /// - /// Contains server timezone names and their base Utc offset (including abbreviations). + /// Gets the to which values + /// will be converted on reading from database. + /// if no local equivalent of server time zone. /// - public IReadOnlyDictionary ServerTimeZones { get; init; } - - /// - /// Gets time zone of connection after connection initialization script was executed. - /// - public string DefaultTimeZone { get; init; } + public TimeZoneInfo DefaultTimeZone { get; init; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs index 90a0860f8..209511060 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs @@ -5,13 +5,11 @@ // Created: 2009.06.23 using System; -using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Security; using Npgsql; using NpgsqlTypes; -using Xtensive.Orm.PostgreSql; using Xtensive.Reflection.PostgreSql; @@ -27,7 +25,7 @@ internal class TypeMapper : Sql.TypeMapper private const long TimeSpanMaxValueAdjustedTicks = 9223372036854775800; protected readonly bool legacyTimestampBehaviorEnabled; - protected readonly TimeSpan? defaultTimeZone; + protected readonly TimeZoneInfo defaultTimeZone; public override bool IsParameterCastRequired(Type type) { @@ -158,7 +156,7 @@ public override void BindDateTime(DbParameter parameter, object value) // in Npgsql 6+, though both types have the same range of values and resolution. // // If no explicit type declared it seems to be identified by DateTime value's Kind, - // so now we have to unbox-box value to change kind of value because of this "talanted" person. + // so now we have to unbox-box value to change kind of value. nativeParameter.NpgsqlDbType = NpgsqlDbType.Timestamp; nativeParameter.Value = value is null ? DBNull.Value @@ -180,7 +178,6 @@ public override void BindDateTimeOffset(DbParameter parameter, object value) nativeParameter.NpgsqlDbType = NpgsqlDbType.TimestampTz; // Manual switch to universal time is required by Npgsql from now on, - // Npgsql team "untaught" the library to do it. nativeParameter.NpgsqlValue = value is null ? DBNull.Value : value is DateTimeOffset dateTimeOffset @@ -300,23 +297,22 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index) return value; } else { - // Here, we try to apply connection timezone to the values we read. - // To not get it from internal connection of DbDataReader - // we assume that time zone switch happens (if happens) - // in DomainConfiguration.ConnectionInitializationSql and - // we cache time zone of native connection after the script - // has been executed. - - return (defaultTimeZone.HasValue) - ? value.ToOffset(defaultTimeZone.Value) - : value.ToLocalTime(); + // Here, we try to apply connection time zone (if it was recongized on client-side) + // to the values we read. + // If any time zone change happens, we assume that DomainConfiguration.ConnectionInitializationSql + // is used to make such change and we cache time zone info on first connection in driver factory + // after initialization has happened. + // If connection time zone has not been recognized we transform value to local offset + // on the assumption that database server is usually in the same region. + return defaultTimeZone is not null + ? TimeZoneInfo.ConvertTime(value, defaultTimeZone) + : (object) value.ToLocalTime(); } } - internal protected ArgumentException ValueNotOfTypeError(string typeName) - { - return new ArgumentException($"Value is not of '{typeName}' type."); - } + internal protected ArgumentException ValueNotOfTypeError(string typeName) => + new($"Value is not of '{typeName}' type."); + // Constructors @@ -325,11 +321,7 @@ public TypeMapper(PostgreSql.Driver driver) { var postgreServerInfo = driver.PostgreServerInfo; legacyTimestampBehaviorEnabled = postgreServerInfo.LegacyTimestampBehavior; - defaultTimeZone = postgreServerInfo.ServerTimeZones.TryGetValue( - PostgreSqlHelper.TryGetZoneFromPosix(postgreServerInfo.DefaultTimeZone), out var offset) - ? offset - : null; - ; + defaultTimeZone = postgreServerInfo.DefaultTimeZone; } } } \ No newline at end of file From 30c151d268d1c8aacfd4ac0016d73404924b072d Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 22 Apr 2025 11:02:21 +0500 Subject: [PATCH 42/48] Output timezone when test failed --- Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs index 834398b0d..3561a22b6 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs @@ -101,7 +101,7 @@ public void PseudoPosixOffsetRecognitionTest(string offset) public void ResolvableTimeZonesTest() { foreach (var tz in timezoneIdsWithWinAnalogue) { - Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Not.Null); + Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Not.Null, tz); } } @@ -109,7 +109,7 @@ public void ResolvableTimeZonesTest() public void UnresolvableTimeZonesTest() { foreach(var tz in timezoneIdsWithoutWinAnalogue) { - Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Null); + Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Null, tz); } } From b2d70fc1e4f53567719dcf2fb39fe185914b2268 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 22 Apr 2025 12:17:29 +0500 Subject: [PATCH 43/48] PostgreSqlHelperTest : better split to resolvable and unresolvable zone ids --- .../PostgreSql/PostgreSqlHelperTest.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs index 3561a22b6..6426b9efa 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs @@ -138,17 +138,13 @@ private static void LoadServerTimeZones(Xtensive.Sql.SqlConnection connection, var name = reader.GetString(0); var abbrev = reader.GetString(1); - if (name.Equals("ZULU", StringComparison.OrdinalIgnoreCase) - || abbrev.Equals("ZULU", StringComparison.OrdinalIgnoreCase)) - continue; - - if (TimeZoneInfo.TryConvertIanaIdToWindowsId(name, out var winAnalogue)) + if (TryFindSystemTimeZoneById(name, out var winAnalogue)) timezoneIdsWithWinAnalogueList.Add(name); else timezoneIdsWithoutWinAnalogueList.Add(name); if (abbrev[0] != '-' && abbrev[0] != '+' && existing.Add(abbrev)) { - if (TimeZoneInfo.TryConvertIanaIdToWindowsId(abbrev, out var winAnalogue1)) + if (TryFindSystemTimeZoneById(abbrev, out var winAnalogue1)) timezoneIdsWithWinAnalogueList.Add(abbrev); else timezoneIdsWithoutWinAnalogueList.Add(abbrev); @@ -161,16 +157,13 @@ private static void LoadServerTimeZones(Xtensive.Sql.SqlConnection connection, while (reader.Read()) { var abbrev = reader.GetString(0); - if (abbrev.Equals("ZULU", StringComparison.OrdinalIgnoreCase) || !existing.Add(abbrev)) - continue; - - if (TimeZoneInfo.TryConvertIanaIdToWindowsId(abbrev, out var winAnalogue)) + if (TryFindSystemTimeZoneById(abbrev, out var winAnalogue)) timezoneIdsWithWinAnalogueList.Add(abbrev); else timezoneIdsWithoutWinAnalogueList.Add(abbrev); - if (abbrev[0] != '-' && abbrev[0] != '+' && existing.Add(abbrev)) { - if (TimeZoneInfo.TryConvertIanaIdToWindowsId(abbrev, out var winAnalogue1)) + if (existing.Add(abbrev)) { + if (TryFindSystemTimeZoneById(abbrev, out var winAnalogue1)) timezoneIdsWithWinAnalogueList.Add(abbrev); else timezoneIdsWithoutWinAnalogueList.Add(abbrev); @@ -180,5 +173,21 @@ private static void LoadServerTimeZones(Xtensive.Sql.SqlConnection connection, timezoneIdsWithoutWinAnalogue = timezoneIdsWithoutWinAnalogueList.ToArray(); timezoneIdsWithWinAnalogue = timezoneIdsWithWinAnalogueList.ToArray(); } + + private static bool TryFindSystemTimeZoneById(string id, out TimeZoneInfo timeZoneInfo) + { +#if NET8_0_OR_GREATER + return TimeZoneInfo.TryFindSystemTimeZoneById(id, out timeZoneInfo); +#else + try { + timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(id); + return true; + } + catch { + timeZoneInfo = null; + return false; + } +#endif + } } } From 80dd482391001ff3237c6fdf786c24644e571a67 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 22 Apr 2025 17:34:01 +0500 Subject: [PATCH 44/48] Improve changelog --- ChangeLog/7.2.0-Beta-2-dev.txt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ChangeLog/7.2.0-Beta-2-dev.txt b/ChangeLog/7.2.0-Beta-2-dev.txt index 29d4b11bc..cc38a0ed4 100644 --- a/ChangeLog/7.2.0-Beta-2-dev.txt +++ b/ChangeLog/7.2.0-Beta-2-dev.txt @@ -1,6 +1,23 @@ [main] Upgrade hints change names of constructors' string parameters for better understanding of what suppose to be in them. [main] Improved string operations Trim/TrimStart/TrimEnd support +[main] Updated BitFaster.Caching to version 2.5.3 +[firebird] Updated client library to version 10.3.2 [mysql] SqlDml.NullIf function now correctly translated [mysql] Improved support for string.PadLeft/PadRight opertaions +[mysql] Updated client library to version 8.4.0 +[postgresql] Updated client library to version 8.0.6 +[postgresql] Improved .Milliseconds part translation for types that have the part +[postgresql] Improved TimeSpan.TotalMilliseconds translation +[postgresql] AppContext switch "Npgsql.EnableLegacyTimestampBehavior" is turned off if user hasn't set it before Domain build +[postgresql] Both states of "Npgsql.EnableLegacyTimestampBehavior" AppContext switch are supported +[postgresql] AppContext switch "Npgsql.DisableDateTimeInfinityConversions" is turned on if user hasn't set it before Domain build +[postgresql] Supported both states of "Npgsql.DisableDateTimeInfinityConversions" AppContext switch, though "true" is recommended +[postgresql] When infinity conversions are enabled, extra statements will be applied to certain operations to return expected results +[postgresql] DateTime values '0001.01.01 00:00:00.00000' and '9999.12.31 23:59:59.99999' will be read as MinValue and MaxValue accordingly +[postgresql] DateTimeOffset values '0001.01.01 00:00:00.00000+00:00' and '9999.12.31 23:59:59.99999+00:00' will be read as MinValue and MaxValue accordingly +[postgresql] When legacy timestamp behavior is disabled, connection time zone is applied to DateTimeOffset values if possible, otherwise, to local one +[postgresql] TimeSpans based on values lower than -9223372036854775800L and higher 92233720368547758xxL will be read as MinValue and MaxValue accordingly +[oracle] Updated client library to version 23.7.0 [sqlite] Fixed string.Lenght translation -[sqlite] Added support for string.PadLeft/PadRight operations \ No newline at end of file +[sqlite] Added support for string.PadLeft/PadRight operations +[sqlserver] Updated client library to version 5.2.2 \ No newline at end of file From 770a7c73bbb2647c41cc12475c42ce4482fe0de8 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 23 Apr 2025 11:09:19 +0500 Subject: [PATCH 45/48] Fix error after merge --- .../Sql.Drivers.PostgreSql/v9_0/Compiler.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs index d5774e259..98bafafbf 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v9_0/Compiler.cs @@ -21,14 +21,6 @@ protected override void VisitIntervalToMilliseconds(SqlFunctionCall node) // Constructors - protected override void VisitIntervalToMilliseconds(SqlFunctionCall node) - { - AppendSpaceIfNecessary(); - _ = context.Output.Append("(TRUNC(EXTRACT(EPOCH FROM ("); - node.Arguments[0].AcceptVisitor(this); - _ = context.Output.Append("))::double precision * 1000))"); - } - public Compiler(PostgreSql.Driver driver) : base(driver) { From e6b0ee3e837f8ea74ca4d70d24a3a8821a9574d9 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 23 Apr 2025 16:06:57 +0500 Subject: [PATCH 46/48] Exclude not PostgreSql providers from using MinMaxXXXEntity Tests require PostgreSQL so model should not have them in model --- .../DateTimeBaseTest.cs | 14 ++++++++++---- .../DateTimeOffsetBaseTest.cs | 18 +++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs index 3abd46f81..41c175f56 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs @@ -45,11 +45,14 @@ protected override void RegisterTypes(DomainConfiguration configuration) configuration.Types.Register(typeof(DateTimeEntity)); configuration.Types.Register(typeof(MillisecondDateTimeEntity)); configuration.Types.Register(typeof(NullableDateTimeEntity)); - configuration.Types.Register(typeof(MinMaxDateTimeEntity)); + configuration.Types.Register(typeof(AllPossiblePartsEntity)); configuration.Types.Register(typeof(DateOnlyEntity)); configuration.Types.Register(typeof(SingleDateOnlyEntity)); - configuration.Types.Register(typeof(MinMaxDateOnlyEntity)); + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.PostgreSql)) { + configuration.Types.Register(typeof(MinMaxDateOnlyEntity)); + configuration.Types.Register(typeof(MinMaxDateTimeEntity)); + } configuration.Types.Register(typeof(TimeOnlyEntity)); configuration.Types.Register(typeof(SingleTimeOnlyEntity)); } @@ -201,8 +204,11 @@ protected override void PopulateEntities(Session session) _ = AllPossiblePartsEntity.FromDateTime(session, FirstMillisecondDateTime, 321); - _ = new MinMaxDateOnlyEntity(session) { MinValue = DateOnly.MinValue, MaxValue = DateOnly.MaxValue }; - _ = new MinMaxDateTimeEntity(session) { MinValue = DateTime.MinValue, MaxValue = DateTime.MaxValue }; + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.PostgreSql)) { + // values are out of range + _ = new MinMaxDateOnlyEntity(session) { MinValue = DateOnly.MinValue, MaxValue = DateOnly.MaxValue }; + _ = new MinMaxDateTimeEntity(session) { MinValue = DateTime.MinValue, MaxValue = DateTime.MaxValue }; + } } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs index 5db11fdd9..995850683 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs @@ -37,12 +37,14 @@ public abstract class DateTimeOffsetBaseTest : BaseTest protected override void RegisterTypes(DomainConfiguration configuration) { - configuration.Types.Register(typeof (SingleDateTimeOffsetEntity)); - configuration.Types.Register(typeof (DateTimeOffsetEntity)); - configuration.Types.Register(typeof (MillisecondDateTimeOffsetEntity)); - configuration.Types.Register(typeof (NullableDateTimeOffsetEntity)); - configuration.Types.Register(typeof (DateTimeEntity)); - configuration.Types.Register(typeof(MinMaxDateTimeOffsetEntity)); + configuration.Types.Register(typeof(SingleDateTimeOffsetEntity)); + configuration.Types.Register(typeof(DateTimeOffsetEntity)); + configuration.Types.Register(typeof(MillisecondDateTimeOffsetEntity)); + configuration.Types.Register(typeof(NullableDateTimeOffsetEntity)); + configuration.Types.Register(typeof(DateTimeEntity)); + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.PostgreSql)) { + configuration.Types.Register(typeof(MinMaxDateTimeOffsetEntity)); + } } protected override void InitializeCustomSettings(DomainConfiguration configuration) @@ -148,7 +150,9 @@ protected override void PopulateEntities(Session session) _ = new NullableDateTimeOffsetEntity { DateTimeOffset = null }; _ = new NullableDateTimeOffsetEntity { DateTimeOffset = null }; - _ = new MinMaxDateTimeOffsetEntity(session) { MinValue = DateTimeOffset.MinValue, MaxValue = DateTimeOffset.MaxValue }; + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.PostgreSql)) { + _ = new MinMaxDateTimeOffsetEntity(session) { MinValue = DateTimeOffset.MinValue, MaxValue = DateTimeOffset.MaxValue }; + } } protected DateTimeOffset TryMoveToLocalTimeZone(DateTimeOffset dateTimeOffset) From b9a912b0fe1a1169accf3c21a9a61c143c92d2e0 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 24 Apr 2025 11:23:11 +0500 Subject: [PATCH 47/48] Temporary revert SQLite client library change --- Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj b/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj index 04c3a2a39..0b9c065b5 100644 --- a/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj +++ b/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj @@ -25,7 +25,7 @@ - + From 1b70d5d2981e0ea6d0c8e036f2987a6a4e574068 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 24 Apr 2025 15:47:01 +0500 Subject: [PATCH 48/48] Revert SQLite client package downgrade --- Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj b/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj index 0b9c065b5..a1f4f229e 100644 --- a/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj +++ b/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj @@ -25,7 +25,7 @@ - +