diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 01f4de3352..4d456aa09c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -963,6 +963,11 @@ + + + ILLink.Substitutions.xml + Resources\ILLink.Substitutions.Windows.xml + @@ -1005,6 +1010,11 @@ + + + ILLink.Substitutions.xml + Resources\ILLink.Substitutions.Unix.xml + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index ce5e960f8d..b1290ea526 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -31,13 +31,6 @@ namespace Microsoft.Data.SqlClient [DesignerCategory("")] public sealed partial class SqlConnection : DbConnection, ICloneable { - private enum CultureCheckState : uint - { - Unknown = 0, - Standard = 1, - Invariant = 2 - } - private bool _AsyncCommandInProgress; // SQLStatistics support @@ -75,9 +68,6 @@ private enum CultureCheckState : uint // using SqlConnection.Open() method. internal bool _applyTransientFaultHandling = false; - // status of invariant culture environment check - private static CultureCheckState _cultureCheckState; - // System column encryption key store providers are added by default private static readonly Dictionary s_systemColumnEncryptionKeyStoreProviders = new(capacity: 3, comparer: StringComparer.OrdinalIgnoreCase) @@ -1956,48 +1946,9 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec { SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; - if (_cultureCheckState != CultureCheckState.Standard) + if (LocalAppContextSwitches.GlobalizationInvariantMode) { - // .NET Core 2.0 and up supports a Globalization Invariant Mode to reduce the size of - // required libraries for applications which don't need globalization support. SqlClient - // requires those libraries for core functionality and will throw exceptions later if they - // are not present. Throwing on open with a meaningful message helps identify the issue. - if (_cultureCheckState == CultureCheckState.Unknown) - { - // check if invariant state has been set by appcontext switch directly - if (AppContext.TryGetSwitch("System.Globalization.Invariant", out bool isEnabled) && isEnabled) - { - _cultureCheckState = CultureCheckState.Invariant; - } - else - { - // check if invariant state has been set through environment variables - string envValue = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); - if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) - { - _cultureCheckState = CultureCheckState.Invariant; - } - else - { - // if it hasn't been manually set it could still apply if the os doesn't have - // icu libs installed or is a native binary with icu support trimmed away - // netcore 3.1 to net5 do not throw in attempting to create en-us in inariant mode - // net6 and greater will throw so catch and infer invariant mode from the exception - try - { - _cultureCheckState = CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") ? CultureCheckState.Invariant : CultureCheckState.Standard; - } - catch (CultureNotFoundException) - { - _cultureCheckState = CultureCheckState.Invariant; - } - } - } - } - if (_cultureCheckState == CultureCheckState.Invariant) - { - throw SQL.GlobalizationInvariantModeNotSupported(); - } + throw SQL.GlobalizationInvariantModeNotSupported(); } _applyTransientFaultHandling = (!overrides.HasFlag(SqlConnectionOverrides.OpenWithoutRetry) && connectionOptions != null && connectionOptions.ConnectRetryCount > 0); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs index 2c924b3b79..8074fda5a7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs @@ -11,7 +11,7 @@ internal sealed partial class TdsParser { internal void PostReadAsyncForMars() { - if (TdsParserStateObjectFactory.UseManagedSNI) + if (LocalAppContextSwitches.UseManagedNetworking) return; // HACK HACK HACK - for Async only diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 4b6a82b7ba..1925fd756b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -69,7 +69,6 @@ internal sealed partial class TdsParser // Constants private const int constBinBufferSize = 4096; // Size of the buffer used to read input parameter of type Stream private const int constTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader - private const string enableTruncateSwitch = "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; // for applications that need to maintain backwards compatibility with the previous behavior // State variables internal TdsParserState _state = TdsParserState.Closed; // status flag for connection @@ -204,16 +203,6 @@ internal SqlInternalConnectionTds Connection } } - private static bool EnableTruncateSwitch - { - get - { - bool value; - value = AppContext.TryGetSwitch(enableTruncateSwitch, out value) ? value : false; - return value; - } - } - internal SqlInternalTransaction CurrentTransaction { get @@ -631,7 +620,7 @@ internal void EnableMars() // Cache physical stateObj and connection. _pMarsPhysicalConObj = _physicalStateObj; - if (TdsParserStateObjectFactory.UseManagedSNI) + if (LocalAppContextSwitches.UseManagedNetworking) _pMarsPhysicalConObj.IncrementPendingCallbacks(); uint info = 0; @@ -1489,7 +1478,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) * !=null | == 0 | replace text left of errorMessage */ - if (TdsParserStateObjectFactory.UseManagedSNI) + if (LocalAppContextSwitches.UseManagedNetworking) { Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); @@ -1538,7 +1527,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } else { - if (TdsParserStateObjectFactory.UseManagedSNI) + if (LocalAppContextSwitches.UseManagedNetworking) { // SNI error. Append additional error message info if available and hasn't been included. string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); @@ -7653,7 +7642,7 @@ internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) { if (d.Scale != newScale) { - bool round = !EnableTruncateSwitch; + bool round = !LocalAppContextSwitches.TruncateScaledDecimal; return SqlDecimal.AdjustScale(d, newScale - d.Scale, round); } @@ -7666,7 +7655,7 @@ internal static decimal AdjustDecimalScale(decimal value, int newScale) if (newScale != oldScale) { - bool round = !EnableTruncateSwitch; + bool round = !LocalAppContextSwitches.TruncateScaledDecimal; SqlDecimal num = new SqlDecimal(value); num = SqlDecimal.AdjustScale(num, newScale - oldScale, round); return num.Value; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index f89613204f..e8b054ba40 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -69,7 +69,6 @@ internal sealed partial class TdsParser // Constants private const int constBinBufferSize = 4096; // Size of the buffer used to read input parameter of type Stream private const int constTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader - private const string enableTruncateSwitch = "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; // for applications that need to maintain backwards compatibility with the previous behavior // State variables internal TdsParserState _state = TdsParserState.Closed; // status flag for connection @@ -205,16 +204,6 @@ internal SqlInternalConnectionTds Connection } } - private static bool EnableTruncateSwitch - { - get - { - bool value; - value = AppContext.TryGetSwitch(enableTruncateSwitch, out value) ? value : false; - return value; - } - } - internal SqlInternalTransaction CurrentTransaction { get @@ -7849,7 +7838,7 @@ internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) { if (d.Scale != newScale) { - bool round = !EnableTruncateSwitch; + bool round = !LocalAppContextSwitches.TruncateScaledDecimal; return SqlDecimal.AdjustScale(d, newScale - d.Scale, round); } @@ -7862,7 +7851,7 @@ internal static decimal AdjustDecimalScale(decimal value, int newScale) if (newScale != oldScale) { - bool round = !EnableTruncateSwitch; + bool round = !LocalAppContextSwitches.TruncateScaledDecimal; SqlDecimal num = new SqlDecimal(value); num = SqlDecimal.AdjustScale(num, newScale - oldScale, round); return num.Value; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs index 25caa8911b..f92e9beb32 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs @@ -15,7 +15,7 @@ private partial DataTable GetDataSourcesInternal() #if NETFRAMEWORK return SqlDataSourceEnumeratorNativeHelper.GetDataSources(); #else - return SqlClient.TdsParserStateObjectFactory.UseManagedSNI ? SqlDataSourceEnumeratorManagedHelper.GetDataSources() : SqlDataSourceEnumeratorNativeHelper.GetDataSources(); + return SqlClient.LocalAppContextSwitches.UseManagedNetworking ? SqlDataSourceEnumeratorManagedHelper.GetDataSources() : SqlDataSourceEnumeratorNativeHelper.GetDataSources(); #endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs index 99783ff3c7..0c6fd07503 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs @@ -28,7 +28,7 @@ internal static WindowsIdentity GetCurrentWindowsIdentity() #else internal static DbConnectionPoolIdentity GetCurrent() { - return TdsParserStateObjectFactory.UseManagedSNI ? GetCurrentManaged() : GetCurrentNative(); + return LocalAppContextSwitches.UseManagedNetworking ? GetCurrentManaged() : GetCurrentNative(); } #endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index dfd94453f2..ee5d088dc4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -15,14 +15,22 @@ private enum Tristate : byte True = 2 } - internal const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; - internal const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; - internal const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; - internal const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; - internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; - internal const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; - internal const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; - internal const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; + private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; + private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; + private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; + private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; + private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; + private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; + private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; +#if NET + private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; + private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; + private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; +#else + private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; +#endif // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests private static Tristate s_legacyRowVersionNullBehavior; @@ -34,6 +42,13 @@ private enum Tristate : byte private static Tristate s_useCompatibilityProcessSni; private static Tristate s_useCompatibilityAsyncBehaviour; private static Tristate s_useConnectionPoolV2; + private static Tristate s_truncateScaledDecimal; +#if NET + private static Tristate s_globalizationInvariantMode; + private static Tristate s_useManagedNetworking; +#else + private static Tristate s_disableTnirByDefault; +#endif #if NET static LocalAppContextSwitches() @@ -51,44 +66,6 @@ static LocalAppContextSwitches() } #endif -#if NETFRAMEWORK - internal const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; - private static Tristate s_disableTnirByDefault; - - /// - /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. - /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname - /// doesn't respond and there are multiple IPs associated with the hostname. - /// - /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: - /// 0: One IP is attempted, followed by all IPs in parallel - /// 1: All IPs are attempted in parallel - /// 2: All IPs are attempted one after another - /// - /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. - /// To disable TNIR, you can enable the app context switch. - /// - /// This app context switch defaults to 'false'. - /// - public static bool DisableTnirByDefault - { - get - { - if (s_disableTnirByDefault == Tristate.NotInitialized) - { - if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) - { - s_disableTnirByDefault = Tristate.True; - } - else - { - s_disableTnirByDefault = Tristate.False; - } - } - return s_disableTnirByDefault == Tristate.True; - } - } -#endif /// /// In TdsParser the ProcessSni function changed significantly when the packet /// multiplexing code needed for high speed multi-packet column values was added. @@ -296,5 +273,159 @@ public static bool UseConnectionPoolV2 return s_useConnectionPoolV2 == Tristate.True; } } + + /// + /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. + /// + public static bool TruncateScaledDecimal + { + get + { + if (s_truncateScaledDecimal == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) + { + s_truncateScaledDecimal = Tristate.True; + } + else + { + s_truncateScaledDecimal = Tristate.False; + } + } + return s_truncateScaledDecimal == Tristate.True; + } + } + +#if NET + /// + /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for + /// applications which don't need globalization support. SqlClient requires those libraries for core functionality, + /// and will throw exceptions later if they are not present. This switch allows SqlClient to detect this mode early. + /// + public static bool GlobalizationInvariantMode + { + get + { + if (s_globalizationInvariantMode == Tristate.NotInitialized) + { + // Check if invariant mode is has been set by the AppContext switch directly + if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) + { + s_globalizationInvariantMode = Tristate.True; + } + else + { + // If the switch is not set, we check the environment variable as the first fallback + string envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); + + if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) + { + s_globalizationInvariantMode = Tristate.True; + } + else + { + // If this hasn't been manually set, it could still apply if the OS doesn't have ICU libraries installed, + // or if the application is a native binary with ICU support trimmed away. + // .NET 3.1 to 5.0 do not throw in attempting to create en-US in invariant mode, but .NET 6+ does. In + // such cases, catch and infer invariant mode from the exception. + try + { + s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") + ? Tristate.True + : Tristate.False; + } + catch (System.Globalization.CultureNotFoundException) + { + // If the culture is not found, it means we are in invariant mode + s_globalizationInvariantMode = Tristate.True; + } + } + } + } + return s_globalizationInvariantMode == Tristate.True; + } + } + + /// + /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. + /// + /// + /// + /// Non-Windows platforms will always use the managed networking implementation. Windows platforms will use the native SNI + /// implementation by default, but this can be overridden by setting the AppContext switch. + /// + /// + /// ILLink.Substitutions.xml allows the unused SNI implementation to be trimmed away when the corresponding AppContext + /// switch is set at compile time. In such cases, this property will return a constant value, even if the AppContext switch is + /// set or reset at runtime. See the ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml resource files for details. + /// + /// + public static bool UseManagedNetworking + { + get + { + if (s_useManagedNetworking == Tristate.NotInitialized) + { + if (!OperatingSystem.IsWindows()) + { + s_useManagedNetworking = Tristate.True; + } + else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) + { + s_useManagedNetworking = Tristate.True; + } + else + { + s_useManagedNetworking = Tristate.False; + } + } + return s_useManagedNetworking == Tristate.True; + } + } +#else + /// + /// .NET Framework does not support Globalization Invariant mode, so this will always be false. + /// + public const bool GlobalizationInvariantMode = false; + + /// + /// .NET Framework does not support the managed SNI, so this will always be false. + /// + public const bool UseManagedNetworking = false; + + /// + /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. + /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname + /// doesn't respond and there are multiple IPs associated with the hostname. + /// + /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: + /// 0: One IP is attempted, followed by all IPs in parallel + /// 1: All IPs are attempted in parallel + /// 2: All IPs are attempted one after another + /// + /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. + /// To disable TNIR, you can enable the app context switch. + /// + /// This app context switch defaults to 'false'. + /// + public static bool DisableTnirByDefault + { + get + { + if (s_disableTnirByDefault == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) + { + s_disableTnirByDefault = Tristate.True; + } + else + { + s_disableTnirByDefault = Tristate.False; + } + } + return s_disableTnirByDefault == Tristate.True; + } + } +#endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 06d679e22a..bcb7fc9005 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3224,7 +3224,7 @@ internal void ReadSni(TaskCompletionSource completion) ReadAsyncCallback(IntPtr.Zero, readPacket, 0); // Only release packet for Managed SNI as for Native SNI packet is released in finally block. - if (TdsParserStateObjectFactory.UseManagedSNI && readFromNetwork && !IsPacketEmpty(readPacket)) + if (LocalAppContextSwitches.UseManagedNetworking && readFromNetwork && !IsPacketEmpty(readPacket)) { ReleasePacket(readPacket); } @@ -3260,7 +3260,7 @@ internal void ReadSni(TaskCompletionSource completion) } finally { - if (!TdsParserStateObjectFactory.UseManagedSNI) + if (!LocalAppContextSwitches.UseManagedNetworking) { if (readFromNetwork && !IsPacketEmpty(readPacket)) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs index f7cb55d451..1362e47b45 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs @@ -8,9 +8,6 @@ namespace Microsoft.Data.SqlClient { internal sealed class TdsParserStateObjectFactory { - - public static bool UseManagedSNI => true; - public static readonly TdsParserStateObjectFactory Singleton = new TdsParserStateObjectFactory(); public EncryptionOptions EncryptionOptions => ManagedSni.SniLoadHandle.SingletonInstance.Options; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs index 8b8fe9186b..53cbc4decc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs @@ -13,28 +13,16 @@ internal sealed class TdsParserStateObjectFactory { public static readonly TdsParserStateObjectFactory Singleton = new TdsParserStateObjectFactory(); - private const string UseManagedNetworkingOnWindows = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; - -#if NET - private static bool s_shouldUseManagedSNI; - - // If the appcontext switch is set then Use Managed SNI based on the value. Otherwise Native SNI.dll will be used by default. - public static bool UseManagedSNI => - AppContext.TryGetSwitch(UseManagedNetworkingOnWindows, out s_shouldUseManagedSNI) ? s_shouldUseManagedSNI : false; -#else - public const bool UseManagedSNI = false; -#endif - public EncryptionOptions EncryptionOptions => #if NET - UseManagedSNI ? ManagedSni.SniLoadHandle.SingletonInstance.Options : SNILoadHandle.SingletonInstance.Options; + LocalAppContextSwitches.UseManagedNetworking ? ManagedSni.SniLoadHandle.SingletonInstance.Options : SNILoadHandle.SingletonInstance.Options; #else SNILoadHandle.SingletonInstance.Options; #endif public uint SNIStatus => #if NET - UseManagedSNI ? ManagedSni.SniLoadHandle.SingletonInstance.Status : SNILoadHandle.SingletonInstance.Status; + LocalAppContextSwitches.UseManagedNetworking ? ManagedSni.SniLoadHandle.SingletonInstance.Status : SNILoadHandle.SingletonInstance.Status; #else SNILoadHandle.SingletonInstance.Status; #endif @@ -44,7 +32,7 @@ internal sealed class TdsParserStateObjectFactory /// public bool ClientOSEncryptionSupport => #if NET - UseManagedSNI ? ManagedSni.SniLoadHandle.SingletonInstance.ClientOSEncryptionSupport : SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; + LocalAppContextSwitches.UseManagedNetworking ? ManagedSni.SniLoadHandle.SingletonInstance.ClientOSEncryptionSupport : SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; #else SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; #endif @@ -52,16 +40,14 @@ internal sealed class TdsParserStateObjectFactory public TdsParserStateObject CreateTdsParserStateObject(TdsParser parser) { #if NET - if (UseManagedSNI) + if (LocalAppContextSwitches.UseManagedNetworking) { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectFactory.CreateTdsParserStateObject | Info | Found AppContext switch '{0}' enabled, managed networking implementation will be used." - , UseManagedNetworkingOnWindows); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectFactory.CreateTdsParserStateObject | Info | Using managed networking implementation."); return new TdsParserStateObjectManaged(parser); } else { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectFactory.CreateTdsParserStateObject | Info | AppContext switch '{0}' not enabled, native networking implementation will be used." - , UseManagedNetworkingOnWindows); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectFactory.CreateTdsParserStateObject | Info | Using native networking implementation."); return new TdsParserStateObjectNative(parser); } #else @@ -72,7 +58,7 @@ public TdsParserStateObject CreateTdsParserStateObject(TdsParser parser) internal TdsParserStateObject CreateSessionObject(TdsParser tdsParser, TdsParserStateObject _pMarsPhysicalConObj, bool v) { #if NET - if (TdsParserStateObjectFactory.UseManagedSNI) + if (LocalAppContextSwitches.UseManagedNetworking) { return new TdsParserStateObjectManaged(tdsParser, _pMarsPhysicalConObj, true); } diff --git a/src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.Unix.xml b/src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.Unix.xml new file mode 100644 index 0000000000..4e105d25f0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.Unix.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.Windows.xml b/src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.Windows.xml new file mode 100644 index 0000000000..e8577c7ae0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.Windows.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 2970b1f1ce..df68f86677 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -30,7 +30,11 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly PropertyInfo _useCompatibilityProcessSniProperty; private readonly PropertyInfo _useCompatibilityAsyncBehaviourProperty; private readonly PropertyInfo _useConnectionPoolV2Property; - #if NETFRAMEWORK + private readonly PropertyInfo _truncateScaledDecimalProperty; + #if NET + private readonly PropertyInfo _globalizationInvariantModeProperty; + private readonly PropertyInfo _useManagedNetworkingProperty; + #else private readonly PropertyInfo _disableTnirByDefaultProperty; #endif @@ -51,7 +55,14 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly Tristate _useCompatibilityAsyncBehaviourOriginal; private readonly FieldInfo _useConnectionPoolV2Field; private readonly Tristate _useConnectionPoolV2Original; - #if NETFRAMEWORK + private readonly FieldInfo _truncateScaledDecimalField; + private readonly Tristate _truncateScaledDecimalOriginal; + #if NET + private readonly FieldInfo _globalizationInvariantModeField; + private readonly Tristate _globalizationInvariantModeOriginal; + private readonly FieldInfo _useManagedNetworkingField; + private readonly Tristate _useManagedNetworkingOriginal; + #else private readonly FieldInfo _disableTnirByDefaultField; private readonly Tristate _disableTnirByDefaultOriginal; #endif @@ -140,7 +151,19 @@ void InitProperty(string name, out PropertyInfo property) "UseConnectionPoolV2", out _useConnectionPoolV2Property); - #if NETFRAMEWORK + InitProperty( + "TruncateScaledDecimal", + out _truncateScaledDecimalProperty); + + #if NET + InitProperty( + "GlobalizationInvariantMode", + out _globalizationInvariantModeProperty); + + InitProperty( + "UseManagedNetworking", + out _useManagedNetworkingProperty); + #else InitProperty( "DisableTnirByDefault", out _disableTnirByDefaultProperty); @@ -201,7 +224,22 @@ void InitField(string name, out FieldInfo field, out Tristate value) out _useConnectionPoolV2Field, out _useConnectionPoolV2Original); - #if NETFRAMEWORK + InitField( + "s_truncateScaledDecimal", + out _truncateScaledDecimalField, + out _truncateScaledDecimalOriginal); + + #if NET + InitField( + "s_globalizationInvariantMode", + out _globalizationInvariantModeField, + out _globalizationInvariantModeOriginal); + + InitField( + "s_useManagedNetworking", + out _useManagedNetworkingField, + out _useManagedNetworkingOriginal); + #else InitField( "s_disableTnirByDefault", out _disableTnirByDefaultField, @@ -265,7 +303,19 @@ void RestoreField(FieldInfo field, Tristate value) _useConnectionPoolV2Field, _useConnectionPoolV2Original); - #if NETFRAMEWORK + RestoreField( + _truncateScaledDecimalField, + _truncateScaledDecimalOriginal); + + #if NET + RestoreField( + _globalizationInvariantModeField, + _globalizationInvariantModeOriginal); + + RestoreField( + _useManagedNetworkingField, + _useManagedNetworkingOriginal); + #else RestoreField( _disableTnirByDefaultField, _disableTnirByDefaultOriginal); @@ -350,7 +400,31 @@ public bool UseConnectionPoolV2 get => (bool)_useConnectionPoolV2Property.GetValue(null); } - #if NETFRAMEWORK + /// + /// Access the LocalAppContextSwitches.TruncateScaledDecimal property. + /// + public bool TruncateScaledDecimal + { + get => (bool)_truncateScaledDecimalProperty.GetValue(null); + } + + #if NET + /// + /// Access the LocalAppContextSwitches.GlobalizationInvariantMode property. + /// + public bool GlobalizationInvariantMode + { + get => (bool)_globalizationInvariantModeProperty.GetValue(null); + } + + /// + /// Access the LocalAppContextSwitches.UseManagedNetworking property. + /// + public bool UseManagedNetworking + { + get => (bool)_useManagedNetworkingProperty.GetValue(null); + } + #else /// /// Access the LocalAppContextSwitches.DisableTnirByDefault property. /// @@ -443,7 +517,34 @@ public Tristate UseConnectionPoolV2Field set => SetValue(_useConnectionPoolV2Field, value); } - #if NETFRAMEWORK + /// + /// Get or set the LocalAppContextSwitches.TruncateScaledDecimal switch value. + /// + public Tristate TruncateScaledDecimalField + { + get => GetValue(_truncateScaledDecimalField); + set => SetValue(_truncateScaledDecimalField, value); + } + + #if NET + /// + /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value. + /// + public Tristate GlobalizationInvariantModeField + { + get => GetValue(_globalizationInvariantModeField); + set => SetValue(_globalizationInvariantModeField, value); + } + + /// + /// Get or set the LocalAppContextSwitches.UseManagedNetworking switch value. + /// + public Tristate UseManagedNetworkingField + { + get => GetValue(_useManagedNetworkingField); + set => SetValue(_useManagedNetworkingField, value); + } + #else /// /// Get or set the LocalAppContextSwitches.DisableTnirByDefault switch /// value. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index bf99691907..d98fe6abd4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -11,6 +11,8 @@ using System.Threading.Tasks; using Xunit; using System.Globalization; +using Microsoft.Data.SqlClient.Tests.Common; + #if !NETFRAMEWORK using Microsoft.SqlServer.Types; @@ -570,13 +572,16 @@ public static void SqlDecimalConvertToDecimal_TestOutOfRange(string sqlDecimalVa [ClassData(typeof(ConnectionStringsProvider))] public static void TestScaledDecimalParameter_CommandInsert(string connectionString, bool truncateScaledDecimal) { + using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); + string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterCMD"); using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); try { using (SqlCommand cmd = connection.CreateCommand()) { - AppContext.SetSwitch(TruncateDecimalSwitch, truncateScaledDecimal); + appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False; + var p = new SqlParameter("@Value", null) { Precision = 18, @@ -602,6 +607,8 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr [ClassData(typeof(ConnectionStringsProvider))] public static void TestScaledDecimalParameter_BulkCopy(string connectionString, bool truncateScaledDecimal) { + using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); + string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterBC"); using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); try @@ -620,7 +627,7 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, } bulkCopy.DestinationTableName = tableName; - AppContext.SetSwitch(TruncateDecimalSwitch, truncateScaledDecimal); + appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False; bulkCopy.WriteToServer(table); } Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); @@ -636,6 +643,8 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, [ClassData(typeof(ConnectionStringsProvider))] public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool truncateScaledDecimal) { + using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); + string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterBC"); string tableTypeName = DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDecimalParameterBC"); string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDecimalParameterBC"); @@ -663,7 +672,7 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool table.Rows.Add(newRow); } p.Value = table; - AppContext.SetSwitch(TruncateDecimalSwitch, truncateScaledDecimal); + appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False; cmd.ExecuteNonQuery(); } // TVP always rounds data without attention to the configuration. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs index 72bab47869..117ab67c0d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs @@ -5,6 +5,7 @@ using System; using System.Data; using System.Data.SqlTypes; +using Microsoft.Data.SqlClient.Tests.Common; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -14,6 +15,7 @@ public static class AdjustPrecScaleForBulkCopy [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void RunTest() { + using LocalAppContextSwitchesHelper appContextSwitches = new(); SqlDecimal value = BulkCopySqlDecimalToTable(new SqlDecimal(0), 1, 0, 2, 2); Assert.Equal("0.00", value.ToString()); @@ -27,7 +29,7 @@ public static void RunTest() Assert.Equal("12.3", value.ToString()); value = BulkCopySqlDecimalToTable(new SqlDecimal(123.45), 10, 2, 4, 1); - if (AppContext.TryGetSwitch("Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal", out bool switchValue) && switchValue) + if (appContextSwitches.TruncateScaledDecimal) { Assert.Equal("123.4", value.ToString()); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index 90ee094f8f..ec60f0d3cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using Xunit; namespace Microsoft.Data.SqlClient.UnitTests; @@ -25,8 +26,12 @@ public void TestDefaultAppContextSwitchValues() Assert.False(LocalAppContextSwitches.UseCompatibilityProcessSni); Assert.False(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); - #if NETFRAMEWORK + Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); +#if NETFRAMEWORK Assert.False(LocalAppContextSwitches.DisableTnirByDefault); - #endif + Assert.False(LocalAppContextSwitches.UseManagedNetworking); +#else + Assert.Equal(!OperatingSystem.IsWindows(), LocalAppContextSwitches.UseManagedNetworking); +#endif } }