1- // Copyright (C) 2009-2021 Xtensive LLC.
1+ // Copyright (C) 2009-2025 Xtensive LLC.
22// This code is distributed under MIT license terms.
33// See the License.txt file in the project root for more information.
44// Created by: Denis Krjuchkov
@@ -23,6 +23,9 @@ public class DriverFactory : SqlDriverFactory
2323 {
2424 private const string DatabaseAndSchemaQuery = "select current_database(), current_schema()" ;
2525
26+ private readonly static bool InfinityAliasForDatesEnabled ;
27+ private readonly static bool LegacyTimestamptBehaviorEnabled ;
28+
2629 /// <inheritdoc/>
2730 [ SecuritySafeCritical ]
2831 protected override string BuildConnectionString ( UrlInfo url )
@@ -44,9 +47,6 @@ protected override string BuildConnectionString(UrlInfo url)
4447 builder . Username = url . User ;
4548 builder . Password = url . Password ;
4649 }
47- else {
48- builder . IntegratedSecurity = true ;
49- }
5050
5151 // custom options
5252 foreach ( var param in url . Params ) {
@@ -67,13 +67,18 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf
6767 OpenConnectionFast ( connection , configuration , false ) . GetAwaiter ( ) . GetResult ( ) ;
6868 var version = GetVersion ( configuration , connection ) ;
6969 var defaultSchema = GetDefaultSchema ( connection ) ;
70- return CreateDriverInstance ( connectionString , version , defaultSchema ) ;
70+ var defaultTimeZoneInfo = PostgreSqlHelper . GetTimeZoneInfoForServerTimeZone ( connection . Timezone ) ;
71+ return CreateDriverInstance ( connectionString , version , defaultSchema , defaultTimeZoneInfo ) ;
7172 }
7273
7374 /// <inheritdoc/>
7475 protected override async Task < SqlDriver > CreateDriverAsync (
7576 string connectionString , SqlDriverConfiguration configuration , CancellationToken token )
7677 {
78+ // these settings needed to be read before any connection happens
79+ var useInfinityAliasForDateTime = InfinityAliasForDatesEnabled ;
80+ var legacyTimestampBehavior = LegacyTimestamptBehaviorEnabled ;
81+
7782 var connection = new NpgsqlConnection ( connectionString ) ;
7883 await using ( connection . ConfigureAwait ( false ) ) {
7984 if ( configuration . DbConnectionAccessors . Count > 0 )
@@ -82,7 +87,8 @@ protected override async Task<SqlDriver> CreateDriverAsync(
8287 await OpenConnectionFast ( connection , configuration , true , token ) . ConfigureAwait ( false ) ;
8388 var version = GetVersion ( configuration , connection ) ;
8489 var defaultSchema = await GetDefaultSchemaAsync ( connection , token : token ) . ConfigureAwait ( false ) ;
85- return CreateDriverInstance ( connectionString , version , defaultSchema ) ;
90+ var defaultTimeZoneInfo = PostgreSqlHelper . GetTimeZoneInfoForServerTimeZone ( connection . Timezone ) ;
91+ return CreateDriverInstance ( connectionString , version , defaultSchema , defaultTimeZoneInfo ) ;
8692 }
8793 }
8894
@@ -95,7 +101,8 @@ private static Version GetVersion(SqlDriverConfiguration configuration, NpgsqlCo
95101 }
96102
97103 private static SqlDriver CreateDriverInstance (
98- string connectionString , Version version , DefaultSchemaInfo defaultSchema )
104+ string connectionString , Version version , DefaultSchemaInfo defaultSchema ,
105+ TimeZoneInfo defaultTimeZone )
99106 {
100107 var coreServerInfo = new CoreServerInfo {
101108 ServerVersion = version ,
@@ -105,20 +112,26 @@ private static SqlDriver CreateDriverInstance(
105112 DefaultSchemaName = defaultSchema . Schema ,
106113 } ;
107114
115+ var pgsqlServerInfo = new PostgreServerInfo ( ) {
116+ InfinityAliasForDatesEnabled = InfinityAliasForDatesEnabled ,
117+ LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled ,
118+ DefaultTimeZone = defaultTimeZone
119+ } ;
120+
108121 if ( version . Major < 8 || ( version . Major == 8 && version . Minor < 3 ) ) {
109122 throw new NotSupportedException ( Strings . ExPostgreSqlBelow83IsNotSupported ) ;
110123 }
111124
112125 // We support 8.3, 8.4 and any 9.0+
113126
114127 return version . Major switch {
115- 8 when version . Minor == 3 => new v8_3 . Driver ( coreServerInfo ) ,
116- 8 when version . Minor > 3 => new v8_4 . Driver ( coreServerInfo ) ,
117- 9 when version . Minor == 0 => new v9_0 . Driver ( coreServerInfo ) ,
118- 9 when version . Minor > 0 => new v9_1 . Driver ( coreServerInfo ) ,
119- 10 => new v10_0 . Driver ( coreServerInfo ) ,
120- 11 => new v10_0 . Driver ( coreServerInfo ) ,
121- _ => new v12_0 . Driver ( coreServerInfo )
128+ 8 when version . Minor == 3 => new v8_3 . Driver ( coreServerInfo , pgsqlServerInfo ) ,
129+ 8 when version . Minor > 3 => new v8_4 . Driver ( coreServerInfo , pgsqlServerInfo ) ,
130+ 9 when version . Minor == 0 => new v9_0 . Driver ( coreServerInfo , pgsqlServerInfo ) ,
131+ 9 when version . Minor > 0 => new v9_1 . Driver ( coreServerInfo , pgsqlServerInfo ) ,
132+ 10 => new v10_0 . Driver ( coreServerInfo , pgsqlServerInfo ) ,
133+ 11 => new v10_0 . Driver ( coreServerInfo , pgsqlServerInfo ) ,
134+ _ => new v12_0 . Driver ( coreServerInfo , pgsqlServerInfo )
122135 } ;
123136 }
124137
@@ -188,5 +201,58 @@ await SqlHelper.NotifyConnectionInitializingAsync(accessors,
188201 }
189202 }
190203 }
204+
205+ #region Helpers
206+
207+ private static bool SetOrGetExistingDisableInfinityAliasForDatesSwitch ( bool valueToSet ) =>
208+ GetSwitchValueOrSet ( Orm . PostgreSql . WellKnown . DateTimeToInfinityConversionSwitchName , valueToSet ) ;
209+
210+ private static bool SetOrGetExistingLegacyTimeStampBehaviorSwitch ( bool valueToSet ) =>
211+ GetSwitchValueOrSet ( Orm . PostgreSql . WellKnown . LegacyTimestampBehaviorSwitchName , valueToSet ) ;
212+
213+ private static bool GetSwitchValueOrSet ( string switchName , bool valueToSet )
214+ {
215+ if ( ! AppContext . TryGetSwitch ( switchName , out var currentValue ) ) {
216+ AppContext . SetSwitch ( switchName , valueToSet ) ;
217+ return valueToSet ;
218+ }
219+ else {
220+ return currentValue ;
221+ }
222+ }
223+
224+ #endregion
225+
226+ static DriverFactory ( )
227+ {
228+ // Starging from Npgsql 6.0 they broke compatibility by forcefully replacing
229+ // DateTime.MinValue/MaxValue of parameters with -Infinity and Infinity values.
230+ // This new "feature", though doesn't affect reading/writing of values and equality/inequality
231+ // filters, breaks some of operations such as parts extraction, default values for columns
232+ // (which are constants and declared on high levels of abstraction) and some others.
233+
234+ // We turn it off to make current code work as before and make current data of
235+ // the user be compatible with algorighms as long as possible.
236+ // But if the user sets the switch then we work with what we have.
237+ // Usage of such aliases makes us to create extra statements in SQL queries to provide
238+ // the same results the queries which are already written, which may make queries a bit slower.
239+
240+ // DO NOT REPLACE method call with constant value when debugging, CHANGE THE PARAMETER VALUE.
241+ InfinityAliasForDatesEnabled = ! SetOrGetExistingDisableInfinityAliasForDatesSwitch ( valueToSet : true ) ;
242+
243+ // Legacy timestamp behavoir turns off certain parameter value binding requirements
244+ // and makes Npgsql work like v4 or older.
245+ // Current behavior require manual specification of unspecified kind for DateTime values,
246+ // because Local or Utc kind now meand that underlying type of value to Timestamp without time zone
247+ // and Timestamp with time zone respectively.
248+ // It also affects DateTimeOffsets, now there is a requirement to move timezone of value to Utc
249+ // this forces us to use only local timezone when reading values, which basically ignores
250+ // Postgre's setting SET TIME ZONE for database session.
251+
252+ // We have to use current mode, not the legacy one, because there is a chance of legacy mode elimination.
253+
254+ // DO NOT REPLACE method call with constant value when debugging, CHANGE THE PARAMETER VALUE.
255+ LegacyTimestamptBehaviorEnabled = SetOrGetExistingLegacyTimeStampBehaviorSwitch ( valueToSet : false ) ;
256+ }
191257 }
192258}
0 commit comments