diff --git a/ChangeLog/7.2.0-Beta-2-dev.txt b/ChangeLog/7.2.0-Beta-2-dev.txt deleted file mode 100644 index 29d4b11bcb..0000000000 --- a/ChangeLog/7.2.0-Beta-2-dev.txt +++ /dev/null @@ -1,6 +0,0 @@ -[main] Upgrade hints change names of constructors' string parameters for better understanding of what suppose to be in them. -[main] Improved string operations Trim/TrimStart/TrimEnd support -[mysql] SqlDml.NullIf function now correctly translated -[mysql] Improved support for string.PadLeft/PadRight opertaions -[sqlite] Fixed string.Lenght translation -[sqlite] Added support for string.PadLeft/PadRight operations \ No newline at end of file diff --git a/ChangeLog/7.2.0-Beta-2.txt b/ChangeLog/7.2.0-Beta-2.txt new file mode 100644 index 0000000000..4c6b6930cf --- /dev/null +++ b/ChangeLog/7.2.0-Beta-2.txt @@ -0,0 +1,28 @@ +[main] Upgrade hints change names of constructors' string parameters for better understanding of what suppose to be in them. +[main] Improved string operations Trim/TrimStart/TrimEnd support +[main] Obsolete DomainConfiguration.DefauktForeignKeyMode const removed, the correctly named constant still exists +[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 9.0.3 +[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 +[postgresql] For PostgreSQL 13+ apply 'trim_scale' function to results of aggregation to improve compatibility with .NET decimal +[oracle] Updated client library to version 23.7.0 +[sqlite] Fixed string.Lenght translation +[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/ChangeLog/7.2.0-RC-dev.txt b/ChangeLog/7.2.0-RC-dev.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Directory.Packages.props b/Directory.Packages.props index 93792cb207..6a25df4eca 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,16 +25,16 @@ - + - + - - - + + + - + diff --git a/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/DomainHandler.cs b/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/DomainHandler.cs index 96bbeeb02c..3d210ee53e 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/DomainHandler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/DomainHandler.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,6 +18,12 @@ namespace Xtensive.Orm.Providers.PostgreSql /// public class DomainHandler : Providers.DomainHandler { + /// + /// if storage can trim insignificant zeros in numeric values + /// + protected bool HasNativeTrimOfInsignificantZerosInDecimals => + Handlers.ProviderInfo.StorageVersion.Major >= 13; + /// protected override ICompiler CreateCompiler(CompilerConfiguration configuration) => new SqlCompiler(Handlers, configuration); @@ -25,8 +31,11 @@ protected override ICompiler CreateCompiler(CompilerConfiguration configuration) /// protected override IPreCompiler CreatePreCompiler(CompilerConfiguration configuration) { - var decimalAggregateCorrector = new AggregateOverDecimalColumnCorrector(Handlers.Domain.Model); - return new CompositePreCompiler(decimalAggregateCorrector, base.CreatePreCompiler(configuration)); + if (!HasNativeTrimOfInsignificantZerosInDecimals) { + var decimalAggregateCorrector = new AggregateOverDecimalColumnCorrector(Handlers.Domain.Model); + return new CompositePreCompiler(decimalAggregateCorrector, base.CreatePreCompiler(configuration)); + } + return base.CreatePreCompiler(configuration); } /// diff --git a/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/PostgresqlSqlDml.cs b/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/PostgresqlSqlDml.cs index ee7f66c4e3..2a530e46b8 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/PostgresqlSqlDml.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/PostgresqlSqlDml.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 @@ -6,6 +6,7 @@ using System; using Xtensive.Core; +using Xtensive.Sql; using Xtensive.Sql.Dml; namespace Xtensive.Orm.Providers.PostgreSql @@ -17,6 +18,15 @@ namespace Xtensive.Orm.Providers.PostgreSql /// public class PostgresqlSqlDml { + /// + /// Creates an expression for native "trim_scale" function call. The function is supported starting from PostgreSQL 13 + /// + public static SqlExpression DecimalTrimScale(SqlExpression operand) + { + ArgumentNullException.ThrowIfNull(operand); + return SqlDml.FunctionCall("TRIM_SCALE", operand); + } + #region Spatial types /// diff --git a/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/SqlCompiler.cs b/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/SqlCompiler.cs index 7fbdd8e25c..35752368a7 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/SqlCompiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Orm.Providers.PostgreSql/SqlCompiler.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 @@ -21,6 +21,8 @@ internal class SqlCompiler : Providers.SqlCompiler { private const int MaxDotnetDecimalPrecision = 28; + private readonly bool canRemoveInsignificantZerosInDecimals; + protected override SqlProvider VisitFreeText(FreeTextProvider provider) { var rankColumnName = provider.Header.Columns[provider.Header.Columns.Count - 1].Name; @@ -61,6 +63,11 @@ protected override SqlExpression ProcessAggregate(SqlProvider source, IReadOnlyL var aggregateType = aggregateColumn.AggregateType; var originCalculateColumn = source.Origin.Header.Columns[aggregateColumn.SourceIndex]; if (AggregateRequiresDecimalAdjustments(aggregateColumn)) { + if (canRemoveInsignificantZerosInDecimals) { + return (IsCalculatedColumn(originCalculateColumn)) + ? PostgresqlSqlDml.DecimalTrimScale(SqlDml.Cast(result, Driver.MapValueType(aggregateColumn.Type))) + : PostgresqlSqlDml.DecimalTrimScale(result); + } if (!IsCalculatedColumn(originCalculateColumn)) { // this is aggregate by one column, result will be defined by the precision and scale of the column return result; @@ -138,6 +145,7 @@ AggregateType.Min or public SqlCompiler(HandlerAccessor handlers, in CompilerConfiguration configuration) : base(handlers, configuration) { + canRemoveInsignificantZerosInDecimals = handlers.ProviderInfo.StorageVersion.Major >= 13; } } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Connection.cs index e520d1c8ff..1263e5aef8 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 @@ -8,6 +8,7 @@ using Npgsql; using System.Data; using System.Data.Common; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xtensive.Core; @@ -54,10 +55,9 @@ public override void Commit(bool rollbackOnFail = false) { EnsureIsNotDisposed(); EnsureTransactionIsActive(); - try { - if (!IsTransactionCompleted()) { - ActiveTransaction.Commit(); + if(!IsTransactionCompleted()) { + activeTransaction.Commit(); } activeTransactionIsCompleted = true; } @@ -66,7 +66,7 @@ public override void Commit(bool rollbackOnFail = false) throw; } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -77,7 +77,7 @@ public override async Task CommitAsync(bool rollbackOnFail = false, Cancellation EnsureTransactionIsActive(); try { if (!IsTransactionCompleted()) { - await ActiveTransaction.CommitAsync(token).ConfigureAwait(false); + await activeTransaction.CommitAsync(token).ConfigureAwait(false); } activeTransactionIsCompleted = true; } @@ -86,7 +86,7 @@ public override async Task CommitAsync(bool rollbackOnFail = false, Cancellation throw; } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwait(false); + await activeTransaction.DisposeAsync().ConfigureAwait(false); ClearActiveTransaction(); } } @@ -95,14 +95,13 @@ public override void Rollback() { EnsureIsNotDisposed(); EnsureTransactionIsActive(); - try { if (!IsTransactionCompleted()) { - ActiveTransaction.Rollback(); + activeTransaction.Rollback(); } } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -113,11 +112,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(); } } @@ -183,7 +182,7 @@ private void ExecuteNonQuery(string commandText) EnsureTransactionIsActive(); using var command = CreateCommand(commandText); - command.ExecuteNonQuery(); + _ = command.ExecuteNonQuery(); } private async Task ExecuteNonQueryAsync(string commandText, CancellationToken token) @@ -193,14 +192,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 && activeTransactionIsCompleted; - } + 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 7f2a41a5ef..5559acb470 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) @@ -64,14 +67,18 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf OpenConnectionFast(connection, configuration, false).GetAwaiter().GetResult(); var version = GetVersion(configuration, connection); var defaultSchema = GetDefaultSchema(connection); - SqlHelper.ExecuteInitializationSql(connection, @"SET TIME ZONE 'UTC'"); - 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) @@ -80,8 +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); - await SqlHelper.ExecuteInitializationSqlAsync(connection, @"SET TIME ZONE 'UTC'", token); - return CreateDriverInstance(connectionString, version, defaultSchema); + var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone); + return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo); } } @@ -94,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, @@ -104,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); } @@ -111,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) }; } @@ -187,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); + } } } \ No newline at end of file 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 af47f74250..ddb79bae7b 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. @@ -41,7 +41,24 @@ private static readonly SqlValueType 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) @@ -141,25 +158,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); @@ -168,10 +185,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); @@ -186,31 +203,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); @@ -293,8 +310,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) { @@ -390,7 +412,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) @@ -457,17 +557,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 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) => - GetDateTimeInTimeZone(timeStamp, TimeSpan.Zero); + protected SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression timestamp) + { + var convertOperand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return GetDateTimeInTimeZone(convertOperand, TimeSpan.Zero); + } - protected SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression timestamp) => - SqlDml.Cast(timestamp, SqlType.DateTime); + protected SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression timestamp) + { + var extractOperand = infinityAliasForDatesEnabled + ? CreateInfinityCheckExpression(timestamp, DateTimeOffsetMaxValue, DateTimeOffsetMinValue) + : timestamp; + return SqlDml.Cast(extractOperand, SqlType.DateTime); + } protected void DateTimeOffsetExtractOffset(SqlExtract node) { @@ -475,7 +617,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); @@ -483,10 +631,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) { @@ -513,32 +674,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); @@ -578,9 +790,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 a97c3cc965..6343daabb7 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 f0e18a371e..35c25eb982 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 c9d071efc7..21ae558540 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 @@ -21,6 +21,15 @@ private static readonly SqlValueType Decimal20Type = new(SqlType.Decimal, 20, 0), VarChar32Type = new(SqlType.VarChar, 32), IntervalType = new(SqlType.Interval); + // 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) { @@ -118,9 +127,11 @@ public override void BindTimeSpan(DbParameter parameter, object value) { var nativeParameter = (NpgsqlParameter) parameter; nativeParameter.NpgsqlDbType = NpgsqlDbType.Interval; - nativeParameter.Value = value != null - ? (object) (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) @@ -129,31 +140,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) { - parameter.DbType = DbType.DateTime2; - if (value is DateTime dt) { -// ((NpgsqlParameter) parameter).NpgsqlDbType = NpgsqlDbType.TimestampTz; - var utc = dt.Kind switch { - DateTimeKind.Local => dt.ToUniversalTime(), - DateTimeKind.Utc => dt, - _ => DateTime.SpecifyKind(dt, DateTimeKind.Utc) - }; - var unspec = DateTime.SpecifyKind(utc, DateTimeKind.Unspecified); - parameter.Value = unspec; + if (legacyTimestampBehaviorEnabled) { + base.BindDateTime(parameter, value); } else { - parameter.Value = DBNull.Value; + 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) { - if (value is DateTimeOffset dto) { - value = dto.ToUniversalTime(); + var nativeParameter = (NpgsqlParameter) parameter; + 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)); } - base.BindDateTimeOffset(parameter, value); } public override SqlValueType MapByte(int? length, int? precision, int? scale) => SqlValueType.Int16; @@ -178,28 +212,93 @@ public override Guid ReadGuid(DbDataReader reader, int index) public override TimeSpan ReadTimeSpan(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; - return nativeReader.GetTimeSpan(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 decimal ReadDecimal(DbDataReader reader, int index) { var nativeReader = (NpgsqlDataReader) reader; return nativeReader.GetDecimal(index); } + public override DateOnly ReadDateOnly(DbDataReader reader, int index) + { + return reader.GetFieldValue(index); + } + + public override DateTime 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 DateTimeOffset 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) + : 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; } } } 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 be7656a974..3397c6fcd1 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 3388459d6e..79cd323baa 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.SqlServer/Sql.Drivers.SqlServer/Connection.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs index afea820e96..452ba9a456 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs @@ -137,11 +137,11 @@ public override void Rollback() try { if (!IsTransactionZombied()) { - ActiveTransaction.Rollback(); + activeTransaction.Rollback(); } } finally { - ActiveTransaction.Dispose(); + activeTransaction.Dispose(); ClearActiveTransaction(); } } @@ -154,11 +154,11 @@ public override async Task RollbackAsync(CancellationToken token = default) try { if (!IsTransactionZombied()) { - await ActiveTransaction.RollbackAsync(token).ConfigureAwaitFalse(); + await activeTransaction.RollbackAsync(token).ConfigureAwaitFalse(); } } finally { - await ActiveTransaction.DisposeAsync().ConfigureAwaitFalse(); + await activeTransaction.DisposeAsync().ConfigureAwaitFalse(); ClearActiveTransaction(); } } @@ -361,7 +361,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.Tests.Framework/TestConfiguration.cs b/Orm/Xtensive.Orm.Tests.Framework/TestConfiguration.cs index 61b6a355bf..292868cc50 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 Lock InstanceLock = new(); @@ -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 f414cc6aeb..47a4c3eecc 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 }; + } } } } 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 906c02321a..9fb30388ca 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,28 +15,68 @@ 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); }); } @@ -44,109 +84,295 @@ public void ExtractMonthTest() [MutePostgreSql] 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); }); } @@ -159,34 +385,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); }); } @@ -195,132 +444,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/Orm/Configuration/DomainConfiguration.cs b/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs index dc554afcb3..afc2c13ac5 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs @@ -86,12 +86,6 @@ public class DomainConfiguration : ConfigurationBase /// public const DomainUpgradeMode DefaultUpgradeMode = DomainUpgradeMode.Default; - /// - /// Default value. - /// - [Obsolete ("User DefaultForeignKeyMode")] - public const ForeignKeyMode DefauktForeignKeyMode = ForeignKeyMode.Default; - /// /// Default value. /// diff --git a/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs b/Orm/Xtensive.Orm/Orm/Internals/FieldAccessors/DefaultFieldAccessor.cs index 4640e39f44..07a45a8e64 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 @@ -17,14 +17,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/Orm/Providers/CommandProcessing/SqlPersistTask.cs b/Orm/Xtensive.Orm/Orm/Providers/CommandProcessing/SqlPersistTask.cs index 4d836eed22..25ce5a51cd 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/CommandProcessing/SqlPersistTask.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/CommandProcessing/SqlPersistTask.cs @@ -69,18 +69,6 @@ public SqlPersistTask(PreparedPersistRequest request, IReadOnlyList tuple Tuples = tuples; } - [Obsolete] - public SqlPersistTask(Key key, IEnumerable requestSequence, Tuple tuple) - : this(key, (requestSequence as IReadOnlyCollection)?? requestSequence.ToList(), tuple) - { - } - - [Obsolete] - public SqlPersistTask(Key key, IEnumerable requestSequence, Tuple tuple, Tuple originalTuple, bool validateRowCount) - : this(key, (requestSequence as IReadOnlyCollection) ?? requestSequence.ToList(), tuple, originalTuple, validateRowCount) - { - } - public SqlPersistTask(Key key, IReadOnlyCollection requestSequence, Tuple tuple) { EntityKey = key; diff --git a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs index b327b50f1f..9d84a3d22d 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/AggregateProvider.cs @@ -197,7 +197,8 @@ private static NotSupportedException AggregateNotSupported(Type sourceColumnType /// /// The property value. /// The column indexes to group by. - public AggregateProvider(CompilableProvider source, IReadOnlyList groupIndexes, IEnumerable columnDescriptors) + /// The descriptors of . + public AggregateProvider(CompilableProvider source, IReadOnlyList groupIndexes, IReadOnlyList columnDescriptors) : base(ProviderType.Aggregate, source) { GroupColumnIndexes = groupIndexes ?? Array.Empty(); diff --git a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/CalculateProvider.cs b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/CalculateProvider.cs index 0f6e1a4c40..6645e20211 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/CalculateProvider.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/CalculateProvider.cs @@ -66,18 +66,24 @@ protected override void Initialize() // Constructors /// - /// Initializes a new instance of this class. + /// Initializes a new instance of this class. /// /// The property value. /// The property value. /// The descriptors of . - public CalculateProvider(CompilableProvider source, IEnumerable columnDescriptors, bool isInlined = false) + public CalculateProvider(CompilableProvider source, IReadOnlyList columnDescriptors, bool isInlined = false) : base(ProviderType.Calculate, source) { ArgumentNullException.ThrowIfNull(columnDescriptors); + IsInlined = isInlined; var baseIndex = Source.Header.Length; - CalculatedColumns = columnDescriptors.Select((desc, i) => new CalculatedColumn(desc, (ColNum) (baseIndex + i))).ToArray(); + var columns = new CalculatedColumn[columnDescriptors.Count]; + for (int i = 0, count = columnDescriptors.Count; i < count; i++) { + var col = new CalculatedColumn(columnDescriptors[i], (ColNum) (baseIndex + i)); + columns.SetValue(col, i); + } + CalculatedColumns = columns; Initialize(); } } diff --git a/Orm/Xtensive.Orm/Orm/Rse/Providers/CompilableProviderVisitor.cs b/Orm/Xtensive.Orm/Orm/Rse/Providers/CompilableProviderVisitor.cs index e1219e4cdc..6889f2a471 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Providers/CompilableProviderVisitor.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Providers/CompilableProviderVisitor.cs @@ -200,7 +200,7 @@ internal protected override CompilableProvider VisitAggregate(AggregateProvider if (resultParameters == null) { return new AggregateProvider(source, provider.GroupColumnIndexes, - provider.AggregateColumns.Select(ac => new AggregateColumnDescriptor(ac.Name, ac.SourceIndex, ac.AggregateType)) + provider.AggregateColumns.Select(ac => new AggregateColumnDescriptor(ac.Name, ac.SourceIndex, ac.AggregateType)).ToArray() ); } var result = (ValueTuple) resultParameters; diff --git a/Orm/Xtensive.Orm/Orm/Upgrade/Internals/CatalogCloner.cs b/Orm/Xtensive.Orm/Orm/Upgrade/Internals/CatalogCloner.cs index 82741ad50e..54a2bfc813 100644 --- a/Orm/Xtensive.Orm/Orm/Upgrade/Internals/CatalogCloner.cs +++ b/Orm/Xtensive.Orm/Orm/Upgrade/Internals/CatalogCloner.cs @@ -160,7 +160,7 @@ private void CloneViews(Schema newSchema, Schema sourceSchema) CopyDbName(newView, sourceView); newView.CheckOptions = sourceView.CheckOptions; if (sourceView.Definition is not null) { - newView.Definition = sourceView.Definition.Clone(); + newView.Definition = (SqlNative) sourceView.Definition.Clone(); } CloneViewColumns(newView, sourceView); CloneIndexes(newView, sourceView); diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAction.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAction.cs index 28900cf2ff..edd9f53f87 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAction.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAction.cs @@ -14,7 +14,7 @@ public override void AcceptVisitor(ISqlVisitor visitor) throw new NotSupportedException(); } - internal override abstract SqlAction Clone(SqlNodeCloneContext? context = null); + internal override abstract SqlAction Clone(SqlNodeCloneContext context); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddColumn.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddColumn.cs index 79b0c65591..c95747777e 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddColumn.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddColumn.cs @@ -12,7 +12,7 @@ public class SqlAddColumn : SqlAction { public TableColumn Column { get; private set; } - internal override SqlAddColumn Clone(SqlNodeCloneContext? context = null) => + internal override SqlAddColumn Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Column)); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddConstraint.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddConstraint.cs index 1c6a16e6db..568c982359 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddConstraint.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAddConstraint.cs @@ -12,7 +12,7 @@ public class SqlAddConstraint : SqlAction { public Constraint Constraint { get; private set; } - internal override SqlAddConstraint Clone(SqlNodeCloneContext? context = null) => + internal override SqlAddConstraint Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Constraint)); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAlterIdentityInfo.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAlterIdentityInfo.cs index 6f9498f925..5a901ff690 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAlterIdentityInfo.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlAlterIdentityInfo.cs @@ -14,7 +14,7 @@ public class SqlAlterIdentityInfo : SqlAction public SequenceDescriptor SequenceDescriptor { get; set; } public SqlAlterIdentityInfoOptions InfoOption { get; set; } - internal override SqlAlterIdentityInfo Clone(SqlNodeCloneContext? context = null) => + internal override SqlAlterIdentityInfo Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Column, t.SequenceDescriptor.Clone(), t.InfoOption)); internal SqlAlterIdentityInfo(TableColumn column, SequenceDescriptor sequenceDescriptor, SqlAlterIdentityInfoOptions infoOption) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropColumn.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropColumn.cs index e2c627f7c9..80bc26c578 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropColumn.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropColumn.cs @@ -12,7 +12,7 @@ public class SqlDropColumn : SqlCascadableAction { public TableColumn Column { get; private set; } - internal override SqlDropColumn Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropColumn Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Column)); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropConstraint.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropConstraint.cs index 1dd65a2048..a2fda251d7 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropConstraint.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropConstraint.cs @@ -12,7 +12,7 @@ public class SqlDropConstraint : SqlCascadableAction { public Constraint Constraint { get; private set; } - internal override SqlDropConstraint Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropConstraint Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Constraint, t.Cascade)); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropDefault.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropDefault.cs index 395640f2d2..db647531c8 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropDefault.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlDropDefault.cs @@ -12,7 +12,7 @@ public class SqlDropDefault : SqlAction { public TableColumn Column { get; private set; } - internal override SqlDropDefault Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropDefault Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Column)); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlRenameColumn.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlRenameColumn.cs index 8bdcf02a5e..86fbf97d83 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlRenameColumn.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlRenameColumn.cs @@ -15,7 +15,7 @@ public class SqlRenameColumn : SqlAction public TableColumn Column { get; private set; } public string NewName { get; private set; } - internal override SqlRenameColumn Clone(SqlNodeCloneContext? context = null) => + internal override SqlRenameColumn Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Column, t.NewName)); @@ -27,4 +27,4 @@ internal SqlRenameColumn(TableColumn column, string newName) NewName = newName; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlSetDefault.cs b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlSetDefault.cs index 8c1b00afb3..6f09545af9 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlSetDefault.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/Actions/SqlSetDefault.cs @@ -14,7 +14,7 @@ public class SqlSetDefault : SqlAction public TableColumn Column { get; private set; } public SqlExpression DefaultValue { get; private set; } - internal override SqlSetDefault Clone(SqlNodeCloneContext? context = null) => + internal override SqlSetDefault Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.DefaultValue.Clone(c), t.Column)); internal SqlSetDefault(SqlExpression defaultValue, TableColumn column) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterDomain.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterDomain.cs index 92940e1b99..996f457591 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterDomain.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterDomain.cs @@ -25,7 +25,7 @@ public Domain Domain { } } - internal override SqlAlterDomain Clone(SqlNodeCloneContext? context = null) => + internal override SqlAlterDomain Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.domain, t.action.Clone(c))); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionFunction.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionFunction.cs index ff30cb48e6..e9712d7e12 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionFunction.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionFunction.cs @@ -31,7 +31,7 @@ public SqlAlterPartitionFunctionOption Option set { option = value; } } - internal override SqlAlterPartitionFunction Clone(SqlNodeCloneContext? context = null) => + internal override SqlAlterPartitionFunction Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.partitionFunction, t.boundary, t.option)); @@ -49,4 +49,4 @@ internal SqlAlterPartitionFunction( this.option = option; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionScheme.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionScheme.cs index 61917561de..ec49c92bb5 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionScheme.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterPartitionScheme.cs @@ -17,7 +17,7 @@ public class SqlAlterPartitionScheme : SqlStatement, ISqlCompileUnit public string Filegroup => filegroup; - internal override SqlAlterPartitionScheme Clone(SqlNodeCloneContext? context = null) => + internal override SqlAlterPartitionScheme Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.partitionSchema, t.filegroup)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterSequence.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterSequence.cs index e30d4ead85..3a245b5917 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterSequence.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterSequence.cs @@ -24,7 +24,7 @@ public SqlAlterIdentityInfoOptions InfoOption set => infoOption = value; } - internal override SqlAlterSequence Clone(SqlNodeCloneContext? context = null) => + internal override SqlAlterSequence Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.sequence, t.sequenceDescriptor.Clone(), t.infoOption)); diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterTable.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterTable.cs index 5dbf1ae6ea..e4f7a2c8f6 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterTable.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlAlterTable.cs @@ -18,7 +18,7 @@ public class SqlAlterTable : SqlStatement, ISqlCompileUnit public Table Table => table; - internal override SqlAlterTable Clone(SqlNodeCloneContext? context = null) => + internal override SqlAlterTable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.table, t.action.Clone(c))); diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCommand.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCommand.cs index 2758880ef0..72e634ab2a 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCommand.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCommand.cs @@ -13,7 +13,7 @@ public class SqlCommand : SqlStatement, ISqlCompileUnit { public SqlCommandType CommandType { get; private set; } - internal override SqlCommand Clone(SqlNodeCloneContext? context = null) => + internal override SqlCommand Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.CommandType)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -29,4 +29,4 @@ internal SqlCommand(SqlCommandType commandType) CommandType = commandType; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateAssertion.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateAssertion.cs index 4f865fc72a..170ed31d72 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateAssertion.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateAssertion.cs @@ -12,7 +12,7 @@ public class SqlCreateAssertion : SqlStatement, ISqlCompileUnit { public Assertion Assertion { get; } - internal override SqlCreateAssertion Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateAssertion Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Assertion)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCharcterSet.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCharcterSet.cs index 1bf0f36b6d..ef7cc70773 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCharcterSet.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCharcterSet.cs @@ -12,7 +12,7 @@ public class SqlCreateCharacterSet : SqlStatement, ISqlCompileUnit { public CharacterSet CharacterSet { get; } - internal override SqlCreateCharacterSet Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateCharacterSet Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.CharacterSet)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCollation.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCollation.cs index 66a092baf5..8a00f3a3f6 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCollation.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateCollation.cs @@ -12,7 +12,7 @@ public class SqlCreateCollation : SqlStatement, ISqlCompileUnit { public Collation Collation { get; } - internal override SqlCreateCollation Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateCollation Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Collation)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateDomain.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateDomain.cs index 0274096e85..35b45e7cca 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateDomain.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateDomain.cs @@ -12,7 +12,7 @@ public class SqlCreateDomain : SqlStatement, ISqlCompileUnit { public Domain Domain { get; } - internal override SqlCreateDomain Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateDomain Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Domain)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateIndex.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateIndex.cs index 92a73b0b53..0a75eab008 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateIndex.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateIndex.cs @@ -13,7 +13,7 @@ public class SqlCreateIndex : SqlStatement, ISqlCompileUnit { public Index Index { get; } - internal override SqlCreateIndex Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateIndex Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Index)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionFunction.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionFunction.cs index 4b3127d0ff..1e128ec208 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionFunction.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionFunction.cs @@ -12,7 +12,7 @@ public class SqlCreatePartitionFunction : SqlStatement, ISqlCompileUnit { public PartitionFunction PartitionFunction { get; } - internal override SqlCreatePartitionFunction Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreatePartitionFunction Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.PartitionFunction)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionScheme.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionScheme.cs index a82133d1fb..dfdbbd7182 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionScheme.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreatePartitionScheme.cs @@ -12,7 +12,7 @@ public class SqlCreatePartitionScheme : SqlStatement, ISqlCompileUnit { public PartitionSchema PartitionSchema { get; } - internal override SqlCreatePartitionScheme Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreatePartitionScheme Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.PartitionSchema)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSchema.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSchema.cs index 3f7cbdb534..a73f311281 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSchema.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSchema.cs @@ -12,7 +12,7 @@ public class SqlCreateSchema : SqlStatement, ISqlCompileUnit { public Schema Schema { get; } - internal override SqlCreateSchema Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateSchema Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Schema)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSequence.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSequence.cs index d3e2149fe5..e37ce8b4a0 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSequence.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateSequence.cs @@ -12,7 +12,7 @@ public class SqlCreateSequence : SqlStatement, ISqlCompileUnit { public Sequence Sequence { get; } - internal override SqlCreateSequence Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateSequence Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Sequence)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTable.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTable.cs index 5a16d786e0..706719bd15 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTable.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTable.cs @@ -12,7 +12,7 @@ public class SqlCreateTable(Table table) : SqlStatement(SqlNodeType.Create), ISq { public Table Table { get; } = table; - internal override SqlCreateTable Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateTable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Table)); public override void AcceptVisitor(ISqlVisitor visitor) => visitor.Visit(this); diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTranslation.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTranslation.cs index fc8e5158d7..8e070bd7e3 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTranslation.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateTranslation.cs @@ -12,7 +12,7 @@ public class SqlCreateTranslation : SqlStatement, ISqlCompileUnit { public Translation Translation { get; } - internal override SqlCreateTranslation Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateTranslation Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Translation)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateView.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateView.cs index 0239648ece..f81aa51acc 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateView.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlCreateView.cs @@ -12,7 +12,7 @@ public class SqlCreateView : SqlStatement, ISqlCompileUnit { public View View { get; } - internal override SqlCreateView Clone(SqlNodeCloneContext? context = null) => + internal override SqlCreateView Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.View)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropAssertion.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropAssertion.cs index 954c44b538..f03f885aab 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropAssertion.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropAssertion.cs @@ -12,7 +12,7 @@ public class SqlDropAssertion : SqlStatement, ISqlCompileUnit { public Assertion Assertion { get; } - internal override SqlDropAssertion Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropAssertion Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Assertion)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCharacterSet.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCharacterSet.cs index 61fad3ea31..df0ac650d4 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCharacterSet.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCharacterSet.cs @@ -12,7 +12,7 @@ public class SqlDropCharacterSet : SqlStatement, ISqlCompileUnit { public CharacterSet CharacterSet { get; } - internal override SqlDropCharacterSet Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropCharacterSet Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.CharacterSet)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCollation.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCollation.cs index c6bdebbc6b..88cd852c31 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCollation.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropCollation.cs @@ -12,7 +12,7 @@ public class SqlDropCollation : SqlStatement, ISqlCompileUnit { public Collation Collation { get; } - internal override SqlDropCollation Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropCollation Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Collation)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropDomain.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropDomain.cs index 8160661d14..35dc3518f2 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropDomain.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropDomain.cs @@ -28,7 +28,7 @@ public bool Cascade { } } - internal override SqlDropDomain Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropDomain Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.domain)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropIndex.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropIndex.cs index af54f9c44a..ab2455b97c 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropIndex.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropIndex.cs @@ -54,7 +54,7 @@ public class SqlDropIndex : SqlStatement, ISqlCompileUnit // } //} - internal override SqlDropIndex Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropIndex Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Index)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionFunction.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionFunction.cs index df1ee36f97..e9fc848212 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionFunction.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionFunction.cs @@ -12,7 +12,7 @@ public class SqlDropPartitionFunction : SqlStatement, ISqlCompileUnit { public PartitionFunction PartitionFunction { get; } - internal override SqlDropPartitionFunction Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropPartitionFunction Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.PartitionFunction)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionScheme.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionScheme.cs index 4b1d8769d1..ed01e46668 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionScheme.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropPartitionScheme.cs @@ -12,7 +12,7 @@ public class SqlDropPartitionScheme : SqlStatement, ISqlCompileUnit { public PartitionSchema PartitionSchema { get; } - internal override SqlDropPartitionScheme Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropPartitionScheme Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.PartitionSchema)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSchema.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSchema.cs index 5a5a7fce7a..6f908ae0c0 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSchema.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSchema.cs @@ -23,7 +23,7 @@ public bool Cascade { } } - internal override SqlDropSchema Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropSchema Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Schema)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSequence.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSequence.cs index 8724808f83..623918ed21 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSequence.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropSequence.cs @@ -23,7 +23,7 @@ public bool Cascade { } } - internal override SqlDropSequence Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropSequence Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Sequence)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTable.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTable.cs index 163ed2287e..b9c568f1eb 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTable.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTable.cs @@ -23,7 +23,7 @@ public bool Cascade { } } - internal override SqlDropTable Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropTable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Table, t.cascade)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTranslation.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTranslation.cs index 5e6b5055f8..7eecc65de3 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTranslation.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropTranslation.cs @@ -12,7 +12,7 @@ public class SqlDropTranslation : SqlStatement, ISqlCompileUnit { public Translation Translation { get; } - internal override SqlDropTranslation Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropTranslation Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Translation)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropView.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropView.cs index 467fb9447f..061f50168d 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlDropView.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlDropView.cs @@ -23,7 +23,7 @@ public bool Cascade { } } - internal override SqlDropView Clone(SqlNodeCloneContext? context = null) => + internal override SqlDropView Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.View)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlRenameTable.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlRenameTable.cs index bc3a67e0b9..7c90aa7a09 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlRenameTable.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlRenameTable.cs @@ -15,7 +15,7 @@ public class SqlRenameTable : SqlStatement, ISqlCompileUnit public Table Table { get; private set; } public string NewName { get; private set; } - internal override SqlRenameTable Clone(SqlNodeCloneContext? context = null) => + internal override SqlRenameTable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Table, t.NewName)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -32,4 +32,4 @@ internal SqlRenameTable(Table table, string newName) NewName = newName; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Ddl/SqlTruncateTable.cs b/Orm/Xtensive.Orm/Sql/Ddl/SqlTruncateTable.cs index ab0cc2872f..da73a19cd7 100644 --- a/Orm/Xtensive.Orm/Sql/Ddl/SqlTruncateTable.cs +++ b/Orm/Xtensive.Orm/Sql/Ddl/SqlTruncateTable.cs @@ -12,7 +12,7 @@ public class SqlTruncateTable : SqlStatement, ISqlCompileUnit { public Table Table { get; } - internal override SqlTruncateTable Clone(SqlNodeCloneContext? context = null) => + internal override SqlTruncateTable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Table)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Collections/SqlInsertValuesCollection.cs b/Orm/Xtensive.Orm/Sql/Dml/Collections/SqlInsertValuesCollection.cs index 86d09ea479..18ddbbb0c6 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Collections/SqlInsertValuesCollection.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Collections/SqlInsertValuesCollection.cs @@ -100,9 +100,8 @@ public void Clear() /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal SqlInsertValuesCollection Clone(SqlNodeCloneContext? context = null) + internal SqlInsertValuesCollection Clone(SqlNodeCloneContext ctx) { - var ctx = context ?? new(); var clone = new SqlInsertValuesCollection(); if (rows.Count == 0) { @@ -117,10 +116,10 @@ internal SqlInsertValuesCollection Clone(SqlNodeCloneContext? context = null) clone.rows = new List(rows.Count); foreach(var oldRow in rows) { - clone.rows.Add(oldRow.Clone()); + clone.rows.Add((SqlRow) oldRow.Clone()); } return clone; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlAggregate.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlAggregate.cs index 150def6e1a..664a61a6e2 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlAggregate.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlAggregate.cs @@ -39,7 +39,7 @@ public override void ReplaceWith(SqlExpression expression) this.expression = replacingExpression.Expression; } - internal override SqlAggregate Clone(SqlNodeCloneContext? context = null) => + internal override SqlAggregate Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.NodeType, t.expression?.Clone(c), t.distinct)); internal SqlAggregate(SqlNodeType nodeType, SqlExpression expression, bool distinct) : base(nodeType) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlArray{T}.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlArray{T}.cs index 209dec734a..9cccade88e 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlArray{T}.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlArray{T}.cs @@ -41,7 +41,7 @@ public override void ReplaceWith(SqlExpression expression) Values = replacingExpression.Values; } - internal override SqlArray Clone(SqlNodeCloneContext? context = null) => + internal override SqlArray Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new((T[]) t.Values.Clone())); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBetween.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBetween.cs index 0a0be40683..2e073d7b7f 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBetween.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBetween.cs @@ -53,7 +53,7 @@ public override void ReplaceWith(SqlExpression expression) expression = replacingExpression.Expression; } - internal override SqlBetween Clone(SqlNodeCloneContext? context = null) => + internal override SqlBetween Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.NodeType, t.expression.Clone(c), t.left.Clone(c), t.right.Clone(c))); @@ -70,4 +70,4 @@ public override void AcceptVisitor(ISqlVisitor visitor) visitor.Visit(this); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBinary.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBinary.cs index 7ae5e5a1e9..0c2b2879fd 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBinary.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlBinary.cs @@ -51,7 +51,7 @@ public override void ReplaceWith(SqlExpression expression) Right = replacingExpression.Right; } - internal override SqlBinary Clone(SqlNodeCloneContext? context = null) => + internal override SqlBinary Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.NodeType, t.Left.Clone(c), t.Right.Clone(c))); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCase.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCase.cs index c99dcaeea0..c079d994bc 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCase.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCase.cs @@ -103,7 +103,7 @@ public override void ReplaceWith(SqlExpression expression) cases.Add(pair); } - internal override SqlCase Clone(SqlNodeCloneContext? context = null) => + internal override SqlCase Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { var clone = new SqlCase(t.value?.Clone(c)); @@ -140,4 +140,4 @@ internal SqlCase(SqlExpression value) this.value = value; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCast.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCast.cs index a964c021fb..880cd3f32d 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCast.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCast.cs @@ -20,7 +20,7 @@ public override void ReplaceWith(SqlExpression expression) Type = replacingExpression.Type; } - internal override SqlCast Clone(SqlNodeCloneContext? context = null) => + internal override SqlCast Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Operand.Clone(c), t.Type)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCollate.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCollate.cs index 94b39a603d..6dff2d7d28 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCollate.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCollate.cs @@ -41,7 +41,7 @@ public override void ReplaceWith(SqlExpression expression) collation = replacingExpression.Collation; } - internal override SqlCollate Clone(SqlNodeCloneContext? context = null) => + internal override SqlCollate Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.operand.Clone(c), t.collation)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumn.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumn.cs index d77377e56d..72b8dcefd5 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumn.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumn.cs @@ -29,7 +29,7 @@ public override void ReplaceWith(SqlExpression expression) Name = replacingExpression.Name; } - internal override abstract SqlColumn Clone(SqlNodeCloneContext? context = null); + internal override abstract SqlColumn Clone(SqlNodeCloneContext context); // Constructor diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnRef.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnRef.cs index d1755c2f66..afa744b65d 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnRef.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnRef.cs @@ -23,7 +23,7 @@ public override void ReplaceWith(SqlExpression expression) SqlColumn = ArgumentValidator.EnsureArgumentIs(expression).SqlColumn; } - internal override SqlColumnRef Clone(SqlNodeCloneContext? context = null) => + internal override SqlColumnRef Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.SqlTable?.Clone(c), t.SqlColumn.Clone(c), t.Name)); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnStub.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnStub.cs index a4a49dbfdc..18d568a23d 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnStub.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlColumnStub.cs @@ -13,7 +13,7 @@ public class SqlColumnStub : SqlColumn { public SqlColumn Column { get; set; } - internal override SqlColumnStub Clone(SqlNodeCloneContext? context = null) => + internal override SqlColumnStub Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.SqlTable?.Clone(c), t.Column)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -33,4 +33,4 @@ private SqlColumnStub(SqlTable sqlTable, SqlColumn column) Column = column; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlComment.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlComment.cs index 1168b0256e..1dcbddb231 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlComment.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlComment.cs @@ -23,7 +23,7 @@ public override void ReplaceWith(SqlExpression expression) Text = replacingExpression.Text; } - internal override SqlComment Clone(SqlNodeCloneContext? context = null) => + internal override SqlComment Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Text)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlConcat.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlConcat.cs index 967618409b..2f12b1b0fc 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlConcat.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlConcat.cs @@ -14,16 +14,15 @@ namespace Xtensive.Sql.Dml [Serializable] public class SqlConcat : SqlExpressionList { - internal override SqlConcat Clone(SqlNodeCloneContext? context = null) + internal override SqlConcat Clone(SqlNodeCloneContext context) { - var ctx = context ?? new(); - if (ctx.NodeMapping.TryGetValue(this, out var value)) { + if (context.NodeMapping.TryGetValue(this, out var value)) { return (SqlConcat)value; } var expressionsClone = new List(expressions.Count); foreach (var e in expressions) - expressionsClone.Add(e.Clone(ctx)); + expressionsClone.Add(e.Clone(context)); var clone = new SqlConcat(expressionsClone); return clone; @@ -47,4 +46,4 @@ internal SqlConcat(IReadOnlyList expressions) { } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlContainer.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlContainer.cs index 9cd673500b..3cc825b4a1 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlContainer.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlContainer.cs @@ -27,7 +27,7 @@ public override void ReplaceWith(SqlExpression expression) Value = replacingExpression.Value; } - internal override SqlContainer Clone(SqlNodeCloneContext? context = null) => + internal override SqlContainer Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Value)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -41,4 +41,4 @@ internal SqlContainer(object value) Value = value; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCursor.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCursor.cs index d08a70a2ca..add06bf6c3 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCursor.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCursor.cs @@ -168,7 +168,7 @@ public override void ReplaceWith(SqlExpression expression) throw new NotImplementedException(); } - internal override SqlCursor Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlCursor Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCustomFunctionCall.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCustomFunctionCall.cs index 1bac097aa1..b63ac58a64 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCustomFunctionCall.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlCustomFunctionCall.cs @@ -28,7 +28,7 @@ public override void ReplaceWith(SqlExpression expression) Arguments = replacingExpression.Arguments; } - internal override SqlCustomFunctionCall Clone(SqlNodeCloneContext? context = null) => + internal override SqlCustomFunctionCall Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.FunctionType, t.Arguments.Select(o => o.Clone(c)).ToArray(t.Arguments.Count))); @@ -46,4 +46,4 @@ public SqlCustomFunctionCall(SqlCustomFunctionType sqlCustomFunctionType, params FunctionType = sqlCustomFunctionType; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDefaultValue.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDefaultValue.cs index aafc33eeff..7f199eaa6b 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDefaultValue.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDefaultValue.cs @@ -16,7 +16,7 @@ public override void ReplaceWith(SqlExpression expression) ArgumentValidator.EnsureArgumentIs(expression); } - internal override SqlDefaultValue Clone(SqlNodeCloneContext? context = null) => this; + internal override SqlDefaultValue Clone(SqlNodeCloneContext context) => this; internal SqlDefaultValue() : base(SqlNodeType.DefaultValue) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDynamicFilter.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDynamicFilter.cs index 59d7337484..d84abe225f 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDynamicFilter.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlDynamicFilter.cs @@ -16,7 +16,7 @@ public class SqlDynamicFilter : SqlExpression public IReadOnlyList Expressions { get; private set; } - internal override SqlDynamicFilter Clone(SqlNodeCloneContext? context = null) => + internal override SqlDynamicFilter Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Id, t.Expressions.Select(e => e.Clone(c)).ToArray())); public override void AcceptVisitor(ISqlVisitor visitor) => visitor.Visit(this); @@ -40,7 +40,7 @@ internal SqlDynamicFilter(object id, IReadOnlyList expressions) public class SqlTvpDynamicFilter(object id, IReadOnlyList expressions) : SqlDynamicFilter(id, expressions) { - internal override SqlTvpDynamicFilter Clone(SqlNodeCloneContext? context = null) => + internal override SqlTvpDynamicFilter Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Id, t.Expressions.Select(e => e.Clone(c)).ToArray())); } } diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs index 34b492be80..a205dcf7ae 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExpression.cs @@ -241,7 +241,9 @@ public static implicit operator SqlExpression(SqlSelect select) public sealed override bool Equals(object obj) => ReferenceEquals(this, obj); - internal override abstract SqlExpression Clone(SqlNodeCloneContext? context = null); + public override SqlExpression Clone() => Clone(new SqlNodeCloneContext()); + + internal override abstract SqlExpression Clone(SqlNodeCloneContext context); // Constructor diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs index b013e03778..9c5aad15a0 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs @@ -64,7 +64,7 @@ public override void ReplaceWith(SqlExpression expression) Operand = replacingExpression.Operand; } - internal override SqlExtract Clone(SqlNodeCloneContext? context) => + internal override SqlExtract Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.internalValue, t.typeMarker, t.Operand.Clone(c))); public override void AcceptVisitor(ISqlVisitor visitor) @@ -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/Dml/Expressions/SqlFunctionCall.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlFunctionCall.cs index c676212f16..1c5bd2a2b8 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlFunctionCall.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlFunctionCall.cs @@ -25,7 +25,7 @@ public override void ReplaceWith(SqlExpression expression) Arguments = replacingExpression.Arguments; } - internal override SqlFunctionCall Clone(SqlNodeCloneContext? context = null) => + internal override SqlFunctionCall Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.FunctionType, t.Arguments.Select(o => o.Clone(c)).ToArray(t.Arguments.Count))); @@ -45,4 +45,4 @@ internal SqlFunctionCall(SqlFunctionType functionType, params SqlExpression[] ar FunctionType = functionType; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLike.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLike.cs index 2a41e89880..43d8af9ad1 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLike.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLike.cs @@ -63,7 +63,7 @@ public override void ReplaceWith(SqlExpression expression) not = replacingExpression.Not; } - internal override SqlLike Clone(SqlNodeCloneContext? context = null) => + internal override SqlLike Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.expression.Clone(c), t.pattern.Clone(c), diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLiteral{T}.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLiteral{T}.cs index d5278e692c..0b8063f7f5 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLiteral{T}.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlLiteral{T}.cs @@ -32,7 +32,7 @@ public override void ReplaceWith(SqlExpression expression) Value = replacingExpression.Value; } - internal override SqlLiteral Clone(SqlNodeCloneContext? context = null) => + internal override SqlLiteral Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Value)); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMatch.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMatch.cs index aaa16b81d2..52dee1786b 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMatch.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMatch.cs @@ -59,7 +59,7 @@ public override void ReplaceWith(SqlExpression expression) unique = replacingExpression.Unique; } - internal override SqlMatch Clone(SqlNodeCloneContext? context = null) => + internal override SqlMatch Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.value.Clone(c), t.subQuery.Clone(c), t.unique, t.matchType)); @@ -77,4 +77,4 @@ public override void AcceptVisitor(ISqlVisitor visitor) visitor.Visit(this); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMetadata.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMetadata.cs index 1944be0298..c5761a077c 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMetadata.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlMetadata.cs @@ -23,7 +23,7 @@ public override void ReplaceWith(SqlExpression expression) Value = source.Value; } - internal override SqlMetadata Clone(SqlNodeCloneContext? context = null) => + internal override SqlMetadata Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Expression.Clone(c), t.Value)); public override void AcceptVisitor(ISqlVisitor visitor) => visitor.Visit(this); @@ -36,4 +36,4 @@ internal SqlMetadata(SqlExpression expression, object value) : base(SqlNodeType. Value = value; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNative.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNative.cs index 7a844b4b20..9ea609f3a5 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNative.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNative.cs @@ -21,7 +21,7 @@ public override void ReplaceWith(SqlExpression expression) Value = replacingExpression.Value; } - internal override SqlNative Clone(SqlNodeCloneContext? context = null) => + internal override SqlNative Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Value)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -279,4 +279,4 @@ internal SqlNative(string value) : base(SqlNodeType.Native) Value = value; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNextValue.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNextValue.cs index bdd56bf3b7..efe61b1acd 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNextValue.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNextValue.cs @@ -42,7 +42,7 @@ public override void ReplaceWith(SqlExpression expression) increment = replacingExpression.Increment; } - internal override SqlNextValue Clone(SqlNodeCloneContext? context = null) => + internal override SqlNextValue Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.sequence, t.increment)); internal SqlNextValue(Sequence sequence) : base(SqlNodeType.NextValue) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNull.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNull.cs index 257a40f463..ddc28c2717 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNull.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlNull.cs @@ -15,7 +15,7 @@ public override void ReplaceWith(SqlExpression expression) _ = ArgumentValidator.EnsureArgumentIs(expression); } - internal override SqlNull Clone(SqlNodeCloneContext? context = null) => this; + internal override SqlNull Clone(SqlNodeCloneContext context) => this; internal SqlNull() : base(SqlNodeType.Null) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlParameterRef.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlParameterRef.cs index 610e8097a4..e94e2c0682 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlParameterRef.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlParameterRef.cs @@ -23,7 +23,7 @@ public override void ReplaceWith(SqlExpression expression) Parameter = replacingExpression.Parameter; } - internal override SqlParameterRef Clone(SqlNodeCloneContext? context = null) => + internal override SqlParameterRef Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Name ?? t.Parameter)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlPlaceholder.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlPlaceholder.cs index a939a59532..e1f6477026 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlPlaceholder.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlPlaceholder.cs @@ -12,7 +12,7 @@ public class SqlPlaceholder : SqlExpression { public object Id { get; private set; } - internal override SqlPlaceholder Clone(SqlNodeCloneContext? context = null) => + internal override SqlPlaceholder Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Id)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -34,4 +34,4 @@ internal SqlPlaceholder(object id) Id = id; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRound.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRound.cs index 16abe003aa..230e69041f 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRound.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRound.cs @@ -26,7 +26,7 @@ public override void ReplaceWith(SqlExpression expression) Mode = replacingExpression.Mode; } - internal override SqlRound Clone(SqlNodeCloneContext? context = null) => + internal override SqlRound Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Argument.Clone(c), t.Length?.Clone(c), t.Type, t.Mode)); @@ -46,4 +46,4 @@ internal SqlRound(SqlExpression argument, SqlExpression length, TypeCode type, M Mode = mode; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRow.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRow.cs index 8050c6b4a0..5e91812777 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRow.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRow.cs @@ -12,10 +12,9 @@ namespace Xtensive.Sql.Dml [Serializable] public class SqlRow: SqlExpressionList { - internal override SqlRow Clone(SqlNodeCloneContext? context = null) + internal override SqlRow Clone(SqlNodeCloneContext context) { - SqlNodeCloneContext ctx = context ?? new(); - return (ctx.TryGet(this) as SqlRow) ?? new(expressions.Select(e => e.Clone(ctx)).ToArray()); + return (context.TryGet(this) as SqlRow) ?? new(expressions.Select(e => e.Clone(context)).ToArray()); } public override void ReplaceWith(SqlExpression expression) => @@ -30,4 +29,4 @@ internal SqlRow(IReadOnlyList expressions) { } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRowNumber.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRowNumber.cs index 87a32f1f4b..63bc8156a9 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRowNumber.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlRowNumber.cs @@ -12,7 +12,7 @@ public class SqlRowNumber : SqlExpression { public SqlOrderCollection OrderBy { get; private set; } - internal override SqlRowNumber Clone(SqlNodeCloneContext? context = null) => + internal override SqlRowNumber Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { var clone = new SqlRowNumber(); foreach (SqlOrder so in t.OrderBy) @@ -42,4 +42,4 @@ internal SqlRowNumber() OrderBy = new SqlOrderCollection(); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlSubQuery.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlSubQuery.cs index a6d750f5d8..7ce6605345 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlSubQuery.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlSubQuery.cs @@ -31,7 +31,7 @@ public override void ReplaceWith(SqlExpression expression) query = replacingExpression.Query; } - internal override SqlSubQuery Clone(SqlNodeCloneContext? context = null) => + internal override SqlSubQuery Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.query is SqlSelect select ? select.Clone(c) @@ -50,4 +50,4 @@ internal SqlSubQuery(ISqlQueryExpression query) this.query = query; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTableColumn.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTableColumn.cs index 728d622dc2..e4031ffdb5 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTableColumn.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTableColumn.cs @@ -22,7 +22,7 @@ public override void ReplaceWith(SqlExpression expression) base.ReplaceWith(expression); } - internal override SqlTableColumn Clone(SqlNodeCloneContext? context = null) => + internal override SqlTableColumn Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { var table = t.SqlTable; if (c.NodeMapping.TryGetValue(t.SqlTable, out var clonedTable)) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTrim.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTrim.cs index c2cd20acde..7b2512d5aa 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTrim.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlTrim.cs @@ -49,7 +49,7 @@ public override void ReplaceWith(SqlExpression expression) trimType = replacingExpression.TrimType; } - internal override SqlTrim Clone(SqlNodeCloneContext? context = null) => + internal override SqlTrim Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.expression.Clone(c), t.trimCharacters, t.trimType)); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUnary.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUnary.cs index cfb4c99e95..d5ab661dc4 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUnary.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUnary.cs @@ -26,7 +26,7 @@ public override void ReplaceWith(SqlExpression expression) Operand = replacingExpression.Operand; } - internal override SqlUnary Clone(SqlNodeCloneContext? context = null) => + internal override SqlUnary Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.NodeType, t.Operand.Clone(c))); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserColumn.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserColumn.cs index c68800474b..2dd112dfb4 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserColumn.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserColumn.cs @@ -27,7 +27,7 @@ public override void ReplaceWith(SqlExpression expression) this.expression = replacingExpression.Expression; } - internal override SqlUserColumn Clone(SqlNodeCloneContext? context = null) => + internal override SqlUserColumn Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.expression.Clone(c))); // Constructor diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserFunctionCall.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserFunctionCall.cs index ffadb4d86d..0dcadaf3ee 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserFunctionCall.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlUserFunctionCall.cs @@ -25,7 +25,7 @@ public override void ReplaceWith(SqlExpression expression) Arguments = replacingExpression.Arguments; } - internal override SqlUserFunctionCall Clone(SqlNodeCloneContext? context = null) => + internal override SqlUserFunctionCall Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Name, t.Arguments.Select(o => o.Clone(c)).ToArray(t.Arguments.Count))); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariable.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariable.cs index d609b71520..1b9aa2c012 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariable.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariable.cs @@ -42,7 +42,7 @@ public override void ReplaceWith(SqlExpression expression) name = replacingExpression.Name; } - internal override SqlVariable Clone(SqlNodeCloneContext? context = null) => + internal override SqlVariable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.name, t.type)); internal SqlVariable(string name, SqlValueType type) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariant.cs b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariant.cs index 7a4b3f5a93..ad484b8967 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariant.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlVariant.cs @@ -22,7 +22,7 @@ public override void ReplaceWith(SqlExpression expression) Id = replacingExpression.Id; } - internal override SqlVariant Clone(SqlNodeCloneContext? context = null) => + internal override SqlVariant Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Id, t.Main.Clone(c), t.Alternative.Clone(c))); @@ -39,4 +39,4 @@ internal SqlVariant(object id, SqlExpression main, SqlExpression alternative) Id = id; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlFastFirstRowsHint.cs b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlFastFirstRowsHint.cs index d5c5c79a9e..4a478f6d9b 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlFastFirstRowsHint.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlFastFirstRowsHint.cs @@ -15,7 +15,7 @@ public class SqlFastFirstRowsHint : SqlHint /// The row amount. public int Amount { get; private set; } - internal override SqlFastFirstRowsHint Clone(SqlNodeCloneContext? context = null) => + internal override SqlFastFirstRowsHint Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Amount)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlForceJoinOrderHint.cs b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlForceJoinOrderHint.cs index 17c05c124d..5a3d8e6bfa 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlForceJoinOrderHint.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlForceJoinOrderHint.cs @@ -17,9 +17,9 @@ public class SqlForceJoinOrderHint : SqlHint /// public IReadOnlyList Tables { get; } - internal override SqlForceJoinOrderHint Clone(SqlNodeCloneContext? context = null) => + internal override SqlForceJoinOrderHint Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => - new(t.Tables?.Select(table => table.Clone()).ToArray(t.Tables.Count))); + new(t.Tables?.Select(table => (SqlTable) table.Clone()).ToArray(t.Tables.Count))); public override void AcceptVisitor(ISqlVisitor visitor) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlHint.cs b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlHint.cs index 22564252ed..3643dd3205 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlHint.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlHint.cs @@ -9,7 +9,7 @@ namespace Xtensive.Sql.Dml [Serializable] public abstract class SqlHint : SqlNode { - internal abstract override SqlHint Clone(SqlNodeCloneContext? context = null); + internal abstract override SqlHint Clone(SqlNodeCloneContext context); /// /// Initializes a new instance of the class. @@ -18,4 +18,4 @@ protected SqlHint() : base(SqlNodeType.Hint) { } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlIndexHint.cs b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlIndexHint.cs index e451f0ab1c..cccce5f3ac 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlIndexHint.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlIndexHint.cs @@ -13,7 +13,7 @@ public class SqlIndexHint : SqlHint public SqlTableRef From { get; } - internal override SqlIndexHint Clone(SqlNodeCloneContext? context = null) => + internal override SqlIndexHint Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.IndexName, t.From)); public override void AcceptVisitor(ISqlVisitor visitor) @@ -27,4 +27,4 @@ internal SqlIndexHint(string indexName, SqlTableRef from) From = from; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlJoinHint.cs b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlJoinHint.cs index 647aa1bbcf..a3b76edb42 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlJoinHint.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlJoinHint.cs @@ -23,8 +23,8 @@ public class SqlJoinHint : SqlHint /// public SqlTable Table { get; private set; } - internal override SqlJoinHint Clone(SqlNodeCloneContext? context = null) => - context.GetOrAdd(this, static (t, c) => new(t.Method, t.Table.Clone())); + internal override SqlJoinHint Clone(SqlNodeCloneContext context) => + context.GetOrAdd(this, static (t, c) => new(t.Method, (SqlTable) t.Table.Clone())); public override void AcceptVisitor(ISqlVisitor visitor) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlNativeHint.cs b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlNativeHint.cs index 9199a5ee26..f40e3324ed 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlNativeHint.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Hints/SqlNativeHint.cs @@ -15,7 +15,7 @@ public class SqlNativeHint : SqlHint /// The hint text. public string HintText { get; private set; } - internal override SqlNativeHint Clone(SqlNodeCloneContext? context = null) => + internal override SqlNativeHint Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.HintText)); public override void AcceptVisitor(ISqlVisitor visitor) diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlContainsTable.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlContainsTable.cs index 7e8d126bcc..9f7ec72907 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlContainsTable.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlContainsTable.cs @@ -18,7 +18,7 @@ public class SqlContainsTable: SqlTable, ISqlQueryExpression public SqlExpression TopNByRank { get; private set; } - internal override SqlContainsTable Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlContainsTable Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { @@ -96,4 +96,4 @@ internal SqlContainsTable(DataTable dataTable, SqlExpression searchCondition, IC columns = new SqlTableColumnCollection(columnNames.Select(columnName => SqlDml.TableColumn(this, columnName)).ToArray(columnNames.Count)); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlFragment.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlFragment.cs index bfff7cbc80..69ff56ae38 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlFragment.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlFragment.cs @@ -10,7 +10,7 @@ public class SqlFragment : SqlNode, ISqlCompileUnit { public SqlExpression Expression { get; private set; } - internal override SqlFragment Clone(SqlNodeCloneContext? context = null) => + internal override SqlFragment Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Expression.Clone(c))); public override void AcceptVisitor(ISqlVisitor visitor) @@ -27,4 +27,4 @@ internal SqlFragment(SqlExpression expression) Expression = expression; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlFreeTextTable.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlFreeTextTable.cs index 4bf2c3a582..62a486baa1 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlFreeTextTable.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlFreeTextTable.cs @@ -24,7 +24,7 @@ public class SqlFreeTextTable : SqlTable, ISqlQueryExpression public SqlExpression TopNByRank { get; private set; } - internal override SqlFreeTextTable Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlFreeTextTable Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { @@ -101,4 +101,4 @@ internal SqlFreeTextTable(DataTable dataTable, SqlExpression freeText, ICollecti columns = new SqlTableColumnCollection(columnNames.Select(columnName => SqlDml.TableColumn(this, columnName)).ToArray(columnNames.Count)); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlJoinExpression.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlJoinExpression.cs index aa6bf3cb10..859151df32 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlJoinExpression.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlJoinExpression.cs @@ -34,7 +34,7 @@ public class SqlJoinExpression : SqlNode /// The expression. public SqlExpression Expression { get; private set; } - internal override SqlJoinExpression Clone(SqlNodeCloneContext? context = null) => + internal override SqlJoinExpression Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.JoinType, t.Left?.Clone(c), diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlJoinedTable.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlJoinedTable.cs index 80d7caac3e..1e73fd9beb 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlJoinedTable.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlJoinedTable.cs @@ -29,7 +29,7 @@ public SqlJoinExpression JoinExpression /// Aliased columns. public SqlColumnCollection AliasedColumns { get; init; } - internal override SqlJoinedTable Clone(SqlNodeCloneContext? context = null) => + internal override SqlJoinedTable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.joinExpression.Clone(c)) { AliasedColumns = new(t.AliasedColumns) diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlOrder.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlOrder.cs index 25a176607e..bd4bc00b4d 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlOrder.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlOrder.cs @@ -30,7 +30,7 @@ public class SqlOrder : SqlNode /// if ascending; otherwise, . public bool Ascending { get; private set; } - internal override SqlOrder Clone(SqlNodeCloneContext? context = null) => + internal override SqlOrder Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => t.Expression is null ? new(t.Position, t.Ascending) diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlQueryRef.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlQueryRef.cs index 0e84a907b7..48b2986e68 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlQueryRef.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlQueryRef.cs @@ -22,7 +22,7 @@ public ISqlQueryExpression Query get { return query; } } - internal override SqlQueryRef Clone(SqlNodeCloneContext? context = null) => + internal override SqlQueryRef Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => t.query is SqlSelect ss ? new(ss.Clone(c), t.Name) @@ -80,4 +80,4 @@ internal SqlQueryRef(ISqlQueryExpression query, string name) columns = new SqlTableColumnCollection(queryColumns); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlTable.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlTable.cs index 9ed3b754d4..ad33aa6756 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlTable.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlTable.cs @@ -139,7 +139,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - internal abstract override SqlTable Clone(SqlNodeCloneContext? context = null); + internal abstract override SqlTable Clone(SqlNodeCloneContext context); // Constructors diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlTableRef.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlTableRef.cs index 3f7fe37666..511e486c73 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlTableRef.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlTableRef.cs @@ -30,13 +30,10 @@ public class SqlTableRef : SqlTable /// The table. public DataTable DataTable { get; private set; } - internal override SqlTableRef Clone(SqlNodeCloneContext? context = null) - { - var ctx = context ?? new(); - return ctx.NodeMapping.TryGetValue(this, out var clone) + internal override SqlTableRef Clone(SqlNodeCloneContext context) => + context.NodeMapping.TryGetValue(this, out var clone) ? (SqlTableRef) clone - : CreateClone(ctx); - } + : CreateClone(context); private SqlTableRef CreateClone(SqlNodeCloneContext context) { @@ -73,4 +70,4 @@ internal SqlTableRef(DataTable dataTable, string name, params string[] columnNam columns = new SqlTableColumnCollection(tableColumns); } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlAssignment.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlAssignment.cs index 1bde2ba1b2..67c295290b 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlAssignment.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlAssignment.cs @@ -30,7 +30,7 @@ public SqlExpression Right { } } - internal override SqlAssignment Clone(SqlNodeCloneContext? context = null) => + internal override SqlAssignment Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new((ISqlLValue)t.left.Clone(), t.right.Clone(c))); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBatch.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBatch.cs index 9989609f1f..69c5a639a9 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBatch.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBatch.cs @@ -99,7 +99,7 @@ IEnumerator IEnumerable.GetEnumerator() #endregion - internal override SqlBatch Clone(SqlNodeCloneContext? context = null) => + internal override SqlBatch Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { var clone = new SqlBatch(); foreach (SqlStatement s in t.statements) @@ -121,4 +121,4 @@ internal SqlBatch() { } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBreak.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBreak.cs index 9a28fbd875..43fa616ea2 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBreak.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlBreak.cs @@ -9,7 +9,7 @@ namespace Xtensive.Sql.Dml [Serializable] public class SqlBreak : SqlStatement { - internal override SqlBreak Clone(SqlNodeCloneContext? context = null) => this; + internal override SqlBreak Clone(SqlNodeCloneContext context) => this; internal SqlBreak() : base(SqlNodeType.Break) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlCloseCursor.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlCloseCursor.cs index 5ea79ebfc8..6cbedc1c78 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlCloseCursor.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlCloseCursor.cs @@ -21,7 +21,7 @@ public SqlCursor Cursor { } } - internal override SqlCloseCursor Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlCloseCursor Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlContinue.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlContinue.cs index 39afc509bc..3b1ef9a5cc 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlContinue.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlContinue.cs @@ -9,7 +9,7 @@ namespace Xtensive.Sql.Dml [Serializable] public class SqlContinue : SqlStatement { - internal override SqlContinue Clone(SqlNodeCloneContext? context = null) => this; + internal override SqlContinue Clone(SqlNodeCloneContext context) => this; internal SqlContinue() : base(SqlNodeType.Continue) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareCursor.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareCursor.cs index ccfc4a765c..9508ff5bee 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareCursor.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareCursor.cs @@ -17,7 +17,7 @@ public SqlCursor Cursor { } } - internal override SqlDeclareCursor Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlDeclareCursor Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareVariable.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareVariable.cs index 7c5f7243b9..593782e5e8 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareVariable.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDeclareVariable.cs @@ -15,7 +15,7 @@ public class SqlDeclareVariable : SqlStatement, ISqlCompileUnit /// The variable. public SqlVariable Variable { get; } - internal override SqlDeclareVariable Clone(SqlNodeCloneContext? context = null) => + internal override SqlDeclareVariable Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.Variable)); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDelete.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDelete.cs index 1e8c93c47e..8c9504c00f 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDelete.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlDelete.cs @@ -60,7 +60,7 @@ public SqlExpression Limit set { limit = value; } } - internal override SqlDelete Clone(SqlNodeCloneContext? context = null) => + internal override SqlDelete Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { SqlDelete clone = new SqlDelete(); if (t.Delete != null) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlFetch.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlFetch.cs index fe094037f5..1d460b6cc5 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlFetch.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlFetch.cs @@ -53,7 +53,7 @@ public IList Targets get { return targets; } } - internal override SqlFetch Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlFetch Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { @@ -72,4 +72,4 @@ internal SqlFetch(SqlFetchOption option, SqlExpression rowCount, SqlCursor curso this.rowCount = rowCount; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlIf.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlIf.cs index 7e4927098d..fd02a2399c 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlIf.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlIf.cs @@ -56,7 +56,7 @@ public SqlStatement False { } } - internal override SqlIf Clone(SqlNodeCloneContext? context = null) => + internal override SqlIf Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.condition.Clone(c), t.trueStatement.Clone(c), diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlInsert.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlInsert.cs index 46b23986c7..1f997ddf81 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlInsert.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlInsert.cs @@ -37,7 +37,7 @@ public class SqlInsert : SqlQueryStatement, ISqlCompileUnit /// public SqlSelect From { get; set; } - internal override SqlInsert Clone(SqlNodeCloneContext? context = null) => + internal override SqlInsert Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { var clone = new SqlInsert { Into = t.Into?.Clone(c), diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlOpenCursor.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlOpenCursor.cs index f29568e3d8..ba33a97afe 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlOpenCursor.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlOpenCursor.cs @@ -21,7 +21,7 @@ public SqlCursor Cursor { } } - internal override SqlOpenCursor Clone(SqlNodeCloneContext? context = null) => throw new NotImplementedException(); + internal override SqlOpenCursor Clone(SqlNodeCloneContext context) => throw new NotImplementedException(); public override void AcceptVisitor(ISqlVisitor visitor) { diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlQueryExpression.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlQueryExpression.cs index 5b9434c88d..f1a32e6a70 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlQueryExpression.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlQueryExpression.cs @@ -32,7 +32,7 @@ public bool All get { return all; } } - internal override SqlQueryExpression Clone(SqlNodeCloneContext? context = null) => + internal override SqlQueryExpression Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => new(t.NodeType, (ISqlQueryExpression)((SqlNode) t.left).Clone(c), @@ -111,4 +111,4 @@ internal SqlQueryExpression(SqlNodeType nodeType, ISqlQueryExpression left, ISql this.all = all; } } -} \ No newline at end of file +} diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlSelect.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlSelect.cs index 85482ff766..9294631612 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlSelect.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlSelect.cs @@ -144,7 +144,7 @@ public SqlExpression Offset /// public bool HasOffset => Offset is not null; - internal override SqlSelect Clone(SqlNodeCloneContext? context = null ) => + internal override SqlSelect Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { SqlSelect clone = new(t.From?.Clone(c)); diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlStatementBlock.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlStatementBlock.cs index 1d688e70d5..ca4e7f5720 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlStatementBlock.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlStatementBlock.cs @@ -98,7 +98,7 @@ public IEnumerator GetEnumerator() #endregion - internal override SqlStatementBlock Clone(SqlNodeCloneContext? context = null) => + internal override SqlStatementBlock Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { SqlStatementBlock clone = new(); foreach (SqlStatement s in t.statements) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlUpdate.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlUpdate.cs index 31958ff3e0..8f778573a9 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlUpdate.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlUpdate.cs @@ -67,7 +67,7 @@ public SqlExpression Limit set { limit = value; } } - internal override SqlUpdate Clone(SqlNodeCloneContext? context = null) => + internal override SqlUpdate Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { var clone = new SqlUpdate(); if (t.update != null) diff --git a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlWhile.cs b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlWhile.cs index 0cd8c88032..eaba50dde4 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlWhile.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/Statements/SqlWhile.cs @@ -43,7 +43,7 @@ public SqlExpression Condition { } } - internal override SqlWhile Clone(SqlNodeCloneContext? context = null) => + internal override SqlWhile Clone(SqlNodeCloneContext context) => context.GetOrAdd(this, static (t, c) => { SqlWhile clone = new(t.condition.Clone(c)); if (t.statement!=null) diff --git a/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs b/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs index 440064f003..42d9afcf47 100644 --- a/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs +++ b/Orm/Xtensive.Orm/Sql/Internals/SqlNodeCloneContext.cs @@ -7,27 +7,20 @@ namespace Xtensive.Sql { - internal readonly struct SqlNodeCloneContext + internal readonly struct SqlNodeCloneContext() { - private readonly Dictionary nodeMapping = new(); - - public Dictionary NodeMapping => nodeMapping; + public Dictionary NodeMapping { get; } = new(); public T TryGet(T node) where T : SqlNode => NodeMapping.TryGetValue(node, out var clone) ? (T) clone : null; - - public SqlNodeCloneContext() - { - } } internal static class SqlNodeCloneContextExtensions { - public static T GetOrAdd(this SqlNodeCloneContext? context, T node, Func factory) where T : SqlNode + public static T GetOrAdd(this SqlNodeCloneContext ctx, T node, Func factory) where T : SqlNode { - var ctx = context ?? new(); if (ctx.NodeMapping.TryGetValue(node, out var clone)) { return (T) clone; } diff --git a/Orm/Xtensive.Orm/Sql/SqlNode.cs b/Orm/Xtensive.Orm/Sql/SqlNode.cs index 7272b557ad..2337c70e21 100644 --- a/Orm/Xtensive.Orm/Sql/SqlNode.cs +++ b/Orm/Xtensive.Orm/Sql/SqlNode.cs @@ -18,9 +18,17 @@ public abstract class SqlNode : ISqlNode /// The type of the node. public SqlNodeType NodeType { get; internal set; } - object ICloneable.Clone() => Clone(); + /// + /// Creates a new object that is a copy of the current instance. + /// + /// + /// A new object that is a copy of this instance. + /// + public virtual SqlNode Clone() => Clone(new SqlNodeCloneContext()); + + object ICloneable.Clone() => Clone(new SqlNodeCloneContext()); - internal abstract SqlNode Clone(SqlNodeCloneContext? context = null); + internal abstract SqlNode Clone(SqlNodeCloneContext context); internal SqlNode(SqlNodeType nodeType) { diff --git a/Orm/Xtensive.Orm/Sql/SqlStatement.cs b/Orm/Xtensive.Orm/Sql/SqlStatement.cs index fffc12e867..c02c5291b4 100644 --- a/Orm/Xtensive.Orm/Sql/SqlStatement.cs +++ b/Orm/Xtensive.Orm/Sql/SqlStatement.cs @@ -12,7 +12,7 @@ namespace Xtensive.Sql [Serializable] public abstract class SqlStatement : SqlNode { - internal abstract override SqlStatement Clone(SqlNodeCloneContext? context = null); + internal abstract override SqlStatement Clone(SqlNodeCloneContext context); protected SqlStatement(SqlNodeType nodeType) : base(nodeType) { diff --git a/Version.props b/Version.props index d7593b4aaf..e14d1572a1 100644 --- a/Version.props +++ b/Version.props @@ -2,7 +2,7 @@ - 7.2.178 + 7.2.179 servicetitan