diff --git a/ChangeLog/7.2.0-Beta-2-dev.txt b/ChangeLog/7.2.0-Beta-2-dev.txt index 54b78559b0..5cfe308968 100644 --- a/ChangeLog/7.2.0-Beta-2-dev.txt +++ b/ChangeLog/7.2.0-Beta-2-dev.txt @@ -4,7 +4,24 @@ [main] Obsolete SqlPersistTask constructors removed [main] Obsolete AggregateProvider constructor removed [main] Obsolete CalculateProvider constructor removed +[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 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 c78b2c42f6..f565618932 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 - + 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 466417e2a3..6a48ba38d4 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 - + diff --git a/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj b/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj index de71f4cda6..5ff93ec8f9 100644 --- a/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj +++ b/Orm/Xtensive.Orm.Firebird/Xtensive.Orm.Firebird.csproj @@ -25,7 +25,7 @@ - + diff --git a/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj b/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj index ec8c617459..7ce09d8176 100644 --- a/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj +++ b/Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj @@ -39,7 +39,7 @@ - + diff --git a/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj b/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj index f41e750d6a..4222822d6a 100644 --- a/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj +++ b/Orm/Xtensive.Orm.Oracle/Xtensive.Orm.Oracle.csproj @@ -25,7 +25,7 @@ - + diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs index 6d8e8cccf1..4c8d9441c6 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,6 +10,8 @@ using System.Data.Common; using System.Threading; using System.Threading.Tasks; +using System.Reflection; +using System; namespace Xtensive.Sql.Drivers.PostgreSql { @@ -50,14 +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 +69,11 @@ public override async Task CommitAsync(CancellationToken token = default) EnsureTransactionIsActive(); try { if (!IsTransactionCompleted()) { - await ActiveTransaction.CommitAsync(token).ConfigureAwait(false); + await activeTransaction.CommitAsync(token).ConfigureAwait(false); } } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwait(false); + await activeTransaction.DisposeAsync().ConfigureAwait(false); ClearActiveTransaction(); } } @@ -81,14 +82,13 @@ public override void Rollback() { EnsureIsNotDisposed(); EnsureTransactionIsActive(); - try { if (!IsTransactionCompleted()) { - ActiveTransaction.Rollback(); + activeTransaction.Rollback(); } } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -99,11 +99,11 @@ public override async Task RollbackAsync(CancellationToken token = default) EnsureTransactionIsActive(); try { if (!IsTransactionCompleted()) { - await ActiveTransaction.RollbackAsync(token).ConfigureAwait(false); + await activeTransaction.RollbackAsync(token).ConfigureAwait(false); } } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwait(false); + await activeTransaction.DisposeAsync().ConfigureAwait(false); ClearActiveTransaction(); } } @@ -169,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) @@ -179,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.IsCompleted; - } + private bool IsTransactionCompleted() => activeTransaction.Connection == null; // Constructors diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Driver.cs index 63d93ce217..1c132aeea5 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() { @@ -95,14 +100,18 @@ private SqlExceptionType ProcessClientSideException(NpgsqlException clientSideEx } } } + if (innerException is TimeoutException timeoutException) { + return SqlExceptionType.OperationTimeout; + } return SqlExceptionType.Unknown; } // 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 f0dbb07d50..da9f3ca708 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) @@ -44,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) { @@ -67,13 +67,18 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf OpenConnectionFast(connection, configuration, false).GetAwaiter().GetResult(); var version = GetVersion(configuration, connection); var defaultSchema = GetDefaultSchema(connection); - return CreateDriverInstance(connectionString, version, defaultSchema); + var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone); + return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo); } /// 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) @@ -82,7 +87,8 @@ protected override async Task CreateDriverAsync( await OpenConnectionFast(connection, configuration, true, token).ConfigureAwait(false); var version = GetVersion(configuration, connection); var defaultSchema = await GetDefaultSchemaAsync(connection, token: token).ConfigureAwait(false); - return CreateDriverInstance(connectionString, version, defaultSchema); + var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone); + return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo); } } @@ -95,7 +101,8 @@ private static Version GetVersion(SqlDriverConfiguration configuration, NpgsqlCo } private static SqlDriver CreateDriverInstance( - string connectionString, Version version, DefaultSchemaInfo defaultSchema) + string connectionString, Version version, DefaultSchemaInfo defaultSchema, + TimeZoneInfo defaultTimeZone) { var coreServerInfo = new CoreServerInfo { ServerVersion = version, @@ -105,6 +112,12 @@ private static SqlDriver CreateDriverInstance( DefaultSchemaName = defaultSchema.Schema, }; + var pgsqlServerInfo = new PostgreServerInfo() { + InfinityAliasForDatesEnabled = InfinityAliasForDatesEnabled, + LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled, + DefaultTimeZone = defaultTimeZone + }; + if (version.Major < 8 || (version.Major == 8 && version.Minor < 3)) { throw new NotSupportedException(Strings.ExPostgreSqlBelow83IsNotSupported); } @@ -112,13 +125,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 +201,58 @@ await SqlHelper.NotifyConnectionInitializingAsync(accessors, } } } + + #region Helpers + + 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; + } + } + + #endregion + + 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 0000000000..d241ce8c01 --- /dev/null +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs @@ -0,0 +1,37 @@ +// 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 System.Text.RegularExpressions; + +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; } + + /// + /// Gets the to which values + /// will be converted on reading from database. + /// if no local equivalent of server time zone. + /// + public TimeZoneInfo 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 0000000000..8a58f739f8 --- /dev/null +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlHelper.cs @@ -0,0 +1,132 @@ +// 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.Globalization; +using System.Text.RegularExpressions; +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); + } + + /// + /// Gets system time zone info for server time zone, if such zone exists. + /// + /// Time zone from connection + /// Instance of if such found, or . + /// Server timezone offset can't be recognized. + public static TimeZoneInfo GetTimeZoneInfoForServerTimeZone(string connectionTimezone) + { + 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; + } + + // 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.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlTypeMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreSqlTypeMapper.cs index e41eba8e68..4ba21476d0 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 aed1c3dee1..c4b1cb4e78 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 f76e02aba6..a36a1c94c6 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 9e1908f62f..f95e2dee3a 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 4506850493..f7a67bdc85 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 4070a0f628..a4eba0c905 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 a63a27cdaa..b3de1f9018 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 58aee378e0..d1dccc2b93 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) with time zone"); + + private static readonly SqlNative PositiveInfinity = SqlDml.Native("'Infinity'"); + private static readonly SqlNative NegativeInfinity = SqlDml.Native("'-Infinity'"); + + + protected readonly bool infinityAliasForDatesEnabled; /// public override void Visit(SqlDeclareCursor node) @@ -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); @@ -289,8 +306,13 @@ protected virtual void VisitIntervalToMilliseconds(SqlFunctionCall node) SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this); } - private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat) => - SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat); + private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat, bool infinityEnabled) + { + var operand = infinityEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.FunctionCall("TO_CHAR", operand, isoFormat); + } private static SqlExpression IntervalToIsoString(SqlExpression interval, bool signed) { @@ -386,7 +408,85 @@ public override void Visit(SqlExtract node) return; } } - base.Visit(node); + + 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); + } + + + (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"); + } + } + + 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 + ? PositiveInfinity + : null, + DateOnly dtValue => dtValue == DateOnly.MinValue + ? NegativeInfinity + : dtValue == DateOnly.MaxValue + ? PositiveInfinity + : null, + DateTimeOffset dtValue => dtValue == DateTimeOffset.MinValue + ? NegativeInfinity + : dtValue == DateTimeOffset.MaxValue + ? PositiveInfinity + : null, + _ => null + }; + + if (infinityExpression is null) { + base.Visit(node); + } + else { + infinityExpression.AcceptVisitor(this); + } + + } } protected virtual SqlExpression ConstructDateTime(IReadOnlyList arguments) @@ -453,17 +553,59 @@ 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 operand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return (operand + addPart); + } - 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 operand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.FunctionCall("date_trunc", "day", operand); + } + + protected SqlExpression DateAddXxx(SqlExpression date, SqlExpression addPart) + { + var operand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(date, DateMaxValue, DateMinValue) + : date; + return (operand + addPart); + } + + protected SqlExpression DateTimeOffsetExtractDate(SqlExpression timestamp) + { + var extractOperand = (infinityAliasForDatesEnabled) + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return SqlDml.FunctionCall("DATE", timestamp); + } + + protected SqlExpression DateTimeOffsetExtractDateTime(SqlExpression timestamp) + { + return DateTimeOffsetToDateTime(timestamp, infinityAliasForDatesEnabled); + } + + protected SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression timestamp) + { + var convertOperand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return GetDateTimeInTimeZone(convertOperand, TimeSpan.Zero); + } + + protected SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression timestamp) + { + var extractOperand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return SqlDml.Cast(extractOperand, SqlType.DateTime); + } protected void DateTimeOffsetExtractOffset(SqlExtract node) { @@ -471,7 +613,13 @@ protected void DateTimeOffsetExtractOffset(SqlExtract node) AppendTranslatedEntry(node); translator.Translate(context.Output, node.DateTimeOffsetPart); AppendTranslated(node, ExtractSection.From); - node.Operand.AcceptVisitor(this); + if (infinityAliasForDatesEnabled) { + CreateInfinityCheckExpression(node.Operand, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + .AcceptVisitor(this); + } + else { + node.Operand.AcceptVisitor(this); + } AppendSpace(); AppendTranslatedExit(node); AppendTranslated(SqlNodeType.Multiply); @@ -479,10 +627,23 @@ protected void DateTimeOffsetExtractOffset(SqlExtract node) } } - 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 == PositiveInfinity] = DateTimeOffsetSubstract(DateTimeOffsetMaxValue, SqlDml.DateTimeTruncate(DateTimeOffsetMaxValue)); + @case[timestamp == NegativeInfinity] = DateTimeOffsetSubstract(DateTimeOffsetMinValue, SqlDml.DateTimeTruncate(DateTimeOffsetMinValue)); + @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) { @@ -509,32 +670,83 @@ 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 convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.Cast(convertOperand, SqlType.DateTimeOffset); + } - private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) => - SqlDml.Cast(dateTimeOffset, SqlType.DateTime); + private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTimeOffset, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : dateTimeOffset; + return SqlDml.Cast(convertOperand, SqlType.DateTime); + } - private static SqlExpression DateTimeToDate(SqlExpression dateTime) => - SqlDml.Cast(dateTime, SqlType.Date); + private static SqlExpression DateTimeToDate(SqlExpression dateTime, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.Cast(convertOperand, SqlType.Date); + } - private static SqlExpression DateToDateTime(SqlExpression date) => - SqlDml.Cast(date, SqlType.DateTime); + private static SqlExpression DateToDateTime(SqlExpression date, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(date, DateMaxValue, DateMinValue) + : date; + return SqlDml.Cast(convertOperand, SqlType.DateTime); + } - private static SqlExpression DateTimeToTime(SqlExpression dateTime) => - SqlDml.Cast(dateTime, SqlType.Time); + private static SqlExpression DateTimeToTime(SqlExpression dateTime, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTime, DateTimeMaxValue, DateTimeMinValue) + : dateTime; + return SqlDml.Cast(convertOperand, SqlType.Time); + } private static SqlExpression TimeToDateTime(SqlExpression time) => SqlDml.Cast(EpochLiteral + time, SqlType.DateTime); - private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset) => - SqlDml.Cast(dateTimeOffset, SqlType.Date); + private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTimeOffset, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : dateTimeOffset; + return SqlDml.Cast(convertOperand, SqlType.Date); + } + + private static SqlExpression DateToDateTimeOffset(SqlExpression date, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(date, DateMaxValue, DateMinValue) + : date; + return SqlDml.Cast(convertOperand, SqlType.DateTimeOffset); + } - private static SqlExpression DateToDateTimeOffset(SqlExpression date) => - SqlDml.Cast(date, SqlType.DateTimeOffset); + private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset, bool infinityAliasEnabled) + { + var convertOperand = infinityAliasEnabled + ? CreateInfinityCheckExpression(dateTimeOffset, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : dateTimeOffset; + return SqlDml.Cast(convertOperand, SqlType.Time); + } - private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset) => - SqlDml.Cast(dateTimeOffset, 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) => SqlDml.Cast(EpochLiteral + time, SqlType.DateTimeOffset); @@ -574,9 +786,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 bf1e7874fd..22474106ec 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/PathMapper.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/PathMapper.cs index 0ae1f821e4..cbd05a1359 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 6b25c19770..5b89426c5c 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.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Translator.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Translator.cs index bd3d92a46a..76139d1516 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. 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 4e419085d3..209511060e 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 @@ -17,6 +17,16 @@ namespace Xtensive.Sql.Drivers.PostgreSql.v8_0 { internal class TypeMapper : Sql.TypeMapper { + // 6 fractions instead of .NET's 7 + 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 TimeZoneInfo defaultTimeZone; + public override bool IsParameterCastRequired(Type type) { switch (Type.GetTypeCode(type)) { @@ -113,9 +123,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) PostgreSqlHelper.CreateNativeIntervalFromTimeSpan(timeSpanValue) + : throw ValueNotOfTypeError(nameof(WellKnownTypes.TimeSpanType)); } public override void BindGuid(DbParameter parameter, object value) @@ -124,12 +136,54 @@ 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) + { + 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. + 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, + 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,28 +235,93 @@ 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 requires us to use raw type + // and construct timespan from its' values. + 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] public override object ReadDecimal(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; 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); + if (value == DateTime.MinValue || value == DateTime.MaxValue) + return value; + if (value.Ticks == DateTimeMaxValueAdjustedTicks) { + // 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. + return DateTime.MaxValue; + } + return value; + } + [SecuritySafeCritical] public override object ReadDateTimeOffset(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; var value = nativeReader.GetFieldValue(index); - return value; + 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. + return DateTimeOffset.MaxValue; + } + if (value == DateTimeOffset.MaxValue || value == DateTimeOffset.MaxValue) + return value; + + if (legacyTimestampBehaviorEnabled) { + // Npgsql 4 or older behavior + return value; + } + else { + // 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) => + new($"Value is not of '{typeName}' type."); + + // Constructors - public TypeMapper(SqlDriver driver) + public TypeMapper(PostgreSql.Driver driver) : base(driver) { + var postgreServerInfo = driver.PostgreServerInfo; + legacyTimestampBehaviorEnabled = postgreServerInfo.LegacyTimestampBehavior; + defaultTimeZone = postgreServerInfo.DefaultTimeZone; } } } \ 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 8f3e38f7e9..4cd2a68e3e 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 082752b95f..1887fbf660 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 9be3f94728..4e6dfad0e3 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 68496eca67..c925071f6a 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 ba36f2e06f..4a032484df 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 987922dc29..8f42593ffe 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 f5c3cabe09..d5bd3c3c16 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 603d96536e..b6dbb46013 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 41c200e1bb..b95f6113ba 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 34a39b8047..d68d0e2e4f 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 40960e64f1..7a71a3db51 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 2a4ca8a50d..7e8b1fb302 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 b9c86e55d4..98bafafbf8 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 @@ -21,7 +21,7 @@ protected override void VisitIntervalToMilliseconds(SqlFunctionCall node) // 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 d4137dff21..793870b1b5 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 72b4c5cb22..bb10c1bf5d 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 231cbb4cb5..6496dc6bf0 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 44dd8af0d5..4ef1608357 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 bb69696f06..5458dbe3fe 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 0000000000..97e4751c57 --- /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 baa140bed5..bc3d4e4de4 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); diff --git a/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj b/Orm/Xtensive.Orm.PostgreSql/Xtensive.Orm.PostgreSql.csproj index f15fe868b2..03fd921a65 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.SqlServer/Sql.Drivers.SqlServer/Connection.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs index c05853c942..e2ffa278fc 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 diff --git a/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj b/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj index 0f159cd162..e3305af605 100644 --- a/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj +++ b/Orm/Xtensive.Orm.SqlServer/Xtensive.Orm.SqlServer.csproj @@ -25,7 +25,7 @@ - + diff --git a/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj b/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj index 04c3a2a39c..a1f4f229e8 100644 --- a/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj +++ b/Orm/Xtensive.Orm.Sqlite/Xtensive.Orm.Sqlite.csproj @@ -25,7 +25,7 @@ - + diff --git a/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs b/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs index 835f0088d5..227e05126e 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 switch1Value)) { + AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", !switch1Value); + } + var legacyTimestampsValue = GetEnvironmentVariable(LegacyTimestapmKey); + if (bool.TryParse(legacyTimestampsValue, out var switch2Value)) { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", switch2Value); + } + } + private string GetEnvironmentVariable(string key) { return new[] {EnvironmentVariableTarget.Process, EnvironmentVariableTarget.User, EnvironmentVariableTarget.Machine} diff --git a/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs b/Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs index e53e5398c8..c9f0494b46 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.Sql/GlobalTestsSetup.cs b/Orm/Xtensive.Orm.Tests.Sql/GlobalTestsSetup.cs new file mode 100644 index 0000000000..2595d06cde --- /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.Sql/PostgreSql/InfinityAliasTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs new file mode 100644 index 0000000000..a3a3217199 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/InfinityAliasTest.cs @@ -0,0 +1,917 @@ +// 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 NUnit.Framework; +using Xtensive.Sql; +using Xtensive.Sql.Dml; + +namespace Xtensive.Orm.Tests.Sql.PostgreSql +{ + [TestFixture] + 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 readonly Dictionary templates = new(); + + private TypeMapping longTypeMapping; + private TypeMapping dateOnlyTypeMapping; + private TypeMapping dateTimeTypeMapping; + private TypeMapping dateTimeOffsetTypeMapping; + + protected override void CheckRequirements() + { + Require.ProviderIs(StorageProvider.PostgreSql); + } + + 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)]; + dateTimeOffsetTypeMapping = Driver.TypeMappings[typeof(DateTimeOffset)]; + + DropTablesForDateTime(); + DropTablesForDateOnly(); + DropTablesForDateTimeOffset(); + + 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 DateTimeMinSelectNoFilterTest() + { + 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)); + } + } + + 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 DateTimeMinSelectByEqualityTest() + { + 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] + public void DateTimeMinSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMinDateTimeSelectDatePart(true); + } + + [Test] + public void DateTimeMinSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMinDateTimeSelectDatePart(false); + } + + private void TestMinDateTimeSelectDatePart(bool isOn) + { + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Year, + DateTime.MinValue.Year, DateTime.MinValue.Year, isOn); + + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Month, + DateTime.MinValue.Month, DateTime.MinValue.Month, isOn); + + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Day, + DateTime.MinValue.Day, DateTime.MinValue.Day, isOn); + + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Hour, + DateTime.MinValue.Hour, DateTime.MinValue.Hour, isOn); + + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Minute, + DateTime.MinValue.Minute, DateTime.MinValue.Minute, isOn); + + TestDateTimePartExtraction(DateTimeMinValueTable, SqlDateTimePart.Second, + DateTime.MinValue.Second, DateTime.MinValue.Second, isOn); + } + + + [Test] + public void DateTimeMaxSelectNoFilterTest() + { + 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))); + } + } + + 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 DateTimeMaxSelectByEqualityTest() + { + 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] + public void DateTimeMaxSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMaxDateTimeSelectDatePart(true); + } + + [Test] + public void DateTimeMaxSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMaxDateTimeSelectDatePart(false); + } + + private void TestMaxDateTimeSelectDatePart(bool isOn) + { + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Year, + DateTime.MaxValue.Year, DateTime.MaxValue.Year, isOn); + + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Month, + DateTime.MaxValue.Month, DateTime.MaxValue.Month, isOn); + + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Day, + DateTime.MaxValue.Day, DateTime.MaxValue.Day, isOn); + + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Hour, + DateTime.MaxValue.Hour, DateTime.MaxValue.Hour, isOn); + + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Minute, + DateTime.MaxValue.Minute, DateTime.MaxValue.Minute, isOn); + + TestDateTimePartExtraction(DateTimeMaxValueTable, SqlDateTimePart.Second, + DateTime.MaxValue.Second, DateTime.MaxValue.Second, isOn); + } + + + [Test] + public void DateOnlyMinNoFilterTest() + { + 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)); + } + } + + 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 DateOnlyMinByEqualityTest() + { + 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] + public void DateOnlyMinSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMinDateOnlySelectDatePart(true); + } + + [Test] + public void DateOnlyMinSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMinDateOnlySelectDatePart(false); + } + + private void TestMinDateOnlySelectDatePart(bool isOn) + { + TestDatePartExtraction(DateOnlyMinValueTable, SqlDatePart.Year, + DateOnly.MinValue.Year, DateOnly.MinValue.Year, isOn); + + TestDatePartExtraction(DateOnlyMinValueTable, SqlDatePart.Month, + DateOnly.MinValue.Month, DateOnly.MinValue.Month, isOn); + + TestDatePartExtraction(DateOnlyMinValueTable, SqlDatePart.Day, + DateOnly.MinValue.Day, DateOnly.MinValue.Day, isOn); + } + + + [Test] + public void DateOnlyMaxNoFilterTest() + { + 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 = reader.GetFieldValue(1); + Assert.That(datetimeValue, Is.EqualTo(DateOnly.MaxValue)); + } + } + + var select = templates[DateOnlyMaxValueTable].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 DateOnlyMaxByEqualityTest() + { + 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] + public void DateOnlyMaxSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMaxDateOnlySelectDatePart(true); + } + + [Test] + public void DateOnlyMaxSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMaxDateOnlySelectDatePart(false); + } + + private void TestMaxDateOnlySelectDatePart(bool isOn) + { + TestDatePartExtraction(DateOnlyMaxValueTable, SqlDatePart.Year, + DateOnly.MaxValue.Year, DateOnly.MaxValue.Year, isOn); + + TestDatePartExtraction(DateOnlyMaxValueTable, SqlDatePart.Month, + DateOnly.MaxValue.Month, DateOnly.MaxValue.Month, isOn); + + TestDatePartExtraction(DateOnlyMaxValueTable, SqlDatePart.Day, + DateOnly.MaxValue.Day, DateOnly.MaxValue.Day, isOn); + } + + + [Test] + public void DateTimeOffsetMinSelectNoFilterTest() + { + 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 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 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); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + public void DateTimeOffsetMinSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMinDateTimeOffsetSelectDatePart(true); + } + + [Test] + public void DateTimeOffsetMinSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMinDateTimeOffsetSelectDatePart(false); + } + + private void TestMinDateTimeOffsetSelectDatePart(bool 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); + } + + + [Test] + public void DateTimeOffsetMaxSelectNoFilterTest() + { + 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 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 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); + + using (command) { + var count = (long) command.ExecuteScalar(); + Assert.That(count, Is.GreaterThan(0)); + } + } + + [Test] + public void DateTimeOffsetMaxSelectDatePartInfinityTest() + { + CheckIfInfinityAliasTurnedOn(); + + TestMaxDateTimeOffsetSelectDatePart(true); + } + + [Test] + public void DateTimeOffsetMaxSelectDatePartDateTest() + { + CheckIfInfinityAliasTurnedOff(); + + TestMaxDateTimeOffsetSelectDatePart(false); + } + + private void TestMaxDateTimeOffsetSelectDatePart(bool isOn) + { + // 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); + + // 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); + + // 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); + } + + private void TestDatePartExtraction(string table, SqlDatePart part, int expectedValueNative, int expectedValueDml, bool aliasesEnabled) + { + var template = templates[table]; + + var command = Connection.CreateCommand($"SELECT EXTRACT ({part.ToString().ToUpperInvariant()} FROM \"Value\") FROM public.\"{table}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + 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); + } + } + } + + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(part, 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, expectedValueDml, aliasesEnabled); + } + } + } + + private void TestDateTimePartExtraction(string table, SqlDateTimePart part, int expectedValueNative, int expectedValueDml, bool aliasesEnabled) + { + var template = templates[table]; + + var command = Connection.CreateCommand($"SELECT EXTRACT ({part.ToString().ToUpperInvariant()} FROM \"Value\") FROM public.\"{table}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + 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); + } + } + } + + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(part, 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, expectedValueDml, aliasesEnabled); + } + } + } + + private void TestDateTimeOffsetPartExtraction(string table, SqlDateTimeOffsetPart part, int expectedValueNative, int expectedValueDml, bool aliasesEnabled) + { + var template = templates[table]; + + var command = Connection.CreateCommand($"SELECT EXTRACT ({part.ToString().ToUpperInvariant()} FROM \"Value\") FROM public.\"{table}\""); + using (command) + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + 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); + } + } + } + + var select = template.Clone(new SqlNodeCloneContext()); + select.Columns.Add(SqlDml.Extract(part, 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, expectedValueDml, aliasesEnabled); + } + } + } + + + 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() + { + 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/IntervalToMillisecondsTest.cs b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs index 7a9d84d834..83a6050ce8 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(), }; } 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 0000000000..77fd4454b2 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeOffsetParameterBinding.cs @@ -0,0 +1,166 @@ +// 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() + { + Require.ProviderIs(StorageProvider.PostgreSql); + } + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + + longTypeMapping = Driver.TypeMappings[typeof(long)]; + dateTimeOffsetTypeMapping = Driver.TypeMappings[typeof(DateTimeOffset)]; + + DropTablesForTests(); + + CreateTablesForTests(); + } + + [Test] + 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] + 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] + 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] + 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 0000000000..b5570bf8f5 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/LegacyVsCurrentDateTimeParameterBinding.cs @@ -0,0 +1,216 @@ +// 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() + { + Require.ProviderIs(StorageProvider.PostgreSql); + } + + protected override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + + longTypeMapping = Driver.TypeMappings[typeof(long)]; + dateTimeTypeMapping = Driver.TypeMappings[typeof(DateTime)]; + + DropTablesForTests(); + + CreateTablesForTests(); + } + + [Test] + 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] + 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] + 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] + 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] + 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] + 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 0000000000..cd92e0a24f --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/NpgsqlIntervalMappingTest.cs @@ -0,0 +1,327 @@ +// 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"; + + private TypeMapping longMapping; + private TypeMapping timeSpanMapping; + + #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 + + 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); + + using (command) + using (var reader = command.ExecuteReader()) { + while (reader.Read()) { + var idFromDb = (long) longMapping.ReadValue(reader, 0); + var valueFromDb = (TimeSpan) timeSpanMapping.ReadValue(reader, 1); + return (idFromDb, valueFromDb); + } + } + + return default; + } + } +} 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 0000000000..6426b9efab --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Sql/PostgreSql/PostgreSqlHelperTest.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 string[] timezoneIdsWithWinAnalogue; + private string[] timezoneIdsWithoutWinAnalogue; + + 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) + }; + } + + public static string[] PosixOffsetFormatValues + { + get => new[] { + "<+02>-02", + "<+05>-05", + "<+07>-07", + "<-02>+02", + "<-05>+05", + "<-07>+07", + "<-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] + [TestCaseSource(nameof(PosixOffsetFormatValues))] + public void PosixOffsetRecognitionTest(string offset) + { + var systemTimezone = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(offset); + Assert.That(systemTimezone, Is.Not.Null); + Assert.That(systemTimezone.Id.Contains("UTC")); + } + + [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, tz); + } + } + + [Test] + public void UnresolvableTimeZonesTest() + { + foreach(var tz in timezoneIdsWithoutWinAnalogue) { + Assert.That(PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(tz), Is.Null, tz); + } + } + + [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)); + } + + + 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 (TryFindSystemTimeZoneById(name, out var winAnalogue)) + timezoneIdsWithWinAnalogueList.Add(name); + else + timezoneIdsWithoutWinAnalogueList.Add(name); + + if (abbrev[0] != '-' && abbrev[0] != '+' && existing.Add(abbrev)) { + if (TryFindSystemTimeZoneById(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 (TryFindSystemTimeZoneById(abbrev, out var winAnalogue)) + timezoneIdsWithWinAnalogueList.Add(abbrev); + else + timezoneIdsWithoutWinAnalogueList.Add(abbrev); + + if (existing.Add(abbrev)) { + if (TryFindSystemTimeZoneById(abbrev, out var winAnalogue1)) + timezoneIdsWithWinAnalogueList.Add(abbrev); + else + timezoneIdsWithoutWinAnalogueList.Add(abbrev); + } + } + } + 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 + } + } +} diff --git a/Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs b/Orm/Xtensive.Orm.Tests/GlobalTestsSetup.cs new file mode 100644 index 0000000000..47a9a58310 --- /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 diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs index 8f749ea4b4..1afe0fa970 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateOnly/DateOnlyToStringTest.cs @@ -1,7 +1,8 @@ -// 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. +using System; using NUnit.Framework; using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; @@ -17,5 +18,15 @@ public void ToStringTest() RunWrongTest(s, c => c.DateOnly.ToString("o") == FirstDateOnly.AddDays(1).ToString("o")); }); } + + [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 cb732ef832..72c3bf12b4 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. @@ -22,6 +22,19 @@ 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)); + + 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 +47,19 @@ 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)); + + 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 +71,18 @@ public void AddDaysTest() RunWrongTest(s, c => c.NullableDateOnly.Value.AddDays(33) == NullableDateOnly.AddDays(44)); }); } + + [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)); + + 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 5703e2a9b9..5a84218b68 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. @@ -22,6 +22,20 @@ 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); + + RunWrongTest(s, c => c.MinValue.Year == WrongDateOnly.Year); + RunWrongTest(s, c => c.MaxValue.Year == WrongDateOnly.Year); + }); + } + [Test] public void ExtractMonthTest() { @@ -41,6 +55,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.Month == WrongDateOnly.Month); + RunWrongTest(s, c => c.MaxValue.Month == WrongDateOnly.Month); + }); + } + [Test] public void ExtractDayTest() { @@ -53,6 +80,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.Day == WrongDateOnly.Day); + RunWrongTest(s, c => c.MaxValue.Day == WrongDateOnly.Day); + }); + } + [Test] public void ExtractDayOfYearTest() { @@ -65,9 +105,23 @@ 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); + + RunWrongTest(s, c => c.MinValue.DayOfYear == WrongDateOnly.DayOfYear); + RunWrongTest(s, c => c.MaxValue.DayOfYear == WrongDateOnly.DayOfYear); + }); + } + [Test] public void ExtractDayOfWeekTest() { + ExecuteInsideSession((s) => { RunTest(s, c => c.DateOnly.DayOfWeek == FirstDateOnly.DayOfWeek); RunTest(s, c => c.NullableDateOnly.Value.DayOfWeek == NullableDateOnly.DayOfWeek); @@ -76,5 +130,18 @@ public void ExtractDayOfWeekTest() RunWrongTest(s, c => c.NullableDateOnly.Value.DayOfWeek == WrongDateOnly.DayOfWeek); }); } + + [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); + + 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 48370ebfa1..8367f39827 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/DateTimeToIsoTest.cs @@ -1,9 +1,10 @@ -// 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 // Created: 2016.08.01 +using System; using NUnit.Framework; using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model; @@ -15,8 +16,22 @@ 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() + { + 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")); + + 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 3b73872219..660b6c6f6b 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/OperationsTest.cs @@ -1,9 +1,10 @@ -// 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 // 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.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)); + + 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(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.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)); - 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.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.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)); - 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.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)); + + 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.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)); - 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(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.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)); - 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.MinValue.AddSeconds(5) == DateTime.MinValue.AddSeconds(2)); + RunWrongTest(s, c => c.MaxValue.AddSeconds(-5) == DateTime.MaxValue.AddSeconds(-2)); }); } @@ -99,37 +178,70 @@ 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.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)); + + 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() + { + 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)); }); } [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(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)); + 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)); + }); + } + + [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)); }); } @@ -137,42 +249,72 @@ 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.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)); }); } [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(c => c.DateTime + FirstOffset == FirstDateTime + WrongOffset); - RunWrongTest(c => c.MillisecondDateTime + SecondOffset == FirstMillisecondDateTime + WrongOffset); - RunWrongTest(c => c.NullableDateTime + FirstOffset == NullableDateTime + WrongOffset); + RunWrongTest(s, c => c.DateTime + FirstOffset == FirstDateTime + WrongOffset); + RunWrongTest(s, c => c.MillisecondDateTime + SecondOffset == FirstMillisecondDateTime + WrongOffset); + RunWrongTest(s, c => c.NullableDateTime + FirstOffset == NullableDateTime + WrongOffset); + }); + } + + [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); }); } [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() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - FirstOffset == DateTime.MaxValue - FirstOffset); + RunWrongTest(s, c => c.MaxValue - FirstOffset == DateTime.MaxValue - WrongOffset); }); } @@ -180,14 +322,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(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); + }); + } + + [Test] + public void MaxValueMinusDateTimeTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MaxValue - SecondDateTime == DateTime.MaxValue - SecondDateTime); + RunWrongTest(s, c => c.MaxValue - SecondDateTime == DateTime.MaxValue - WrongDateTime); }); } @@ -195,19 +347,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 1d1b2a574e..b203cb14f5 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 @@ -26,6 +26,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.Year == WrongDateTime.Year); + RunWrongTest(s, c => c.MaxValue.Year == WrongDateTime.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 == 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 +80,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.Day == WrongDateTime.Day); + RunWrongTest(s, c => c.MaxValue.Day == WrongDateTime.Day); + }); + } + [Test] public void ExtractHourTest() { @@ -68,6 +107,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.Hour == WrongDateTime.Hour); + RunWrongTest(s, c => c.MaxValue.Hour == WrongDateTime.Hour); + }); + } + [Test] public void ExtractMinuteTest() { @@ -82,6 +134,20 @@ 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); + + RunWrongTest(s, c => c.MinValue.Minute == WrongDateTime.Minute); + RunWrongTest(s, c => c.MaxValue.Minute == WrongDateTime.Minute); + }); + } + [Test] public void ExtractSecondTest() { @@ -96,6 +162,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.Second == WrongDateTime.Second); + RunWrongTest(s, c => c.MaxValue.Second == WrongDateTime.Second); + }); + } + [Test] public void ExtractMillisecondTest() { @@ -106,6 +185,22 @@ public void ExtractMillisecondTest() }); } + [Test] + public void MinMaxValueExtractMillisecondTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + 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 +226,19 @@ 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); + + 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 +269,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.TimeOfDay == WrongDateTime.TimeOfDay); + RunWrongTest(s, c => c.MaxValue.TimeOfDay == WrongDateTime.TimeOfDay); + }); + } + [Test] public void ExtractDayOfYearTest() { @@ -175,6 +296,19 @@ 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); + + RunWrongTest(s, c => c.MinValue.DayOfYear == WrongDateTime.DayOfYear); + RunWrongTest(s, c => c.MaxValue.DayOfYear == WrongDateTime.DayOfYear); + }); + } + [Test] public void ExtractDayOfWeekTest() { @@ -189,6 +323,19 @@ 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); + + 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 85b19ff1af..41c175f56d 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeBaseTest.cs @@ -45,9 +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(AllPossiblePartsEntity)); configuration.Types.Register(typeof(DateOnlyEntity)); configuration.Types.Register(typeof(SingleDateOnlyEntity)); + 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)); } @@ -198,6 +203,12 @@ protected override void PopulateEntities(Session session) _ = new NullableDateTimeEntity(session) { DateTime = null }; _ = AllPossiblePartsEntity.FromDateTime(session, FirstMillisecondDateTime, 321); + + 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/DateTimeOffset/OperationsTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/OperationsTest.cs index f02738374b..652ce7622d 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 @@ -15,205 +15,383 @@ 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() + { + 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)); - 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() + { + 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)); - 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() + { + 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)); + + 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() + { + 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)); - 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() + { + 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)); + + 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() + { + 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)); - 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() + { + 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)); + + 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() + { + 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)); }); } [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() + { + 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)); }); } [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() + { + 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)); }); } [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() + { + 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); + + 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() + { + Require.ProviderIs(StorageProvider.PostgreSql); + 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() + { + Require.ProviderIs(StorageProvider.PostgreSql); + 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() + { + Require.ProviderIs(StorageProvider.PostgreSql); + 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() + { + 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); - 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 +399,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 +410,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 +420,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 05d5b32396..49d9d079ee 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 @@ -15,137 +15,363 @@ 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); + 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); + }); + } - RunWrongTest(c => c.DateTimeOffset.Year==WrongDateTimeOffset.Year); - RunWrongTest(c => c.MillisecondDateTimeOffset.Year==WrongMillisecondDateTimeOffset.Year); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Year==WrongDateTimeOffset.Year); + [Test] + public void ExtractYearMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Year == DateTimeOffset.MinValue.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); + }); + } + [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); + 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 ExtractMonthMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Month == DateTimeOffset.MinValue.Month); + RunWrongTest(s, c => c.MinValue.Month == DateTimeOffset.MinValue.AddMonths(1).Month); + }); + } - RunWrongTest(c => c.DateTimeOffset.Month==WrongDateTimeOffset.Month); - RunWrongTest(c => c.MillisecondDateTimeOffset.Month==WrongMillisecondDateTimeOffset.Month); - RunWrongTest(c => c.NullableDateTimeOffset.Value.Month==WrongDateTimeOffset.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); }); } [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 ExtractDayMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Day == DateTimeOffset.MinValue.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); }); } [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 ExtractHourMinValueTest() + { + 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 \"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); }); } [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 ExtractMinuteMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Minute == DateTimeOffset.MinValue.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); }); } [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 ExtractSecondMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Second == DateTimeOffset.MinValue.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); }); } [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); + }); + } + + [Test] + public void ExtractDateMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.Date == DateTimeOffset.MinValue.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); }); } @@ -158,34 +384,57 @@ 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 ExtractTimeOfDayMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + 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); }); } [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 +443,195 @@ 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 ExtractDayOfYearMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfYear == DateTimeOffset.MinValue.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); }); } [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 ExtractDayOfWeekMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + RunTest(s, c => c.MinValue.DayOfWeek == DateTimeOffset.MinValue.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); }); } [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 ExtractDateTimeMinValueTest() + { + Require.ProviderIs(StorageProvider.PostgreSql); + ExecuteInsideSession((s) => { + 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); }); } [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 5c73276e5d..200baabdb9 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,15 +186,17 @@ 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; + + 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(); Assert.That(whereLocal.Length, Is.Not.EqualTo(0)); Assert.That(whereByServer.Length, Is.Not.EqualTo(0)); diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs index cd454cfa24..9958506833 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffsetBaseTest.cs @@ -37,11 +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(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) @@ -61,7 +64,7 @@ protected override void CheckRequirements() protected override void PopulateEntities(Session session) { - new SingleDateTimeOffsetEntity { + _ = new SingleDateTimeOffsetEntity(session) { DateTimeOffset = FirstDateTimeOffset, MillisecondDateTimeOffset = FirstMillisecondDateTimeOffset, NullableDateTimeOffset = NullableDateTimeOffset @@ -98,50 +101,58 @@ 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 }; + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.PostgreSql)) { + _ = 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 cd6c8a03dc..8ce38ec464 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 { diff --git a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj index 0d3ae1dca3..595e761994 100644 --- a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj +++ b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs b/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs index 68bcf69823..a86606e133 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; diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs index f7a174e5b6..1c041a8ca6 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/Dml/Expressions/SqlExtract.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs index 17ca502ab4..d192f7c7bc 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs @@ -124,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; } } diff --git a/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs b/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs index a67f025d1f..e25a413712 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 293f94fc94..2337c70e21 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); diff --git a/Orm/Xtensive.Orm/Xtensive.Orm.csproj b/Orm/Xtensive.Orm/Xtensive.Orm.csproj index 24af9cafcf..0b7a1746c6 100644 --- a/Orm/Xtensive.Orm/Xtensive.Orm.csproj +++ b/Orm/Xtensive.Orm/Xtensive.Orm.csproj @@ -61,7 +61,7 @@ - +