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