From dfa62a1746e4702e57f67380772a2e126088c8fb Mon Sep 17 00:00:00 2001 From: David Engel Date: Mon, 16 May 2022 15:50:51 -0700 Subject: [PATCH 01/35] Parallelize SSRP requests when MSF is specified (#1578) --- .../Microsoft/Data/SqlClient/SNI/SNICommon.cs | 1 + .../Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 10 +- .../Data/SqlClient/SNI/SNITcpHandle.cs | 23 +- .../src/Microsoft/Data/SqlClient/SNI/SSRP.cs | 198 ++++++++++++++++-- .../SQL/InstanceNameTest/InstanceNameTest.cs | 124 ++++++++++- 5 files changed, 321 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs index b57dc4f5f3..6980eb09f2 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs @@ -108,6 +108,7 @@ internal class SNICommon internal const int ConnTimeoutError = 11; internal const int ConnNotUsableError = 19; internal const int InvalidConnStringError = 25; + internal const int ErrorLocatingServerInstance = 26; internal const int HandshakeFailureError = 31; internal const int InternalExceptionError = 35; internal const int ConnOpenFailedError = 40; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index 501a68e401..f9a3c88fa3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -141,7 +141,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode) /// /// IP address preference /// Used for DNS Cache - /// Used for DNS Cache + /// Used for DNS Cache /// SNI handle internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) @@ -263,7 +263,7 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr /// Should MultiSubnetFailover be used /// IP address preference /// Key for DNS Cache - /// Used for DNS Cache + /// Used for DNS Cache /// SNITCPHandle private static SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, bool parallel, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { @@ -285,12 +285,12 @@ private static SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire try { port = isAdminConnection ? - SSRP.GetDacPortByInstanceName(hostName, details.InstanceName) : - SSRP.GetPortByInstanceName(hostName, details.InstanceName); + SSRP.GetDacPortByInstanceName(hostName, details.InstanceName, timerExpire, parallel, ipPreference) : + SSRP.GetPortByInstanceName(hostName, details.InstanceName, timerExpire, parallel, ipPreference); } catch (SocketException se) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InvalidConnStringError, se); + SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.ErrorLocatingServerInstance, se); return null; } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs index ad833e14be..537b697067 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs @@ -146,9 +146,9 @@ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel bool reportError = true; SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Connecting to serverName {1} and port {2}", args0: _connectionId, args1: serverName, args2: port); - // We will always first try to connect with serverName as before and let the DNS server to resolve the serverName. - // If the DSN resolution fails, we will try with IPs in the DNS cache if existed. We try with cached IPs based on IPAddressPreference. - // The exceptions will be throw to upper level and be handled as before. + // We will always first try to connect with serverName as before and let DNS resolve the serverName. + // If DNS resolution fails, we will try with IPs in the DNS cache if they exist. We try with cached IPs based on IPAddressPreference. + // Exceptions will be thrown to the caller and be handled as before. try { if (parallel) @@ -280,7 +280,12 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i Task connectTask; Task serverAddrTask = Dns.GetHostAddressesAsync(hostName); - serverAddrTask.Wait(ts); + bool complete = serverAddrTask.Wait(ts); + + // DNS timed out - don't block + if (!complete) + return null; + IPAddress[] serverAddresses = serverAddrTask.Result; if (serverAddresses.Length > MaxParallelIpAddresses) @@ -324,7 +329,6 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i availableSocket = connectTask.Result; return availableSocket; - } // Connect to server with hostName and port. @@ -334,7 +338,14 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo { SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "IP preference : {0}", Enum.GetName(typeof(SqlConnectionIPAddressPreference), ipPreference)); - IPAddress[] ipAddresses = Dns.GetHostAddresses(serverName); + Task serverAddrTask = Dns.GetHostAddressesAsync(serverName); + bool complete = serverAddrTask.Wait(timeout); + + // DNS timed out - don't block + if (!complete) + return null; + + IPAddress[] ipAddresses = serverAddrTask.Result; string IPv4String = null; string IPv6String = null; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs index d182a8d31f..ce6eb653bd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs @@ -3,10 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Data.SqlClient.SNI @@ -27,8 +30,11 @@ internal sealed class SSRP /// /// SQL Sever Browser hostname /// instance name to find port number + /// Connection timer expiration + /// query all resolved IP addresses in parallel + /// IP address preference /// port number for given instance name - internal static int GetPortByInstanceName(string browserHostName, string instanceName) + internal static int GetPortByInstanceName(string browserHostName, string instanceName, long timerExpire, bool allIPsInParallel, SqlConnectionIPAddressPreference ipPreference) { Debug.Assert(!string.IsNullOrWhiteSpace(browserHostName), "browserHostName should not be null, empty, or whitespace"); Debug.Assert(!string.IsNullOrWhiteSpace(instanceName), "instanceName should not be null, empty, or whitespace"); @@ -38,7 +44,7 @@ internal static int GetPortByInstanceName(string browserHostName, string instanc byte[] responsePacket = null; try { - responsePacket = SendUDPRequest(browserHostName, SqlServerBrowserPort, instanceInfoRequest); + responsePacket = SendUDPRequest(browserHostName, SqlServerBrowserPort, instanceInfoRequest, timerExpire, allIPsInParallel, ipPreference); } catch (SocketException se) { @@ -93,14 +99,17 @@ private static byte[] CreateInstanceInfoRequest(string instanceName) /// /// SQL Sever Browser hostname /// instance name to lookup DAC port + /// Connection timer expiration + /// query all resolved IP addresses in parallel + /// IP address preference /// DAC port for given instance name - internal static int GetDacPortByInstanceName(string browserHostName, string instanceName) + internal static int GetDacPortByInstanceName(string browserHostName, string instanceName, long timerExpire, bool allIPsInParallel, SqlConnectionIPAddressPreference ipPreference) { Debug.Assert(!string.IsNullOrWhiteSpace(browserHostName), "browserHostName should not be null, empty, or whitespace"); Debug.Assert(!string.IsNullOrWhiteSpace(instanceName), "instanceName should not be null, empty, or whitespace"); byte[] dacPortInfoRequest = CreateDacPortInfoRequest(instanceName); - byte[] responsePacket = SendUDPRequest(browserHostName, SqlServerBrowserPort, dacPortInfoRequest); + byte[] responsePacket = SendUDPRequest(browserHostName, SqlServerBrowserPort, dacPortInfoRequest, timerExpire, allIPsInParallel, ipPreference); const byte SvrResp = 0x05; const byte ProtocolVersion = 0x01; @@ -137,14 +146,23 @@ private static byte[] CreateDacPortInfoRequest(string instanceName) return requestPacket; } + private class SsrpResult + { + public byte[] ResponsePacket; + public Exception Error; + } + /// /// Sends request to server, and receives response from server by UDP. /// /// UDP server hostname /// UDP server port /// request packet + /// Connection timer expiration + /// query all resolved IP addresses in parallel + /// IP address preference /// response packet from UDP server - private static byte[] SendUDPRequest(string browserHostname, int port, byte[] requestPacket) + private static byte[] SendUDPRequest(string browserHostname, int port, byte[] requestPacket, long timerExpire, bool allIPsInParallel, SqlConnectionIPAddressPreference ipPreference) { using (TrySNIEventScope.Create(nameof(SSRP))) { @@ -152,26 +170,174 @@ private static byte[] SendUDPRequest(string browserHostname, int port, byte[] re Debug.Assert(port >= 0 && port <= 65535, "Invalid port"); Debug.Assert(requestPacket != null && requestPacket.Length > 0, "requestPacket should not be null or 0-length array"); - const int sendTimeOutMs = 1000; - const int receiveTimeOutMs = 1000; - bool isIpAddress = IPAddress.TryParse(browserHostname, out IPAddress address); - byte[] responsePacket = null; - using (UdpClient client = new UdpClient(!isIpAddress ? AddressFamily.InterNetwork : address.AddressFamily)) + TimeSpan ts = default; + // In case the Timeout is Infinite, we will receive the max value of Int64 as the tick count + // The infinite Timeout is a function of ConnectionString Timeout=0 + if (long.MaxValue != timerExpire) + { + ts = DateTime.FromFileTime(timerExpire) - DateTime.Now; + ts = ts.Ticks < 0 ? TimeSpan.FromTicks(0) : ts; + } + + IPAddress[] ipAddresses = null; + if (!isIpAddress) + { + Task serverAddrTask = Dns.GetHostAddressesAsync(browserHostname); + bool taskComplete; + try + { + taskComplete = serverAddrTask.Wait(ts); + } + catch (AggregateException ae) + { + throw ae.InnerException; + } + + // If DNS took too long, need to return instead of blocking + if (!taskComplete) + return null; + + ipAddresses = serverAddrTask.Result; + } + + Debug.Assert(ipAddresses.Length > 0, "DNS should throw if zero addresses resolve"); + + switch (ipPreference) + { + case SqlConnectionIPAddressPreference.IPv4First: + { + SsrpResult response4 = SendUDPRequest(ipAddresses.Where(i => i.AddressFamily == AddressFamily.InterNetwork).ToArray(), port, requestPacket, allIPsInParallel); + if (response4 != null && response4.ResponsePacket != null) + return response4.ResponsePacket; + + SsrpResult response6 = SendUDPRequest(ipAddresses.Where(i => i.AddressFamily == AddressFamily.InterNetworkV6).ToArray(), port, requestPacket, allIPsInParallel); + if (response6 != null && response6.ResponsePacket != null) + return response6.ResponsePacket; + + // No responses so throw first error + if (response4 != null && response4.Error != null) + throw response4.Error; + else if (response6 != null && response6.Error != null) + throw response6.Error; + + break; + } + case SqlConnectionIPAddressPreference.IPv6First: + { + SsrpResult response6 = SendUDPRequest(ipAddresses.Where(i => i.AddressFamily == AddressFamily.InterNetworkV6).ToArray(), port, requestPacket, allIPsInParallel); + if (response6 != null && response6.ResponsePacket != null) + return response6.ResponsePacket; + + SsrpResult response4 = SendUDPRequest(ipAddresses.Where(i => i.AddressFamily == AddressFamily.InterNetwork).ToArray(), port, requestPacket, allIPsInParallel); + if (response4 != null && response4.ResponsePacket != null) + return response4.ResponsePacket; + + // No responses so throw first error + if (response6 != null && response6.Error != null) + throw response6.Error; + else if (response4 != null && response4.Error != null) + throw response4.Error; + + break; + } + default: + { + SsrpResult response = SendUDPRequest(ipAddresses, port, requestPacket, true); // allIPsInParallel); + if (response != null && response.ResponsePacket != null) + return response.ResponsePacket; + else if (response != null && response.Error != null) + throw response.Error; + + break; + } + } + + return null; + } + } + + /// + /// Sends request to server, and receives response from server by UDP. + /// + /// IP Addresses + /// UDP server port + /// request packet + /// query all resolved IP addresses in parallel + /// response packet from UDP server + private static SsrpResult SendUDPRequest(IPAddress[] ipAddresses, int port, byte[] requestPacket, bool allIPsInParallel) + { + if (ipAddresses.Length == 0) + return null; + + if (allIPsInParallel) // Used for MultiSubnetFailover + { + List> tasks = new(ipAddresses.Length); + CancellationTokenSource cts = new CancellationTokenSource(); + for (int i = 0; i < ipAddresses.Length; i++) + { + IPEndPoint endPoint = new IPEndPoint(ipAddresses[i], port); + tasks.Add(Task.Factory.StartNew(() => SendUDPRequest(endPoint, requestPacket))); + } + + List> completedTasks = new(); + while (tasks.Count > 0) + { + int first = Task.WaitAny(tasks.ToArray()); + if (tasks[first].Result.ResponsePacket != null) + { + cts.Cancel(); + return tasks[first].Result; + } + else + { + completedTasks.Add(tasks[first]); + tasks.Remove(tasks[first]); + } + } + + Debug.Assert(completedTasks.Count > 0, "completedTasks should never be 0"); + + // All tasks failed. Return the error from the first failure. + return completedTasks[0].Result; + } + else + { + // If not parallel, use the first IP address provided + IPEndPoint endPoint = new IPEndPoint(ipAddresses[0], port); + return SendUDPRequest(endPoint, requestPacket); + } + } + + private static SsrpResult SendUDPRequest(IPEndPoint endPoint, byte[] requestPacket) + { + const int sendTimeOutMs = 1000; + const int receiveTimeOutMs = 1000; + + SsrpResult result = new(); + + try + { + using (UdpClient client = new UdpClient(endPoint.AddressFamily)) { - Task sendTask = client.SendAsync(requestPacket, requestPacket.Length, browserHostname, port); + Task sendTask = client.SendAsync(requestPacket, requestPacket.Length, endPoint); Task receiveTask = null; - + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Waiting for UDP Client to fetch Port info."); if (sendTask.Wait(sendTimeOutMs) && (receiveTask = client.ReceiveAsync()).Wait(receiveTimeOutMs)) { SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Received Port info from UDP Client."); - responsePacket = receiveTask.Result.Buffer; + result.ResponsePacket = receiveTask.Result.Buffer; } } - return responsePacket; } + catch (Exception e) + { + result.Error = e; + } + + return result; } /// @@ -186,7 +352,7 @@ internal static string SendBroadcastUDPRequest() byte[] CLNT_BCAST_EX_Request = new byte[1] { CLNT_BCAST_EX }; //0x02 // Waits 5 seconds for the first response and every 1 second up to 15 seconds // https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/f2640a2d-3beb-464b-a443-f635842ebc3e#Appendix_A_3 - int currentTimeOut = FirstTimeoutForCLNT_BCAST_EX; + int currentTimeOut = FirstTimeoutForCLNT_BCAST_EX; using (TrySNIEventScope.Create(nameof(SSRP))) { @@ -198,7 +364,7 @@ internal static string SendBroadcastUDPRequest() Stopwatch sw = new Stopwatch(); //for waiting until 15 sec elapsed sw.Start(); try - { + { while ((receiveTask = clientListener.ReceiveAsync()).Wait(currentTimeOut) && sw.ElapsedMilliseconds <= RecieveMAXTimeoutsForCLNT_BCAST_EX && receiveTask != null) { currentTimeOut = RecieveTimeoutsForCLNT_BCAST_EX; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs index 3202636c3c..ceba949d55 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -2,7 +2,9 @@ // 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 System.Net.Sockets; +using System.Text; using System.Threading.Tasks; using Xunit; @@ -13,7 +15,7 @@ public static class InstanceNameTest [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void ConnectToSQLWithInstanceNameTest() { - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); bool proceed = true; string dataSourceStr = builder.DataSource.Replace("tcp:", ""); @@ -26,24 +28,116 @@ public static void ConnectToSQLWithInstanceNameTest() if (proceed) { - using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) + using SqlConnection connection = new(builder.ConnectionString); + connection.Open(); + connection.Close(); + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureServer))] + [InlineData(true, SqlConnectionIPAddressPreference.IPv4First)] + [InlineData(true, SqlConnectionIPAddressPreference.IPv6First)] + [InlineData(true, SqlConnectionIPAddressPreference.UsePlatformDefault)] + [InlineData(false, SqlConnectionIPAddressPreference.IPv4First)] + [InlineData(false, SqlConnectionIPAddressPreference.IPv6First)] + [InlineData(false, SqlConnectionIPAddressPreference.UsePlatformDefault)] + public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailover, SqlConnectionIPAddressPreference ipPreference) + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + builder.MultiSubnetFailover = useMultiSubnetFailover; + builder.IPAddressPreference = ipPreference; + + + Assert.True(ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName)); + + if (IsBrowserAlive(hostname) && IsValidInstance(hostname, instanceName)) + { + builder.DataSource = hostname + "\\" + instanceName; + try + { + using SqlConnection connection = new(builder.ConnectionString); + connection.Open(); + } + catch (Exception ex) + { + Assert.True(false, "Unexpected connection failure: " + ex.Message); + } + } + + builder.ConnectTimeout = 2; + instanceName = "invalidinstance3456"; + if (!IsValidInstance(hostname, instanceName)) + { + builder.DataSource = hostname + "\\" + instanceName; + try { + using SqlConnection connection = new(builder.ConnectionString); connection.Open(); - connection.Close(); + Assert.True(false, "Unexpected connection success against " + instanceName); + } + catch (Exception ex) + { + Assert.Contains("Error Locating Server/Instance Specified", ex.Message); } } } + private static bool ParseDataSource(string dataSource, out string hostname, out int port, out string instanceName) + { + hostname = string.Empty; + port = -1; + instanceName = string.Empty; + + if (dataSource.Contains(",") && dataSource.Contains("\\")) + return false; + + if (dataSource.Contains(":")) + { + dataSource = dataSource.Substring(dataSource.IndexOf(":") + 1); + } + + if (dataSource.Contains(",")) + { + if (!int.TryParse(dataSource.Substring(dataSource.LastIndexOf(",") + 1), out port)) + { + return false; + } + dataSource = dataSource.Substring(0, dataSource.IndexOf(",") - 1); + } + + if (dataSource.Contains("\\")) + { + instanceName = dataSource.Substring(dataSource.LastIndexOf("\\") + 1); + dataSource = dataSource.Substring(0, dataSource.LastIndexOf("\\")); + } + + hostname = dataSource; + + return hostname.Length > 0 && hostname.IndexOfAny(new char[] { '\\', ':', ',' }) == -1; + } + private static bool IsBrowserAlive(string browserHostname) + { + const byte ClntUcastEx = 0x03; + + byte[] responsePacket = QueryBrowser(browserHostname, new byte[] { ClntUcastEx }); + return responsePacket != null && responsePacket.Length > 0; + } + + private static bool IsValidInstance(string browserHostName, string instanceName) + { + byte[] request = CreateInstanceInfoRequest(instanceName); + byte[] response = QueryBrowser(browserHostName, request); + return response != null && response.Length > 0; + } + + private static byte[] QueryBrowser(string browserHostname, byte[] requestPacket) { const int DefaultBrowserPort = 1434; const int sendTimeout = 1000; const int receiveTimeout = 1000; - const byte ClntUcastEx = 0x03; - - byte[] requestPacket = new byte[] { ClntUcastEx }; byte[] responsePacket = null; - using (UdpClient client = new UdpClient(AddressFamily.InterNetwork)) + using (UdpClient client = new(AddressFamily.InterNetwork)) { try { @@ -56,7 +150,21 @@ private static bool IsBrowserAlive(string browserHostname) } catch { } } - return responsePacket != null && responsePacket.Length > 0; + + return responsePacket; + } + + private static byte[] CreateInstanceInfoRequest(string instanceName) + { + const byte ClntUcastInst = 0x04; + instanceName += char.MinValue; + int byteCount = Encoding.ASCII.GetByteCount(instanceName); + + byte[] requestPacket = new byte[byteCount + 1]; + requestPacket[0] = ClntUcastInst; + Encoding.ASCII.GetBytes(instanceName, 0, instanceName.Length, requestPacket, 1); + + return requestPacket; } } } From c2e7e3e40f8c2c576f4d18a7a64b4e51766997e3 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Wed, 25 May 2022 11:32:34 -0700 Subject: [PATCH 02/35] Test | Improve instance name test (#1618) --- .../SQL/InstanceNameTest/InstanceNameTest.cs | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs index ceba949d55..8079530796 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -34,7 +34,7 @@ public static void ConnectToSQLWithInstanceNameTest() } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureServer))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData(true, SqlConnectionIPAddressPreference.IPv4First)] [InlineData(true, SqlConnectionIPAddressPreference.IPv6First)] [InlineData(true, SqlConnectionIPAddressPreference.UsePlatformDefault)] @@ -47,21 +47,14 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove builder.MultiSubnetFailover = useMultiSubnetFailover; builder.IPAddressPreference = ipPreference; - - Assert.True(ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName)); + Assert.True(ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName), "Invalid data source."); if (IsBrowserAlive(hostname) && IsValidInstance(hostname, instanceName)) { builder.DataSource = hostname + "\\" + instanceName; - try - { - using SqlConnection connection = new(builder.ConnectionString); - connection.Open(); - } - catch (Exception ex) - { - Assert.True(false, "Unexpected connection failure: " + ex.Message); - } + + using SqlConnection connection = new(builder.ConnectionString); + connection.Open(); } builder.ConnectTimeout = 2; @@ -69,16 +62,10 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove if (!IsValidInstance(hostname, instanceName)) { builder.DataSource = hostname + "\\" + instanceName; - try - { - using SqlConnection connection = new(builder.ConnectionString); - connection.Open(); - Assert.True(false, "Unexpected connection success against " + instanceName); - } - catch (Exception ex) - { - Assert.Contains("Error Locating Server/Instance Specified", ex.Message); - } + + using SqlConnection connection = new(builder.ConnectionString); + SqlException ex = Assert.Throws(() => connection.Open()); + Assert.Contains("Error Locating Server/Instance Specified", ex.Message); } } From caaecea987ea24c41df1adb312e10ffc5b0d02d9 Mon Sep 17 00:00:00 2001 From: Lawrence LCI <31262254+lcheunglci@users.noreply.github.com> Date: Thu, 26 May 2022 09:58:14 -0700 Subject: [PATCH 03/35] Chore | upgrade Azure Identity from 1.5.0 to 1.6.0 (#1611) --- tools/props/Versions.props | 6 +++--- tools/specs/Microsoft.Data.SqlClient.nuspec | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 57ca8239fd..95122fdfc6 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -26,8 +26,8 @@ - 1.5.0 - 4.30.1 + 1.6.0 + 4.43.2 6.8.0 6.8.0 4.5.1 @@ -55,7 +55,7 @@ - [1.20.0,2.0.0) + [1.24.0,2.0.0) [4.0.3,5.0.0) 5.0.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 631922575e..2759aafe6c 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -29,8 +29,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -43,8 +43,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -61,8 +61,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -79,8 +79,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + From 2c568306c9ba41bfe2534ff3355c7d2563e4c1e0 Mon Sep 17 00:00:00 2001 From: Wraith Date: Thu, 26 May 2022 17:58:57 +0100 Subject: [PATCH 04/35] Simplify managed SNI receive callback use (#1186) --- .../src/Microsoft.Data.SqlClient.csproj | 1 - .../Microsoft/Data/SqlClient/SNI/SNIHandle.cs | 3 +- .../Data/SqlClient/SNI/SNIMarsConnection.cs | 9 ++-- .../Data/SqlClient/SNI/SNIMarsHandle.cs | 19 ++++--- .../Data/SqlClient/SNI/SNIMarsQueuedPacket.cs | 30 ----------- .../Data/SqlClient/SNI/SNINpHandle.cs | 9 ++-- .../Microsoft/Data/SqlClient/SNI/SNIPacket.cs | 51 ++++++++----------- .../Data/SqlClient/SNI/SNITcpHandle.cs | 10 ++-- 8 files changed, 43 insertions(+), 89 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsQueuedPacket.cs 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 70fe79041e..8ca4487b3a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -614,7 +614,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs index dbee403f41..7eec717a8a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs @@ -51,9 +51,8 @@ internal abstract class SNIHandle /// Send a packet asynchronously /// /// SNI packet - /// Completion callback /// SNI error code - public abstract uint SendAsync(SNIPacket packet, SNIAsyncCallback callback = null); + public abstract uint SendAsync(SNIPacket packet); /// /// Receive a packet synchronously diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs index 395cfed4be..90bf83dacc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs @@ -112,16 +112,15 @@ public uint Send(SNIPacket packet) /// Send a packet asynchronously /// /// SNI packet - /// Completion callback /// SNI error code - public uint SendAsync(SNIPacket packet, SNIAsyncCallback callback) + public uint SendAsync(SNIPacket packet) { long scopeID = SqlClientEventSource.Log.TrySNIScopeEnterEvent(s_className); try { lock (this) { - return _lowerHandle.SendAsync(packet, callback); + return _lowerHandle.SendAsync(packet); } } finally @@ -192,7 +191,7 @@ public void HandleReceiveError(SNIPacket packet) Debug.Assert(Monitor.IsEntered(this), "HandleReceiveError was called without being locked."); foreach (SNIMarsHandle handle in _sessions.Values) { - if (packet.HasCompletionCallback) + if (packet.HasAsyncIOCompletionCallback) { handle.HandleReceiveError(packet); #if DEBUG @@ -215,7 +214,7 @@ public void HandleReceiveError(SNIPacket packet) /// SNI error code public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) { - packet.InvokeCompletionCallback(sniErrorCode); + packet.InvokeAsyncIOCompletionCallback(sniErrorCode); } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs index 62eff5accc..8246ce3d6f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs @@ -19,7 +19,7 @@ internal sealed class SNIMarsHandle : SNIHandle private readonly SNIMarsConnection _connection; private readonly uint _status = TdsEnums.SNI_UNINITIALIZED; private readonly Queue _receivedPacketQueue = new Queue(); - private readonly Queue _sendPacketQueue = new Queue(); + private readonly Queue _sendPacketQueue = new Queue(); private readonly object _callbackObject; private readonly Guid _connectionId; private readonly ushort _sessionId; @@ -191,9 +191,8 @@ public override uint Send(SNIPacket packet) /// Send packet asynchronously /// /// SNI packet - /// Completion callback /// SNI error code - private uint InternalSendAsync(SNIPacket packet, SNIAsyncCallback callback) + private uint InternalSendAsync(SNIPacket packet) { Debug.Assert(packet.ReservedHeaderSize == SNISMUXHeader.HEADER_LENGTH, "mars handle attempting to send muxed packet without smux reservation in InternalSendAsync"); using (TrySNIEventScope.Create("SNIMarsHandle.InternalSendAsync | SNI | INFO | SCOPE | Entering Scope {0}")) @@ -207,9 +206,9 @@ private uint InternalSendAsync(SNIPacket packet, SNIAsyncCallback callback) } SNIPacket muxedPacket = SetPacketSMUXHeader(packet); - muxedPacket.SetCompletionCallback(callback ?? HandleSendComplete); + muxedPacket.SetAsyncIOCompletionCallback(_handleSendCompleteCallback); SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Sending packet", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); - return _connection.SendAsync(muxedPacket, callback); + return _connection.SendAsync(muxedPacket); } } } @@ -222,7 +221,7 @@ private uint SendPendingPackets() { using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) { - SNIMarsQueuedPacket packet = null; + SNIPacket packet = null; while (true) { @@ -233,7 +232,7 @@ private uint SendPendingPackets() if (_sendPacketQueue.Count != 0) { packet = _sendPacketQueue.Peek(); - uint result = InternalSendAsync(packet.Packet, packet.Callback); + uint result = InternalSendAsync(packet); if (result != TdsEnums.SNI_SUCCESS && result != TdsEnums.SNI_SUCCESS_IO_PENDING) { @@ -264,15 +263,15 @@ private uint SendPendingPackets() /// Send a packet asynchronously /// /// SNI packet - /// Completion callback /// SNI error code - public override uint SendAsync(SNIPacket packet, SNIAsyncCallback callback = null) + public override uint SendAsync(SNIPacket packet) { using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) { + packet.SetAsyncIOCompletionCallback(_handleSendCompleteCallback); lock (this) { - _sendPacketQueue.Enqueue(new SNIMarsQueuedPacket(packet, callback ?? _handleSendCompleteCallback)); + _sendPacketQueue.Enqueue(packet); } SendPendingPackets(); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsQueuedPacket.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsQueuedPacket.cs deleted file mode 100644 index 0f97eb4978..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsQueuedPacket.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Data.SqlClient.SNI -{ - /// - /// Mars queued packet - /// - internal sealed class SNIMarsQueuedPacket - { - private readonly SNIPacket _packet; - private readonly SNIAsyncCallback _callback; - - /// - /// Constructor - /// - /// SNI packet - /// Completion callback - public SNIMarsQueuedPacket(SNIPacket packet, SNIAsyncCallback callback) - { - _packet = packet; - _callback = callback; - } - - public SNIPacket Packet => _packet; - - public SNIAsyncCallback Callback => _callback; - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs index c4ba4640f6..a3ba9e0bba 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs @@ -210,10 +210,10 @@ public override uint ReceiveAsync(ref SNIPacket packet) { SNIPacket errorPacket; packet = RentPacket(headerSize: 0, dataSize: _bufferSize); - + packet.SetAsyncIOCompletionCallback(_receiveCallback); try { - packet.ReadFromStreamAsync(_stream, _receiveCallback); + packet.ReadFromStreamAsync(_stream); SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Rented and read packet asynchronously, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); return TdsEnums.SNI_SUCCESS_IO_PENDING; } @@ -288,13 +288,12 @@ public override uint Send(SNIPacket packet) } } - public override uint SendAsync(SNIPacket packet, SNIAsyncCallback callback = null) + public override uint SendAsync(SNIPacket packet) { using (TrySNIEventScope.Create(nameof(SNINpHandle))) { - SNIAsyncCallback cb = callback ?? _sendCallback; SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Packet writing to stream, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); - packet.WriteToStreamAsync(_stream, cb, SNIProviders.NP_PROV); + packet.WriteToStreamAsync(_stream, _sendCallback, SNIProviders.NP_PROV); return TdsEnums.SNI_SUCCESS_IO_PENDING; } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs index 58ac68c7c4..d83682021b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs @@ -6,7 +6,6 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; @@ -19,14 +18,14 @@ namespace Microsoft.Data.SqlClient.SNI /// internal sealed class SNIPacket { + private static readonly Action, object> s_readCallback = ReadFromStreamAsyncContinuation; private int _dataLength; // the length of the data in the data segment, advanced by Append-ing data, does not include smux header length private int _dataCapacity; // the total capacity requested, if the array is rented this may be less than the _data.Length, does not include smux header length private int _dataOffset; // the start point of the data in the data segment, advanced by Take-ing data private int _headerLength; // the amount of space at the start of the array reserved for the smux header, this is zeroed in SetHeader // _headerOffset is not needed because it is always 0 private byte[] _data; - private SNIAsyncCallback _completionCallback; - private readonly Action, object> _readCallback; + private SNIAsyncCallback _asyncIOCompletionCallback; #if DEBUG internal readonly int _id; // in debug mode every packet is assigned a unique id so that the entire lifetime can be tracked when debugging /// refcount = 0 means that a packet should only exist in the pool @@ -85,7 +84,6 @@ public SNIPacket(SNIHandle owner, int id) #endif public SNIPacket() { - _readCallback = ReadFromStreamAsyncContinuation; } /// @@ -110,25 +108,19 @@ public SNIPacket() public int ReservedHeaderSize => _headerLength; - public bool HasCompletionCallback => !(_completionCallback is null); + public bool HasAsyncIOCompletionCallback => _asyncIOCompletionCallback is not null; /// - /// Set async completion callback + /// Set async receive callback /// - /// Completion callback - public void SetCompletionCallback(SNIAsyncCallback completionCallback) - { - _completionCallback = completionCallback; - } + /// Completion callback + public void SetAsyncIOCompletionCallback(SNIAsyncCallback asyncIOCompletionCallback) => _asyncIOCompletionCallback = asyncIOCompletionCallback; /// - /// Invoke the completion callback + /// Invoke the receive callback /// /// SNI error - public void InvokeCompletionCallback(uint sniErrorCode) - { - _completionCallback(this, sniErrorCode); - } + public void InvokeAsyncIOCompletionCallback(uint sniErrorCode) => _asyncIOCompletionCallback(this, sniErrorCode); /// /// Allocate space for data @@ -253,7 +245,7 @@ public void Release() _dataLength = 0; _dataOffset = 0; _headerLength = 0; - _completionCallback = null; + _asyncIOCompletionCallback = null; IsOutOfBand = false; } @@ -273,49 +265,48 @@ public void ReadFromStream(Stream stream) /// Read data from a stream asynchronously /// /// Stream to read from - /// Completion callback - public void ReadFromStreamAsync(Stream stream, SNIAsyncCallback callback) + public void ReadFromStreamAsync(Stream stream) { stream.ReadAsync(_data, 0, _dataCapacity, CancellationToken.None) .ContinueWith( - continuationAction: _readCallback, - state: callback, + continuationAction: s_readCallback, + state: this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default ); } - private void ReadFromStreamAsyncContinuation(Task t, object state) + private static void ReadFromStreamAsyncContinuation(Task task, object state) { - SNIAsyncCallback callback = (SNIAsyncCallback)state; + SNIPacket packet = (SNIPacket)state; bool error = false; - Exception e = t.Exception?.InnerException; + Exception e = task.Exception?.InnerException; if (e != null) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, e); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, Internal Exception occurred while reading data: {1}", args0: _owner?.ConnectionId, args1: e?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, Internal Exception occurred while reading data: {1}", args0: packet._owner?.ConnectionId, args1: e?.Message); #endif error = true; } else { - _dataLength = t.Result; + packet._dataLength = task.Result; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} read from stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} read from stream.", args0: packet._owner?.ConnectionId, args1: packet._id, args2: packet._dataLength); #endif - if (_dataLength == 0) + if (packet._dataLength == 0) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, 0, SNICommon.ConnTerminatedError, Strings.SNI_ERROR_2); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, No data read from stream, connection was terminated.", args0: _owner?.ConnectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, No data read from stream, connection was terminated.", args0: packet._owner?.ConnectionId); #endif error = true; } } - callback(this, error ? TdsEnums.SNI_ERROR : TdsEnums.SNI_SUCCESS); + packet.InvokeAsyncIOCompletionCallback(error ? TdsEnums.SNI_ERROR : TdsEnums.SNI_SUCCESS); } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs index 537b697067..b2ec6ccccd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs @@ -813,14 +813,12 @@ public override void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyn /// Send a packet asynchronously /// /// SNI packet - /// Completion callback /// SNI error code - public override uint SendAsync(SNIPacket packet, SNIAsyncCallback callback = null) + public override uint SendAsync(SNIPacket packet) { using (TrySNIEventScope.Create(nameof(SNITCPHandle))) { - SNIAsyncCallback cb = callback ?? _sendCallback; - packet.WriteToStreamAsync(_stream, cb, SNIProviders.TCP_PROV); + packet.WriteToStreamAsync(_stream, _sendCallback, SNIProviders.TCP_PROV); SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Data sent to stream asynchronously", args0: _connectionId); return TdsEnums.SNI_SUCCESS_IO_PENDING; } @@ -835,10 +833,10 @@ public override uint ReceiveAsync(ref SNIPacket packet) { SNIPacket errorPacket; packet = RentPacket(headerSize: 0, dataSize: _bufferSize); - + packet.SetAsyncIOCompletionCallback(_receiveCallback); try { - packet.ReadFromStreamAsync(_stream, _receiveCallback); + packet.ReadFromStreamAsync(_stream); SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Data received from stream asynchronously", args0: _connectionId); return TdsEnums.SNI_SUCCESS_IO_PENDING; } From b0d0b2af4def7f6fc5caac0131956712fe10eed6 Mon Sep 17 00:00:00 2001 From: David Engel Date: Thu, 26 May 2022 09:59:27 -0700 Subject: [PATCH 05/35] Improve error reporting from SqlDbManager (#1617) --- .../SqlDbManager.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs index a09c9313a8..6a936ac5a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs @@ -55,6 +55,7 @@ public static void Run(string[] args) builder.InitialCatalog = DB_Master; using (SqlConnection conn = new SqlConnection(builder.ConnectionString)) { + Console.WriteLine($"Connecting to {builder.DataSource}"); SqlServer.Management.Smo.Server server = new SqlServer.Management.Smo.Server(new ServerConnection(conn)); ServerConnection context = server.ConnectionContext; @@ -102,7 +103,7 @@ public static void Run(string[] args) } catch (Exception e) { - throw new Exception($"{args[0]} execution failed with Error: {e.Message}"); + throw new Exception($"{args[0]} execution failed with Error: {e}"); } } @@ -158,6 +159,7 @@ private static void UpdateConfig(string key, SqlConnectionStringBuilder builder) private static void DropIfExistsDatabase(string dbName, ServerConnection context) { + Console.WriteLine($"Dropping database [{dbName}] if it exists"); try { string dropScript = $"IF EXISTS (select * from sys.databases where name = '{dbName}') BEGIN DROP DATABASE [{dbName}] END;"; @@ -165,7 +167,7 @@ private static void DropIfExistsDatabase(string dbName, ServerConnection context } catch (ExecutionFailureException ex) { - Console.WriteLine($"FAILED to drop database '{dbName}'. Error message: {ex.Message}"); + Console.WriteLine($"FAILED to drop database '{dbName}'. Error message: {ex}"); } } @@ -174,6 +176,7 @@ private static void CreateDatabase(string dbName, ServerConnection context) DropIfExistsDatabase(dbName, context); string createScript = File.ReadAllText(NorthWindScriptPath); + Console.WriteLine($"Creating database [{dbName}]"); try { createScript = createScript.Replace(DB_Northwind, dbName); @@ -183,7 +186,7 @@ private static void CreateDatabase(string dbName, ServerConnection context) } catch (ExecutionFailureException ex) { - Console.WriteLine(ex.Message); + Console.WriteLine(ex); throw; } } From e1fcf5bf029be64062e5a3a50162f3374d1c45b3 Mon Sep 17 00:00:00 2001 From: Chris B Date: Fri, 27 May 2022 02:41:42 +0200 Subject: [PATCH 06/35] Send retained transaction descriptor in MARS TDS header for .NET Core and .NET 5+ (#1623) (#1624) --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 cfb1f477f4..5ef3842952 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 @@ -10753,7 +10753,8 @@ private void WriteMarsHeaderData(TdsParserStateObject stateObj, SqlInternalTrans } else { - WriteLong(SqlInternalTransaction.NullTransactionId, stateObj); + // If no transaction, send over retained transaction descriptor (empty if none retained) + WriteLong(_retainedTransactionId, stateObj); WriteInt(stateObj.IncrementAndObtainOpenResultCount(null), stateObj); } } From 845902c729b273e2f36337b0affc402e0500ceb7 Mon Sep 17 00:00:00 2001 From: Javad Date: Thu, 26 May 2022 18:52:17 -0700 Subject: [PATCH 07/35] FIX | db_id returns null on Azure SQL Database. (#1294) * db_id() issue. * closed reader. * commit * review comments Co-authored-by: David Engel --- .../Data/SqlClient/SqlDependencyListener.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs index ce266f7fb9..440755ddca 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -106,6 +106,7 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str _con.Open(); _cachedServer = _con.DataSource; + bool? dbId = null; #if NETFRAMEWORK if (hashHelper.Identity != null) @@ -125,7 +126,16 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str CommandText = "select is_broker_enabled from sys.databases where database_id=db_id()" }; - if (!(bool)_com.ExecuteScalar()) + // db_id() returns the database ID of the current database hence it will always be one line result + using (SqlDataReader reader = _com.ExecuteReader(CommandBehavior.SingleRow)) + { + if (reader.Read() && reader[0] is not null) + { + dbId = reader.GetBoolean(0); + } + } + + if (dbId is null || !dbId.Value) { throw SQL.SqlDependencyDatabaseBrokerDisabled(); } From a46f61262a15b9f6998c841318f9851e4e1d2efc Mon Sep 17 00:00:00 2001 From: Michael Wilson <57151586+tf-micwil@users.noreply.github.com> Date: Fri, 27 May 2022 20:45:04 -0400 Subject: [PATCH 08/35] Fix populating of TYPE_SCHEMA column in StructuredTypeMembers metadata (#1500) --- .../SqlClientMetaDataCollectionNames.xml | 4 ++++ .../Microsoft.Data.SqlClient.SqlMetaData.xml | 2 +- .../Microsoft.Data.SqlClient.SqlMetaData.xml | 2 +- .../SqlClient/SqlClientMetaDataCollectionNames.cs | 4 ++++ .../SqlClientMetaDataCollectionNamesTest.cs | 1 + .../SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs | 14 ++++++++++---- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml index e013016ee9..01d30ee8d3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml @@ -60,5 +60,9 @@ A constant for use with the **GetSchema** method that represents the **ColumnSetColumns** collection. To be added. + + A constant for use with the **GetSchema** method that represents the **StructuredTypeMembers** collection. + To be added. + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml index af9532a1ae..73ddc2f575 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml @@ -197,7 +197,7 @@ 4 4 SQLCommand - SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE /* int/decimal/numeric/real/float/money */ WHEN c.system_type_id IN (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE /* int/money/decimal/numeric */ WHEN c.system_type_id IN (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, /* real/float */ convert(int, CASE /* datetime/smalldatetime */ WHEN c.system_type_id IN (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE /* datetime/smalldatetime */ WHEN c.system_type_id IN (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN (35, 167, 175) /*char/varchar/text*/ THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN (99, 231, 239) /*nchar/nvarchar/ntext*/ THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.schemas sc join sys.objects o on sc.schema_id = o.schema_id JOIN sys.table_types tt on o.object_id = tt.type_table_object_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name + SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name 09.00.000.0 diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml index af9532a1ae..73ddc2f575 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml @@ -197,7 +197,7 @@ 4 4 SQLCommand - SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE /* int/decimal/numeric/real/float/money */ WHEN c.system_type_id IN (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE /* int/money/decimal/numeric */ WHEN c.system_type_id IN (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, /* real/float */ convert(int, CASE /* datetime/smalldatetime */ WHEN c.system_type_id IN (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE /* datetime/smalldatetime */ WHEN c.system_type_id IN (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN (35, 167, 175) /*char/varchar/text*/ THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN (99, 231, 239) /*nchar/nvarchar/ntext*/ THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.schemas sc join sys.objects o on sc.schema_id = o.schema_id JOIN sys.table_types tt on o.object_id = tt.type_table_object_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name + SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name 09.00.000.0 diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs index 04ac2aab0f..d67d58d18d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs @@ -48,5 +48,9 @@ public static class SqlClientMetaDataCollectionNames /// public static readonly string ColumnSetColumns = "ColumnSetColumns"; + + /// + public static readonly string StructuredTypeMembers = "StructuredTypeMembers"; + } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs index 789949ee68..7c51a326c2 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs @@ -25,6 +25,7 @@ public void ValuesTest() Assert.Equal("Views", SqlClientMetaDataCollectionNames.Views); Assert.Equal("AllColumns", SqlClientMetaDataCollectionNames.AllColumns); Assert.Equal("ColumnSetColumns", SqlClientMetaDataCollectionNames.ColumnSetColumns); + Assert.Equal("StructuredTypeMembers", SqlClientMetaDataCollectionNames.StructuredTypeMembers); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs index 7b2742e8d6..283ddc5c63 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs @@ -64,7 +64,7 @@ public static void GetAllColumnsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); } - + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void GetColumnSetColumnsFromSchema() { @@ -93,7 +93,13 @@ public static void GetViewColumnsFromSchema() public static void GetUserDefinedTypesFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" }); - } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void GetStructuredTypeMembersFromSchema() + { + VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" }); + } private static void VerifySchemaTable(string schemaItemName, string[] testColumnNames) { @@ -104,11 +110,11 @@ private static void VerifySchemaTable(string schemaItemName, string[] testColumn using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) { - // Connect to the database then retrieve the schema information. + // Connect to the database then retrieve the schema information connection.Open(); DataTable table = connection.GetSchema(schemaItemName); - // Get all table columns + // Get all table columns HashSet columnNames = new HashSet(); foreach (DataColumn column in table.Columns) From 2cfac4d415990e8618b0c3e26d426ce9fe4665dc Mon Sep 17 00:00:00 2001 From: Javad Date: Mon, 30 May 2022 13:47:53 -0700 Subject: [PATCH 09/35] Fix CommandText length for stored procedures (#1484) --- .../Microsoft/Data/SqlClient/SqlCommand.cs | 16 +++++++- .../Microsoft/Data/SqlClient/SqlCommand.cs | 19 +++++++-- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 + .../SqlCommand/SqlCommandStoredProcTest.cs | 39 +++++++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index 61b061610f..66bacefd98 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -34,6 +34,7 @@ namespace Microsoft.Data.SqlClient public sealed partial class SqlCommand : DbCommand, ICloneable { private static int _objectTypeCount; // EventSource Counter + private const int MaxRPCNameLength = 1046; internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); private string _commandText; private static readonly Func s_beginExecuteReaderAsync = BeginExecuteReaderAsyncCallback; @@ -5803,7 +5804,20 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql GetRPCObject(0, userParameterCount, ref rpc); rpc.ProcID = 0; - rpc.rpcName = this.CommandText; // just get the raw command text + + // TDS Protocol allows rpc name with maximum length of 1046 bytes for ProcName + // 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523 + // each char takes 2 bytes. 523 * 2 = 1046 + int commandTextLength = ADP.CharSize * CommandText.Length; + + if (commandTextLength <= MaxRPCNameLength) + { + rpc.rpcName = CommandText; // just get the raw command text + } + else + { + throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength); + } SetUpRPCParameters(rpc, inSchema, parameters); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index 89a56464be..8b8d298bb6 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -40,6 +40,7 @@ namespace Microsoft.Data.SqlClient public sealed class SqlCommand : DbCommand, ICloneable { private static int _objectTypeCount; // EventSource Counter + private const int MaxRPCNameLength = 1046; internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); private string _commandText; @@ -1081,7 +1082,7 @@ public override void Prepare() { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG InternalPrepare(); } @@ -1266,7 +1267,7 @@ public override void Cancel() { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); @@ -6692,7 +6693,19 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql int count = CountSendableParameters(parameters); GetRPCObject(count, ref rpc); - rpc.rpcName = this.CommandText; // just get the raw command text + // TDS Protocol allows rpc name with maximum length of 1046 bytes for ProcName + // 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523 + // each char takes 2 bytes. 523 * 2 = 1046 + int commandTextLength = ADP.CharSize * CommandText.Length; + + if (commandTextLength <= MaxRPCNameLength) + { + rpc.rpcName = CommandText; // just get the raw command text + } + else + { + throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength); + } SetUpRPCParameters(rpc, 0, inSchema, parameters); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 14c95c74d0..48288c80ca 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -272,6 +272,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs new file mode 100644 index 0000000000..211e9c0d38 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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 System.Data; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class SqlCommandStoredProcTest + { + private static readonly string s_tcp_connStr = DataTestUtility.TCPConnectionString; + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void ShouldFailWithExceededLengthForSP() + { + string baseCommandText = "random text\u0000\u400a\u7300\u7400\u6100\u7400\u6500\u6d00\u6500\u6e00\u7400\u0000\u0006\u01ff\u0900\uf004\u0000\uffdc\u0001"; + string exceededLengthText = baseCommandText + new string(' ', 2000); + using SqlConnection conn = new(s_tcp_connStr); + conn.Open(); + using SqlCommand command = new() + { + Connection = conn, + CommandType = CommandType.StoredProcedure, + CommandText = exceededLengthText + }; + + // It should fail on the driver as the length of RPC is over 1046 + // 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523 + // each char takes 2 bytes. 523 * 2 = 1046 + Assert.Throws(() => command.ExecuteScalar()); + + command.CommandText = baseCommandText; + var ex = Assert.Throws(() => command.ExecuteScalar()); + Assert.StartsWith("Could not find stored procedure", ex.Message); + } + } +} From a43348b4044c952cdb65d87a951b90375087cc44 Mon Sep 17 00:00:00 2001 From: David Engel Date: Mon, 30 May 2022 14:59:06 -0700 Subject: [PATCH 10/35] Automatically adjust the default ConnectRetryCount if targeting OnDemand endpoint (#1626) --- .../Microsoft/Data/SqlClient/SqlConnection.cs | 10 +++++-- .../Microsoft/Data/SqlClient/SqlConnection.cs | 10 +++++-- .../src/Microsoft/Data/Common/AdapterUtil.cs | 19 ++++++++++-- .../FunctionalTests/SqlConnectionTest.cs | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+), 7 deletions(-) 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 bcb9378643..08f32ef0bf 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 @@ -412,9 +412,15 @@ private void CacheConnectionStringProperties() if (connString != null) { _connectRetryCount = connString.ConnectRetryCount; + // For Azure Synapse ondemand connections, set _connectRetryCount to 5 instead of 1 to greatly improve recovery + // success rate. Note: Synapse should be detected first as it could be detected as a regular Azure SQL DB endpoint. + if (_connectRetryCount == 1 && ADP.IsAzureSynapseOnDemandEndpoint(connString.DataSource)) + { + _connectRetryCount = 5; + } // For Azure SQL connection, set _connectRetryCount to 2 instead of 1 will greatly improve recovery - // success rate - if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(connString.DataSource)) + // success rate + else if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(connString.DataSource)) { _connectRetryCount = 2; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index 2172fa6151..ae1c8d50db 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -432,9 +432,15 @@ private void CacheConnectionStringProperties() if (connString != null) { _connectRetryCount = connString.ConnectRetryCount; + // For Azure Synapse ondemand connections, set _connectRetryCount to 5 instead of 1 to greatly improve recovery + // success rate. Note: Synapse should be detected first as it could be detected as a regular Azure SQL DB endpoint. + if (_connectRetryCount == 1 && ADP.IsAzureSynapseOnDemandEndpoint(connString.DataSource)) + { + _connectRetryCount = 5; + } // For Azure SQL connection, set _connectRetryCount to 2 instead of 1 will greatly improve recovery - // success rate - if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(connString.DataSource)) + // success rate + else if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(connString.DataSource)) { _connectRetryCount = 2; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index d7f6ce7cf0..290bed05c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -686,13 +686,26 @@ internal static Version GetAssemblyVersion() } + private const string ONDEMAND_PREFIX = "-ondemand"; + private const string AZURE_SYNAPSE = "-ondemand.sql.azuresynapse."; + + internal static bool IsAzureSynapseOnDemandEndpoint(string dataSource) + { + return IsEndpoint(dataSource, ONDEMAND_PREFIX) || dataSource.Contains(AZURE_SYNAPSE); + } + internal static readonly string[] s_azureSqlServerEndpoints = { StringsHelper.GetString(Strings.AZURESQL_GenericEndpoint), StringsHelper.GetString(Strings.AZURESQL_GermanEndpoint), StringsHelper.GetString(Strings.AZURESQL_UsGovEndpoint), StringsHelper.GetString(Strings.AZURESQL_ChinaEndpoint)}; - // This method assumes dataSource parameter is in TCP connection string format. internal static bool IsAzureSqlServerEndpoint(string dataSource) + { + return IsEndpoint(dataSource, null); + } + + // This method assumes dataSource parameter is in TCP connection string format. + private static bool IsEndpoint(string dataSource, string prefix) { int length = dataSource.Length; // remove server port @@ -715,10 +728,10 @@ internal static bool IsAzureSqlServerEndpoint(string dataSource) length -= 1; } - // check if servername end with any azure endpoints + // check if servername ends with any endpoints for (int index = 0; index < s_azureSqlServerEndpoints.Length; index++) { - string endpoint = s_azureSqlServerEndpoints[index]; + string endpoint = string.IsNullOrEmpty(prefix) ? s_azureSqlServerEndpoints[index] : prefix + s_azureSqlServerEndpoints[index]; if (length > endpoint.Length) { if (string.Compare(dataSource, length - endpoint.Length, endpoint, 0, endpoint.Length, StringComparison.OrdinalIgnoreCase) == 0) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index 888abca555..af8c76878a 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -6,6 +6,7 @@ using System.Data; using System.Collections.Generic; using Xunit; +using System.Reflection; namespace Microsoft.Data.SqlClient.Tests { @@ -1031,5 +1032,34 @@ public void ConnectionString_IPAddressPreference_Invalid(string value) Assert.Contains("'ip address preference'", ex.Message, StringComparison.OrdinalIgnoreCase); Assert.Null(ex.ParamName); } + + [Fact] + public void ConnectionRetryForNonAzureEndpoints() + { + SqlConnection cn = new SqlConnection("Data Source = someserver"); + FieldInfo field = typeof(SqlConnection).GetField("_connectRetryCount", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field.GetValue(cn)); + Assert.Equal(1, (int)field.GetValue(cn)); + } + + [Fact] + public void ConnectionRetryForAzureDbEndpoints() + { + SqlConnection cn = new SqlConnection("Data Source = someserver.database.windows.net"); + FieldInfo field = typeof(SqlConnection).GetField("_connectRetryCount", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field.GetValue(cn)); + Assert.Equal(2, (int)field.GetValue(cn)); + } + + [Theory] + [InlineData("myserver-ondemand.sql.azuresynapse.net")] + [InlineData("someserver-ondemand.database.windows.net")] + public void ConnectionRetryForAzureOnDemandEndpoints(string serverName) + { + SqlConnection cn = new SqlConnection($"Data Source = {serverName}"); + FieldInfo field = typeof(SqlConnection).GetField("_connectRetryCount", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field.GetValue(cn)); + Assert.Equal(5, (int)field.GetValue(cn)); + } } } From 73aa0da943962c31c887f5638816dc68b00f19a4 Mon Sep 17 00:00:00 2001 From: Wraith Date: Tue, 31 May 2022 17:19:10 +0100 Subject: [PATCH 11/35] Combine: Move SqlEnvChange to shared and sync usage (#1525) --- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../SqlClient/SqlInternalConnectionTds.cs | 34 ++-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 138 ++++++------- .../Data/SqlClient/TdsParserHelperClasses.cs | 64 ------ .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../SqlClient/SqlInternalConnectionTds.cs | 35 ++-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 187 +++++++++--------- .../Data/SqlClient/TdsParserHelperClasses.cs | 17 -- .../Data/SqlClient/TdsParserStateObject.cs | 10 +- .../Microsoft/Data/SqlClient/SqlEnvChange.cs | 72 +++++++ 10 files changed, 289 insertions(+), 274 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnvChange.cs 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 8ca4487b3a..0763809c20 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -388,6 +388,9 @@ Microsoft\Data\SqlClient\SqlEnums.cs + + Microsoft\Data\SqlClient\SqlEnvChange.cs + Microsoft\Data\SqlClient\SqlError.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index dcbc62ef0d..ac3aa1f30b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1994,36 +1994,36 @@ internal bool IgnoreEnvChange internal void OnEnvChange(SqlEnvChange rec) { Debug.Assert(!IgnoreEnvChange, "This function should not be called if IgnoreEnvChange is set!"); - switch (rec.type) + switch (rec._type) { case TdsEnums.ENV_DATABASE: // If connection is not open and recovery is not in progress, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { - _originalDatabase = rec.newValue; + _originalDatabase = rec._newValue; } - CurrentDatabase = rec.newValue; + CurrentDatabase = rec._newValue; break; case TdsEnums.ENV_LANG: // If connection is not open and recovery is not in progress, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { - _originalLanguage = rec.newValue; + _originalLanguage = rec._newValue; } - _currentLanguage = rec.newValue; + _currentLanguage = rec._newValue; break; case TdsEnums.ENV_PACKETSIZE: - _currentPacketSize = int.Parse(rec.newValue, CultureInfo.InvariantCulture); + _currentPacketSize = int.Parse(rec._newValue, CultureInfo.InvariantCulture); break; case TdsEnums.ENV_COLLATION: if (_currentSessionData != null) { - _currentSessionData._collation = rec.newCollation; + _currentSessionData._collation = rec._newCollation; } break; @@ -2043,20 +2043,20 @@ internal void OnEnvChange(SqlEnvChange rec) { throw SQL.ROR_FailoverNotSupportedServer(this); } - _currentFailoverPartner = rec.newValue; + _currentFailoverPartner = rec._newValue; break; case TdsEnums.ENV_PROMOTETRANSACTION: - byte[] dtcToken = null; - if (rec.newBinRented) + byte[] dtcToken; + if (rec._newBinRented) { - dtcToken = new byte[rec.newLength]; - Buffer.BlockCopy(rec.newBinValue, 0, dtcToken, 0, dtcToken.Length); + dtcToken = new byte[rec._newLength]; + Buffer.BlockCopy(rec._newBinValue, 0, dtcToken, 0, dtcToken.Length); } else { - dtcToken = rec.newBinValue; - rec.newBinValue = null; + dtcToken = rec._newBinValue; + rec._newBinValue = null; } PromotedDTCToken = dtcToken; break; @@ -2077,16 +2077,16 @@ internal void OnEnvChange(SqlEnvChange rec) break; case TdsEnums.ENV_USERINSTANCE: - _instanceName = rec.newValue; + _instanceName = rec._newValue; break; case TdsEnums.ENV_ROUTING: SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received routing info", ObjectID); - if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) + if (string.IsNullOrEmpty(rec._newRoutingInfo.ServerName) || rec._newRoutingInfo.Protocol != 0 || rec._newRoutingInfo.Port == 0) { throw SQL.ROR_InvalidRoutingInfo(this); } - RoutingInfo = rec.newRoutingInfo; + RoutingInfo = rec._newRoutingInfo; break; default: 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 5ef3842952..ec1e898691 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 @@ -2179,7 +2179,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead { if (!this.Connection.IgnoreEnvChange) { - switch (env.type) + switch (env._type) { case TdsEnums.ENV_BEGINTRAN: case TdsEnums.ENV_ENLISTDTC: @@ -2194,12 +2194,12 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead if (null != _currentTransaction) { - _currentTransaction.TransactionId = env.newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. + _currentTransaction.TransactionId = env._newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. } else { - TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env.type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; - _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env.newLongValue); + TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env._type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; + _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env._newLongValue); } if (null != _statistics && !_statisticsIsInTransaction) { @@ -2224,22 +2224,22 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead // Check null for case where Begin and Rollback obtained in the same message. if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) { - Debug.Assert(_currentTransaction.TransactionId != env.newLongValue, "transaction id's are not equal!"); + Debug.Assert(_currentTransaction.TransactionId != env._newLongValue, "transaction id's are not equal!"); } #endif - if (TdsEnums.ENV_COMMITTRAN == env.type) + if (TdsEnums.ENV_COMMITTRAN == env._type) { _currentTransaction.Completed(TransactionState.Committed); } - else if (TdsEnums.ENV_ROLLBACKTRAN == env.type) + else if (TdsEnums.ENV_ROLLBACKTRAN == env._type) { // Hold onto transaction id if distributed tran is rolled back. This must // be sent to the server on subsequent executions even though the transaction // is considered to be rolled back. if (_currentTransaction.IsDistributed && _currentTransaction.IsActive) { - _retainedTransactionId = env.oldLongValue; + _retainedTransactionId = env._oldLongValue; } _currentTransaction.Completed(TransactionState.Aborted); } @@ -2258,7 +2258,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead } } SqlEnvChange head = env; - env = env.Next; + env = env._next; head.Clear(); head = null; } @@ -2556,7 +2556,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, while (tokenLength > processedLength) { var env = new SqlEnvChange(); - if (!stateObj.TryReadByte(out env.type)) + if (!stateObj.TryReadByte(out env._type)) { return false; } @@ -2568,11 +2568,11 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, } else { - tail.Next = env; + tail._next = env; tail = env; } - switch (env.type) + switch (env._type) { case TdsEnums.ENV_DATABASE: case TdsEnums.ENV_LANG: @@ -2589,16 +2589,16 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - if (env.newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) + if (env._newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) { _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } else { - Debug.Assert(env.newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); + Debug.Assert(env._newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); - string stringCodePage = env.newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); + string stringCodePage = env._newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); _defaultCodePage = int.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); @@ -2614,9 +2614,10 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, // Changing packet size does not support retry, should not pend" throw SQL.SynchronousCallMayNotPend(); } + // Only set on physical state object - this should only occur on LoginAck prior // to MARS initialization! - int packetSize = int.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + int packetSize = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); if (_physicalStateObj.SetPacketSize(packetSize)) { @@ -2626,7 +2627,6 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, // Update SNI ConsumerInfo value to be resulting packet size uint unsignedPacketSize = (uint)packetSize; - uint result = _physicalStateObj.SetConnectionBufferSize(ref unsignedPacketSize); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); } @@ -2638,7 +2638,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - _defaultLCID = int.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + _defaultLCID = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); break; case TdsEnums.ENV_COMPFLAGS: @@ -2649,54 +2649,54 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, break; case TdsEnums.ENV_COLLATION: - Debug.Assert(env.newLength == 5 || env.newLength == 0, "Improper length in new collation!"); + Debug.Assert(env._newLength == 5 || env._newLength == 0, "Improper length in new collation!"); if (!stateObj.TryReadByte(out byteLength)) { return false; } - env.newLength = byteLength; - if (env.newLength == 5) + env._newLength = byteLength; + if (env._newLength == 5) { - if (!TryProcessCollation(stateObj, out env.newCollation)) + if (!TryProcessCollation(stateObj, out env._newCollation)) { return false; } // Give the parser the new collation values in case parameters don't specify one - _defaultCollation = env.newCollation; + _defaultCollation = env._newCollation; // UTF8 collation - if (env.newCollation.IsUTF8) + if (env._newCollation.IsUTF8) { _defaultEncoding = Encoding.UTF8; } else { - int newCodePage = GetCodePage(env.newCollation, stateObj); + int newCodePage = GetCodePage(env._newCollation, stateObj); if (newCodePage != _defaultCodePage) { _defaultCodePage = newCodePage; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } } - _defaultLCID = env.newCollation.LCID; + _defaultLCID = env._newCollation.LCID; } if (!stateObj.TryReadByte(out byteLength)) { return false; } - env.oldLength = byteLength; - Debug.Assert(env.oldLength == 5 || env.oldLength == 0, "Improper length in old collation!"); - if (env.oldLength == 5) + env._oldLength = byteLength; + Debug.Assert(env._oldLength == 5 || env._oldLength == 0, "Improper length in old collation!"); + if (env._oldLength == 5) { - if (!TryProcessCollation(stateObj, out env.oldCollation)) + if (!TryProcessCollation(stateObj, out env._oldCollation)) { return false; } } - env.length = 3 + env.newLength + env.oldLength; + env._length = 3 + env._newLength + env._oldLength; break; case TdsEnums.ENV_BEGINTRAN: @@ -2709,44 +2709,44 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.newLength = byteLength; - Debug.Assert(env.newLength == 0 || env.newLength == 8, "Improper length for new transaction id!"); + env._newLength = byteLength; + Debug.Assert(env._newLength == 0 || env._newLength == 8, "Improper length for new transaction id!"); - if (env.newLength > 0) + if (env._newLength > 0) { - if (!stateObj.TryReadInt64(out env.newLongValue)) + if (!stateObj.TryReadInt64(out env._newLongValue)) { return false; } - Debug.Assert(env.newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. + Debug.Assert(env._newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { - env.newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. + env._newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } if (!stateObj.TryReadByte(out byteLength)) { return false; } - env.oldLength = byteLength; - Debug.Assert(env.oldLength == 0 || env.oldLength == 8, "Improper length for old transaction id!"); + env._oldLength = byteLength; + Debug.Assert(env._oldLength == 0 || env._oldLength == 8, "Improper length for old transaction id!"); - if (env.oldLength > 0) + if (env._oldLength > 0) { - if (!stateObj.TryReadInt64(out env.oldLongValue)) + if (!stateObj.TryReadInt64(out env._oldLongValue)) { return false; } - Debug.Assert(env.oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. + Debug.Assert(env._oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { - env.oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. + env._oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } // env.length includes 1 byte type token - env.length = 3 + env.newLength + env.oldLength; + env._length = 3 + env._newLength + env._oldLength; break; case TdsEnums.ENV_LOGSHIPNODE: @@ -2759,12 +2759,12 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, break; case TdsEnums.ENV_PROMOTETRANSACTION: - if (!stateObj.TryReadInt32(out env.newLength)) + if (!stateObj.TryReadInt32(out env._newLength)) { // new value has 4 byte length return false; } - env.newBinValue = new byte[env.newLength]; - if (!stateObj.TryReadByteArray(env.newBinValue, env.newLength)) + env._newBinValue = new byte[env._newLength]; + if (!stateObj.TryReadByteArray(env._newBinValue, env._newLength)) { // read new value with 4 byte length return false; } @@ -2773,11 +2773,11 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.oldLength = byteLength; - Debug.Assert(0 == env.oldLength, "old length should be zero"); + env._oldLength = byteLength; + Debug.Assert(0 == env._oldLength, "old length should be zero"); // env.length includes 1 byte for type token - env.length = 5 + env.newLength; + env._length = 5 + env._newLength; break; case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: @@ -2801,7 +2801,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.newLength = newLength; + env._newLength = newLength; byte protocol; if (!stateObj.TryReadByte(out protocol)) { @@ -2822,7 +2822,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.newRoutingInfo = new RoutingInfo(protocol, port, serverName); + env._newRoutingInfo = new RoutingInfo(protocol, port, serverName); ushort oldLength; if (!stateObj.TryReadUInt16(out oldLength)) { @@ -2832,14 +2832,14 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.length = env.newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] + env._length = env._newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] break; default: - Debug.Fail("Unknown environment change token: " + env.type); + Debug.Fail("Unknown environment change token: " + env._type); break; } - processedLength += env.length; + processedLength += env._length; } sqlEnvChange = head; @@ -2854,10 +2854,10 @@ private bool TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject state { return false; } - env.newLength = byteLength; - env.newBinValue = ArrayPool.Shared.Rent(env.newLength); - env.newBinRented = true; - if (!stateObj.TryReadByteArray(env.newBinValue, env.newLength)) + env._newLength = byteLength; + env._newBinValue = ArrayPool.Shared.Rent(env._newLength); + env._newBinRented = true; + if (!stateObj.TryReadByteArray(env._newBinValue, env._newLength)) { return false; } @@ -2865,16 +2865,16 @@ private bool TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject state { return false; } - env.oldLength = byteLength; - env.oldBinValue = ArrayPool.Shared.Rent(env.oldLength); - env.oldBinRented = true; - if (!stateObj.TryReadByteArray(env.oldBinValue, env.oldLength)) + env._oldLength = byteLength; + env._oldBinValue = ArrayPool.Shared.Rent(env._oldLength); + env._oldBinRented = true; + if (!stateObj.TryReadByteArray(env._oldBinValue, env._oldLength)) { return false; } // env.length includes 1 byte type token - env.length = 3 + env.newLength + env.oldLength; + env._length = 3 + env._newLength + env._oldLength; return true; } @@ -2900,13 +2900,13 @@ private bool TryReadTwoStringFields(SqlEnvChange env, TdsParserStateObject state return false; } - env.newLength = newLength; - env.newValue = newValue; - env.oldLength = oldLength; - env.oldValue = oldValue; + env._newLength = newLength; + env._newValue = newValue; + env._oldLength = oldLength; + env._oldValue = oldValue; // env.length includes 1 byte type token - env.length = 3 + env.newLength * 2 + env.oldLength * 2; + env._length = 3 + env._newLength * 2 + env._oldLength * 2; return true; } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 6daecb969c..85f4588c8c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -94,70 +94,6 @@ internal RoutingInfo(byte protocol, ushort port, string servername) } } - internal sealed class SqlEnvChange - { - internal byte type; - internal byte oldLength; - internal int newLength; // 7206 TDS changes makes this length an int - internal int length; - internal string newValue; - internal string oldValue; - /// - /// contains binary data, before using this field check newBinRented to see if you can take the field array or whether you should allocate and copy - /// - internal byte[] newBinValue; - /// - /// contains binary data, before using this field check newBinRented to see if you can take the field array or whether you should allocate and copy - /// - internal byte[] oldBinValue; - internal long newLongValue; - internal long oldLongValue; - internal SqlCollation newCollation; - internal SqlCollation oldCollation; - internal RoutingInfo newRoutingInfo; - internal bool newBinRented; - internal bool oldBinRented; - - internal SqlEnvChange Next; - - internal void Clear() - { - type = 0; - oldLength = 0; - newLength = 0; - length = 0; - newValue = null; - oldValue = null; - if (newBinValue != null) - { - Array.Clear(newBinValue, 0, newBinValue.Length); - if (newBinRented) - { - ArrayPool.Shared.Return(newBinValue); - } - - newBinValue = null; - } - if (oldBinValue != null) - { - Array.Clear(oldBinValue, 0, oldBinValue.Length); - if (oldBinRented) - { - ArrayPool.Shared.Return(oldBinValue); - } - oldBinValue = null; - } - newBinRented = false; - oldBinRented = false; - newLongValue = 0; - oldLongValue = 0; - newCollation = null; - oldCollation = null; - newRoutingInfo = null; - Next = null; - } - } - internal sealed class SqlLogin { internal SqlAuthenticationMethod authentication = SqlAuthenticationMethod.NotSpecified; // Authentication type diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index b1e683badc..3e195fdcac 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -452,6 +452,9 @@ Microsoft\Data\SqlClient\SqlEnums.cs + + Microsoft\Data\SqlClient\SqlEnvChange.cs + Microsoft\Data\SqlClient\SqlError.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 616c44a3f4..529b38f2d3 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2457,36 +2457,36 @@ internal bool IgnoreEnvChange internal void OnEnvChange(SqlEnvChange rec) { Debug.Assert(!IgnoreEnvChange, "This function should not be called if IgnoreEnvChange is set!"); - switch (rec.type) + switch (rec._type) { case TdsEnums.ENV_DATABASE: // If connection is not open and recovery is not in progresss, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { - _originalDatabase = rec.newValue; + _originalDatabase = rec._newValue; } - CurrentDatabase = rec.newValue; + CurrentDatabase = rec._newValue; break; case TdsEnums.ENV_LANG: // If connection is not open and recovery is not in progresss, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { - _originalLanguage = rec.newValue; + _originalLanguage = rec._newValue; } - _currentLanguage = rec.newValue; // TODO: finish this. + _currentLanguage = rec._newValue; // TODO: finish this. break; case TdsEnums.ENV_PACKETSIZE: - _currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture); + _currentPacketSize = int.Parse(rec._newValue, CultureInfo.InvariantCulture); break; case TdsEnums.ENV_COLLATION: if (_currentSessionData != null) { - _currentSessionData._collation = rec.newCollation; + _currentSessionData._collation = rec._newCollation; } break; @@ -2502,11 +2502,22 @@ internal void OnEnvChange(SqlEnvChange rec) break; case TdsEnums.ENV_LOGSHIPNODE: - _currentFailoverPartner = rec.newValue; + _currentFailoverPartner = rec._newValue; break; case TdsEnums.ENV_PROMOTETRANSACTION: - PromotedDTCToken = rec.newBinValue; + byte[] dtcToken; + if (rec._newBinRented) + { + dtcToken = new byte[rec._newLength]; + Buffer.BlockCopy(rec._newBinValue, 0, dtcToken, 0, dtcToken.Length); + } + else + { + dtcToken = rec._newBinValue; + rec._newBinValue = null; + } + PromotedDTCToken = dtcToken; break; case TdsEnums.ENV_TRANSACTIONENDED: @@ -2525,16 +2536,16 @@ internal void OnEnvChange(SqlEnvChange rec) break; case TdsEnums.ENV_USERINSTANCE: - _instanceName = rec.newValue; + _instanceName = rec._newValue; break; case TdsEnums.ENV_ROUTING: SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received routing info", ObjectID); - if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) + if (string.IsNullOrEmpty(rec._newRoutingInfo.ServerName) || rec._newRoutingInfo.Protocol != 0 || rec._newRoutingInfo.Port == 0) { throw SQL.ROR_InvalidRoutingInfo(this); } - _routingInfo = rec.newRoutingInfo; + _routingInfo = rec._newRoutingInfo; break; default: 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 9301a3d958..a181d96276 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 @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; @@ -2574,17 +2575,17 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead // ENVCHANGE must be processed synchronously (since it can modify the state of many objects) stateObj._syncOverAsync = true; - SqlEnvChange[] env; + SqlEnvChange env; if (!TryProcessEnvChange(tokenLength, stateObj, out env)) { return false; } - for (int ii = 0; ii < env.Length; ii++) + while (env != null) { - if (env[ii] != null && !this.Connection.IgnoreEnvChange) + if (!Connection.IgnoreEnvChange) { - switch (env[ii].type) + switch (env._type) { case TdsEnums.ENV_BEGINTRAN: case TdsEnums.ENV_ENLISTDTC: @@ -2599,12 +2600,12 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead if (null != _currentTransaction) { - _currentTransaction.TransactionId = env[ii].newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. + _currentTransaction.TransactionId = env._newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. } else { - TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env[ii].type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; - _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env[ii].newLongValue); + TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env._type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; + _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env._newLongValue); } if (null != _statistics && !_statisticsIsInTransaction) { @@ -2630,22 +2631,22 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead // Check null for case where Begin and Rollback obtained in the same message. if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) { - Debug.Assert(_currentTransaction.TransactionId != env[ii].newLongValue, "transaction id's are not equal!"); + Debug.Assert(_currentTransaction.TransactionId != env._newLongValue, "transaction id's are not equal!"); } #endif - if (TdsEnums.ENV_COMMITTRAN == env[ii].type) + if (TdsEnums.ENV_COMMITTRAN == env._type) { _currentTransaction.Completed(TransactionState.Committed); } - else if (TdsEnums.ENV_ROLLBACKTRAN == env[ii].type) + else if (TdsEnums.ENV_ROLLBACKTRAN == env._type) { // Hold onto transaction id if distributed tran is rolled back. This must // be sent to the server on subsequent executions even though the transaction // is considered to be rolled back. if (_currentTransaction.IsDistributed && _currentTransaction.IsActive) { - _retainedTransactionId = env[ii].oldLongValue; + _retainedTransactionId = env._oldLongValue; } _currentTransaction.Completed(TransactionState.Aborted); } @@ -2659,10 +2660,15 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead _statisticsIsInTransaction = false; break; default: - _connHandler.OnEnvChange(env[ii]); + _connHandler.OnEnvChange(env); break; } } + + SqlEnvChange head = env; + env = env._next; + head.Clear(); + head = null; } break; } @@ -2948,41 +2954,36 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead return true; } - private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange[] sqlEnvChange) + private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange sqlEnvChange) { // There could be multiple environment change messages following this token. byte byteLength; int processedLength = 0; - int nvalues = 0; - SqlEnvChange[] envarray = new SqlEnvChange[3]; // Why is this hardcoded to 3? + SqlEnvChange head = null; + SqlEnvChange tail = null; sqlEnvChange = null; while (tokenLength > processedLength) { - - if (nvalues >= envarray.Length) + var env = new SqlEnvChange(); + if (!stateObj.TryReadByte(out env._type)) { - // This is a rare path. Most of the time we will have 1 or 2 envchange data streams. - SqlEnvChange[] newenvarray = new SqlEnvChange[envarray.Length + 3]; - - for (int ii = 0; ii < envarray.Length; ii++) - newenvarray[ii] = envarray[ii]; - - envarray = newenvarray; + return false; } - SqlEnvChange env = new SqlEnvChange(); - - if (!stateObj.TryReadByte(out env.type)) + if (head is null) { - return false; + head = env; + tail = env; + } + else + { + tail._next = env; + tail = env; } - envarray[nvalues] = env; - nvalues++; - - switch (env.type) + switch (env._type) { case TdsEnums.ENV_DATABASE: case TdsEnums.ENV_LANG: @@ -3000,18 +3001,18 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - if (env.newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) + if (env._newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) { _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } else { - Debug.Assert(env.newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); + Debug.Assert(env._newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); - string stringCodePage = env.newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); + string stringCodePage = env._newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); - _defaultCodePage = Int32.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); + _defaultCodePage = int.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } @@ -3028,7 +3029,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, // Only set on physical state object - this should only occur on LoginAck prior // to MARS initialization! - Int32 packetSize = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + int packetSize = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); if (_physicalStateObj.SetPacketSize(packetSize)) { @@ -3037,8 +3038,8 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, _physicalStateObj.ClearAllWritePackets(); // Update SNI ConsumerInfo value to be resulting packet size - UInt32 unsignedPacketSize = (UInt32)packetSize; - UInt32 result = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); + uint unsignedPacketSize = (uint)packetSize; + uint result = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); } @@ -3052,7 +3053,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - _defaultLCID = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + _defaultLCID = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); break; case TdsEnums.ENV_COMPFLAGS: @@ -3063,27 +3064,28 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, break; case TdsEnums.ENV_COLLATION: - Debug.Assert(env.newLength == 5 || env.newLength == 0, "Improper length in new collation!"); + Debug.Assert(env._newLength == 5 || env._newLength == 0, "Improper length in new collation!"); if (!stateObj.TryReadByte(out byteLength)) { return false; } - env.newLength = byteLength; - if (env.newLength == 5) + env._newLength = byteLength; + if (env._newLength == 5) { - if (!TryProcessCollation(stateObj, out env.newCollation)) + if (!TryProcessCollation(stateObj, out env._newCollation)) { return false; } // give the parser the new collation values in case parameters don't specify one - _defaultCollation = env.newCollation; - _defaultLCID = env.newCollation.LCID; + _defaultCollation = env._newCollation; + _defaultLCID = env._newCollation.LCID; - int newCodePage = GetCodePage(env.newCollation, stateObj); + int newCodePage = GetCodePage(env._newCollation, stateObj); - if (env.newCollation.IsUTF8) - { // UTF8 collation + // UTF8 collation + if (env._newCollation.IsUTF8) + { _defaultEncoding = Encoding.UTF8; if (newCodePage != _defaultCodePage) @@ -3106,17 +3108,17 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.oldLength = byteLength; - Debug.Assert(env.oldLength == 5 || env.oldLength == 0, "Improper length in old collation!"); - if (env.oldLength == 5) + env._oldLength = byteLength; + Debug.Assert(env._oldLength == 5 || env._oldLength == 0, "Improper length in old collation!"); + if (env._oldLength == 5) { - if (!TryProcessCollation(stateObj, out env.oldCollation)) + if (!TryProcessCollation(stateObj, out env._oldCollation)) { return false; } } - env.length = 3 + env.newLength + env.oldLength; + env._length = 3 + env._newLength + env._oldLength; break; case TdsEnums.ENV_BEGINTRAN: @@ -3131,44 +3133,44 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.newLength = byteLength; - Debug.Assert(env.newLength == 0 || env.newLength == 8, "Improper length for new transaction id!"); + env._newLength = byteLength; + Debug.Assert(env._newLength == 0 || env._newLength == 8, "Improper length for new transaction id!"); - if (env.newLength > 0) + if (env._newLength > 0) { - if (!stateObj.TryReadInt64(out env.newLongValue)) + if (!stateObj.TryReadInt64(out env._newLongValue)) { return false; } - Debug.Assert(env.newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. + Debug.Assert(env._newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { - env.newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. + env._newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } if (!stateObj.TryReadByte(out byteLength)) { return false; } - env.oldLength = byteLength; - Debug.Assert(env.oldLength == 0 || env.oldLength == 8, "Improper length for old transaction id!"); + env._oldLength = byteLength; + Debug.Assert(env._oldLength == 0 || env._oldLength == 8, "Improper length for old transaction id!"); - if (env.oldLength > 0) + if (env._oldLength > 0) { - if (!stateObj.TryReadInt64(out env.oldLongValue)) + if (!stateObj.TryReadInt64(out env._oldLongValue)) { return false; } - Debug.Assert(env.oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. + Debug.Assert(env._oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { - env.oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. + env._oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } // env.length includes 1 byte type token - env.length = 3 + env.newLength + env.oldLength; + env._length = 3 + env._newLength + env._oldLength; break; case TdsEnums.ENV_LOGSHIPNODE: @@ -3183,12 +3185,12 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, case TdsEnums.ENV_PROMOTETRANSACTION: Debug.Assert(_is2005, "Received new ENVCHANGE tokens on pre 9.0 server!"); - if (!stateObj.TryReadInt32(out env.newLength)) + if (!stateObj.TryReadInt32(out env._newLength)) { // new value has 4 byte length return false; } - env.newBinValue = new byte[env.newLength]; - if (!stateObj.TryReadByteArray(env.newBinValue, env.newLength)) + env._newBinValue = new byte[env._newLength]; + if (!stateObj.TryReadByteArray(env._newBinValue, env._newLength)) { // read new value with 4 byte length return false; } @@ -3197,11 +3199,11 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.oldLength = byteLength; - Debug.Assert(0 == env.oldLength, "old length should be zero"); + env._oldLength = byteLength; + Debug.Assert(0 == env._oldLength, "old length should be zero"); // env.length includes 1 byte for type token - env.length = 5 + env.newLength; + env._length = 5 + env._newLength; break; case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: @@ -3228,7 +3230,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.newLength = newLength; + env._newLength = newLength; byte protocol; if (!stateObj.TryReadByte(out protocol)) { @@ -3249,7 +3251,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.newRoutingInfo = new RoutingInfo(protocol, port, serverName); + env._newRoutingInfo = new RoutingInfo(protocol, port, serverName); UInt16 oldLength; if (!stateObj.TryReadUInt16(out oldLength)) { @@ -3259,17 +3261,17 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, { return false; } - env.length = env.newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] + env._length = env._newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] break; default: - Debug.Fail("Unknown environment change token: " + env.type); + Debug.Fail("Unknown environment change token: " + env._type); break; } - processedLength += env.length; + processedLength += env._length; } - sqlEnvChange = envarray; + sqlEnvChange = head; return true; } @@ -3281,9 +3283,10 @@ private bool TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject state { return false; } - env.newLength = byteLength; - env.newBinValue = new byte[env.newLength]; - if (!stateObj.TryReadByteArray(env.newBinValue, env.newLength)) + env._newLength = byteLength; + env._newBinValue = ArrayPool.Shared.Rent(env._newLength); + env._newBinRented = true; + if (!stateObj.TryReadByteArray(env._newBinValue, env._newLength)) { return false; } @@ -3291,15 +3294,16 @@ private bool TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject state { return false; } - env.oldLength = byteLength; - env.oldBinValue = new byte[env.oldLength]; - if (!stateObj.TryReadByteArray(env.oldBinValue, env.oldLength)) + env._oldLength = byteLength; + env._oldBinValue = ArrayPool.Shared.Rent(env._oldLength); + env._oldBinRented = true; + if (!stateObj.TryReadByteArray(env._oldBinValue, env._oldLength)) { return false; } // env.length includes 1 byte type token - env.length = 3 + env.newLength + env.oldLength; + env._length = 3 + env._newLength + env._oldLength; return true; } @@ -3325,13 +3329,13 @@ private bool TryReadTwoStringFields(SqlEnvChange env, TdsParserStateObject state return false; } - env.newLength = newLength; - env.newValue = newValue; - env.oldLength = oldLength; - env.oldValue = oldValue; + env._newLength = newLength; + env._newValue = newValue; + env._oldLength = oldLength; + env._oldValue = oldValue; // env.length includes 1 byte type token - env.length = 3 + env.newLength * 2 + env.oldLength * 2; + env._length = 3 + env._newLength * 2 + env._oldLength * 2; return true; } @@ -6835,6 +6839,7 @@ private bool TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte { return false; } + ReadOnlySpan dateTimeData = datetimeBuffer.Slice(0, length); switch (tdsType) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 3c289ba0e3..21004f4be2 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -312,23 +312,6 @@ internal RoutingInfo(byte protocol, UInt16 port, string servername) } } - sealed internal class SqlEnvChange - { - internal byte type; - internal byte oldLength; - internal int newLength; // 7206 TDS changes makes this length an int - internal int length; - internal string newValue; - internal string oldValue; - internal byte[] newBinValue; - internal byte[] oldBinValue; - internal long newLongValue; - internal long oldLongValue; - internal SqlCollation newCollation; - internal SqlCollation oldCollation; - internal RoutingInfo newRoutingInfo; - } - sealed internal class SqlLogin { internal SqlAuthenticationMethod authentication = SqlAuthenticationMethod.NotSpecified; // Authentication type diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 1ede8f6baf..e6b42319a2 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -1391,7 +1391,7 @@ internal bool TryPeekByte(out byte value) // bytes from the in buffer. public bool TryReadByteArray(Span buff, int len) { - return TryReadByteArray(buff, len, out int _); + return TryReadByteArray(buff, len, out _); } // NOTE: This method must be retriable WITHOUT replaying a snapshot @@ -2018,7 +2018,9 @@ internal int ReadPlpBytesChunk(byte[] buff, int offset, int len) bool result = TryReadByteArray(buff.AsSpan(start: offset), bytesToRead, out value); _longlenleft -= (ulong)bytesToRead; if (!result) - { throw SQL.SynchronousCallMayNotPend(); } + { + throw SQL.SynchronousCallMayNotPend(); + } return value; } @@ -2139,7 +2141,7 @@ internal bool TrySkipLongBytes(long num) while (num > 0) { cbSkip = (int)Math.Min((long)Int32.MaxValue, num); - if (!TryReadByteArray(null, cbSkip)) + if (!TryReadByteArray(Span.Empty, cbSkip)) { return false; } @@ -2153,7 +2155,7 @@ internal bool TrySkipLongBytes(long num) internal bool TrySkipBytes(int num) { Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - return TryReadByteArray(null, num); + return TryReadByteArray(Span.Empty, num); } ///////////////////////////////////////// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnvChange.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnvChange.cs new file mode 100644 index 0000000000..f441892f29 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnvChange.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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 System.Buffers; + +namespace Microsoft.Data.SqlClient +{ + internal sealed class SqlEnvChange + { + internal byte _type; + internal byte _oldLength; + internal int _newLength; // 7206 TDS changes makes this length an int + internal int _length; + internal string _newValue; + internal string _oldValue; + /// + /// contains binary data, before using this field check newBinRented to see if you can take the field array or whether you should allocate and copy + /// + internal byte[] _newBinValue; + /// + /// contains binary data, before using this field check newBinRented to see if you can take the field array or whether you should allocate and copy + /// + internal byte[] _oldBinValue; + internal long _newLongValue; + internal long _oldLongValue; + internal SqlCollation _newCollation; + internal SqlCollation _oldCollation; + internal RoutingInfo _newRoutingInfo; + internal bool _newBinRented; + internal bool _oldBinRented; + + internal SqlEnvChange _next; + + internal void Clear() + { + _type = 0; + _oldLength = 0; + _newLength = 0; + _length = 0; + _newValue = null; + _oldValue = null; + if (_newBinValue != null) + { + Array.Clear(_newBinValue, 0, _newBinValue.Length); + if (_newBinRented) + { + ArrayPool.Shared.Return(_newBinValue); + } + _newBinValue = null; + } + if (_oldBinValue != null) + { + Array.Clear(_oldBinValue, 0, _oldBinValue.Length); + if (_oldBinRented) + { + ArrayPool.Shared.Return(_oldBinValue); + } + _oldBinValue = null; + } + _newBinRented = false; + _oldBinRented = false; + _newLongValue = 0; + _oldLongValue = 0; + _newCollation = null; + _oldCollation = null; + _newRoutingInfo = null; + _next = null; + } + } +} From d0c5c26accf3266efaf29434129dfc90e2d1ed47 Mon Sep 17 00:00:00 2001 From: Parminder Kaur <88398605+Kaur-Parminder@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:44:42 -0700 Subject: [PATCH 12/35] Moved to Shared - SqlParameter (#1354) --- .../src/Microsoft.Data.SqlClient.csproj | 6 +- .../netcore/src/Resources/Strings.Designer.cs | 18 + .../netcore/src/Resources/Strings.resx | 6 + .../netcore/src/Resources/StringsHelper.cs | 1 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 4 +- .../Microsoft/Data/SqlClient/SqlCommand.cs | 7 +- .../Microsoft/Data/SqlClient/SqlParameter.cs | 2396 ----------------- .../Microsoft/Data/SqlClient/SqlParameter.cs | 176 +- 8 files changed, 140 insertions(+), 2474 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs rename src/Microsoft.Data.SqlClient/{netcore => }/src/Microsoft/Data/SqlClient/SqlParameter.cs (90%) 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 0763809c20..cffb127ec7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -430,6 +430,9 @@ Microsoft\Data\SqlClient\SqlObjectPool.cs + + Microsoft\Data\SqlClient\SqlParameter.cs + Microsoft\Data\SqlClient\SqlParameterCollection.cs @@ -565,7 +568,7 @@ - + @@ -642,7 +645,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs index f33751fb08..0629ceb1ac 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -942,6 +942,24 @@ internal static string Data_InvalidOffsetLength { } } + /// + /// Looks up a localized string similar to Update. + /// + internal static string DataCategory_Update { + get { + return ResourceManager.GetString("DataCategory_Update", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to XML. + /// + internal static string DataCategory_Xml { + get { + return ResourceManager.GetString("DataCategory_Xml", resourceCulture); + } + } + /// /// Looks up a localized string similar to Internal error occurred when retrying the download of the HGS root certificate after the initial request failed. Contact Customer Support Services.. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx index bfe5559389..6be833f7ee 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx @@ -1932,4 +1932,10 @@ Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. + + Update + + + XML + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs index 4b61d284ff..067426eee3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs @@ -76,6 +76,7 @@ internal class ResourceNames { internal const string DataCategory_Data = @"Data"; internal const string DataCategory_Update = @"Update"; + internal const string DataCategory_Xml = @"XML"; internal const string DbCommand_CommandTimeout = @"Time to wait for command to execute."; internal const string DbConnection_State = @"The ConnectionState indicating whether the connection is open or closed."; internal const string DataCategory_Fill = @"Fill"; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 3e195fdcac..ab43046cb3 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -494,6 +494,9 @@ Microsoft\Data\SqlClient\SqlParameterCollection.cs + + Microsoft\Data\SqlClient\SqlParameter.cs + Microsoft\Data\SqlClient\SqlQueryMetadataCache.cs @@ -627,7 +630,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index 8b8d298bb6..7294c745d1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -6528,7 +6528,6 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP int paramCount = GetParameterCount(parameters); int j = startCount; TdsParser parser = _activeConnection.Parser; - bool is2005OrNewer = parser.Is2005OrNewer; for (ii = 0; ii < paramCount; ii++) { @@ -6537,7 +6536,7 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP // func will change type to that with a 4 byte length if the type has a two // byte length and a parameter length > than that expressable in 2 bytes - if ((!parameter.ValidateTypeLengths(is2005OrNewer).IsPlp) && (parameter.Direction != ParameterDirection.Output)) + if ((!parameter.ValidateTypeLengths().IsPlp) && (parameter.Direction != ParameterDirection.Output)) { parameter.FixStreamDataForNonPLP(); } @@ -6925,8 +6924,6 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete StringBuilder paramList = new StringBuilder(); bool fAddSeparator = false; - bool is2005OrNewer = parser.Is2005OrNewer; - int count = 0; count = parameters.Count; @@ -6976,7 +6973,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete { // func will change type to that with a 4 byte length if the type has a two // byte length and a parameter length > than that expressable in 2 bytes - mt = sqlParam.ValidateTypeLengths(is2005OrNewer); + mt = sqlParam.ValidateTypeLengths(); if ((!mt.IsPlp) && (sqlParam.Direction != ParameterDirection.Output)) { sqlParam.FixStreamDataForNonPLP(); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs deleted file mode 100644 index 503273c317..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ /dev/null @@ -1,2396 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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 System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.Design.Serialization; -using System.Data; -using System.Data.Common; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Xml; -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient -{ - internal abstract class DataFeed - { - } - - internal class StreamDataFeed : DataFeed - { - internal Stream _source; - - internal StreamDataFeed(Stream source) - { - _source = source; - } - } - - internal class TextDataFeed : DataFeed - { - internal TextReader _source; - - internal TextDataFeed(TextReader source) - { - _source = source; - } - } - - internal class XmlDataFeed : DataFeed - { - internal XmlReader _source; - - internal XmlDataFeed(XmlReader source) - { - _source = source; - } - } - - /// - [TypeConverter(typeof(SqlParameter.SqlParameterConverter))] - public sealed partial class SqlParameter : DbParameter, IDbDataParameter, ICloneable - { - internal sealed class SqlParameterConverter : ExpandableObjectConverter - { - - // converter classes should have public ctor - public SqlParameterConverter() - { - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (typeof(InstanceDescriptor) == destinationType) - { - return true; - } - return base.CanConvertTo(context, destinationType); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == null) - { - throw ADP.ArgumentNull(nameof(destinationType)); - } - if ((typeof(InstanceDescriptor) == destinationType) && (value is SqlParameter)) - { - return ConvertToInstanceDescriptor(value as SqlParameter); - } - return base.ConvertTo(context, culture, value, destinationType); - } - - private InstanceDescriptor ConvertToInstanceDescriptor(SqlParameter p) - { - int flags = 0; // if part of the collection - the parametername can't be empty - - if (p.ShouldSerializeSqlDbType()) - { - flags |= 1; - } - if (p.ShouldSerializeSize()) - { - flags |= 2; - } - if (!string.IsNullOrEmpty(p.SourceColumn)) - { - flags |= 4; - } - if (null != p.Value) - { - flags |= 8; - } - if ( - (ParameterDirection.Input != p.Direction) || - p.IsNullable || - p.ShouldSerializePrecision() || - p.ShouldSerializeScale() || - (DataRowVersion.Current != p.SourceVersion) - ) - { - flags |= 16; // v1.0 everything - } - - if ( - p.SourceColumnNullMapping || - !string.IsNullOrEmpty(p.XmlSchemaCollectionDatabase) || - !string.IsNullOrEmpty(p.XmlSchemaCollectionOwningSchema) || - !string.IsNullOrEmpty(p.XmlSchemaCollectionName) - ) - { - flags |= 32; // v2.0 everything - } - - Type[] ctorParams; - object[] ctorValues; - switch (flags) - { - case 0: // ParameterName - case 1: // SqlDbType - ctorParams = new Type[] { typeof(string), typeof(SqlDbType) }; - ctorValues = new object[] { p.ParameterName, p.SqlDbType }; - break; - case 2: // Size - case 3: // Size, SqlDbType - ctorParams = new Type[] { typeof(string), typeof(SqlDbType), typeof(int) }; - ctorValues = new object[] { p.ParameterName, p.SqlDbType, p.Size }; - break; - case 4: // SourceColumn - case 5: // SourceColumn, SqlDbType - case 6: // SourceColumn, Size - case 7: // SourceColumn, Size, SqlDbType - ctorParams = new Type[] { typeof(string), typeof(SqlDbType), typeof(int), typeof(string) }; - ctorValues = new object[] { p.ParameterName, p.SqlDbType, p.Size, p.SourceColumn }; - break; - case 8: // Value - ctorParams = new Type[] { typeof(string), typeof(object) }; - ctorValues = new object[] { p.ParameterName, p.Value }; - break; - default: - if (0 == (32 & flags)) - { // v1.0 everything - ctorParams = new Type[] { - typeof(string), typeof(SqlDbType), typeof(int), typeof(ParameterDirection), - typeof(bool), typeof(byte), typeof(byte), - typeof(string), typeof(DataRowVersion), - typeof(object) }; - ctorValues = new object[] { - p.ParameterName, p.SqlDbType, p.Size, p.Direction, - p.IsNullable, p.PrecisionInternal, p.ScaleInternal, - p.SourceColumn, p.SourceVersion, - p.Value }; - } - else - { // v2.0 everything - round trip all browsable properties + precision/scale - ctorParams = new Type[] { - typeof(string), typeof(SqlDbType), typeof(int), typeof(ParameterDirection), - typeof(byte), typeof(byte), - typeof(string), typeof(DataRowVersion), typeof(bool), - typeof(object), - typeof(string), typeof(string), - typeof(string) }; - ctorValues = new object[] { - p.ParameterName, p.SqlDbType, p.Size, p.Direction, - p.PrecisionInternal, p.ScaleInternal, - p.SourceColumn, p.SourceVersion, p.SourceColumnNullMapping, - p.Value, - p.XmlSchemaCollectionDatabase, p.XmlSchemaCollectionOwningSchema, - p.XmlSchemaCollectionName}; - } - break; - } - ConstructorInfo ctor = typeof(SqlParameter).GetConstructor(ctorParams); - return new InstanceDescriptor(ctor, ctorValues); - } - } - - [Flags] - private enum SqlParameterFlags : ushort - { - None = 0, - IsNull = 1, - IsNullable = 2, - IsSqlParameterSqlType = 4, - SourceColumnNullMapping = 8, - CoercedValueIsSqlType = 16, - CoercedValueIsDataFeed = 32, - HasReceivedMetadata = 64, - ForceColumnEncryption = 128, - IsDerivedParameterTypeName = 256, - HasScale = 512, - } - - private MetaType _metaType; - private SqlCollation _collation; - private SqlMetaDataXmlSchemaCollection _xmlSchemaCollection; - private string _udtTypeName; - private string _typeName; - private Exception _udtLoadError; - private string _parameterName; - private byte _precision; - private byte _scale; - private MetaType _internalMetaType; - private SqlBuffer _sqlBufferReturnValue; - private INullable _valueAsINullable; - private int _actualSize; - private object _value; - private object _coercedValue; - private object _parent; - private ParameterDirection _direction; - private int _size; - private int _offset; - private string _sourceColumn; - private DataRowVersion _sourceVersion; - private SqlParameterFlags _flags; - - /// - public SqlParameter() : base() - { - _flags = SqlParameterFlags.IsNull; - _actualSize = -1; - _direction = ParameterDirection.Input; - } - - /// - public SqlParameter(string parameterName, SqlDbType dbType) : this() - { - ParameterName = parameterName; - SqlDbType = dbType; - } - - /// - public SqlParameter(string parameterName, object value) : this() - { - Debug.Assert(!(value is SqlDbType), "use SqlParameter(string, SqlDbType)"); - - ParameterName = parameterName; - Value = value; - } - - /// - public SqlParameter(string parameterName, SqlDbType dbType, int size) : this() - { - ParameterName = parameterName; - SqlDbType = dbType; - Size = size; - } - - /// - public SqlParameter(string parameterName, SqlDbType dbType, int size, string sourceColumn) : this() - { - ParameterName = parameterName; - SqlDbType = dbType; - Size = size; - SourceColumn = sourceColumn; - } - - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public SqlParameter( - string parameterName, - SqlDbType dbType, - int size, - ParameterDirection direction, - bool isNullable, - byte precision, - byte scale, - string sourceColumn, - DataRowVersion sourceVersion, - object value - ) - : this(parameterName, dbType, size, sourceColumn) - { - Direction = direction; - IsNullable = isNullable; - PrecisionInternal = precision; - ScaleInternal = scale; - SourceVersion = sourceVersion; - Value = value; - } - - /// - public SqlParameter( - string parameterName, - SqlDbType dbType, - int size, - ParameterDirection direction, - byte precision, - byte scale, - string sourceColumn, - DataRowVersion sourceVersion, - bool sourceColumnNullMapping, - object value, - string xmlSchemaCollectionDatabase, - string xmlSchemaCollectionOwningSchema, - string xmlSchemaCollectionName - ) - : this() - { - ParameterName = parameterName; - SqlDbType = dbType; - Size = size; - Direction = direction; - PrecisionInternal = precision; - ScaleInternal = scale; - SourceColumn = sourceColumn; - SourceVersion = sourceVersion; - SourceColumnNullMapping = sourceColumnNullMapping; - Value = value; - if (!string.IsNullOrEmpty(xmlSchemaCollectionDatabase) || !string.IsNullOrEmpty(xmlSchemaCollectionOwningSchema) || !string.IsNullOrEmpty(xmlSchemaCollectionName)) - { - EnsureXmlSchemaCollection(); - _xmlSchemaCollection.Database = xmlSchemaCollectionDatabase; - _xmlSchemaCollection.OwningSchema = xmlSchemaCollectionOwningSchema; - _xmlSchemaCollection.Name = xmlSchemaCollectionName; - } - } - - private SqlParameter(SqlParameter source) : this() - { - ADP.CheckArgumentNull(source, nameof(source)); - source.CloneHelper(this); - if (_value is ICloneable cloneable) - { - _value = cloneable.Clone(); - } - } - - /// - /// Get or set the encryption related metadata of this SqlParameter. - /// Should be set to a non-null value only once. - /// - internal SqlCipherMetadata CipherMetadata { get; set; } - - /// - /// Indicates if the parameter encryption metadata received by sp_describe_parameter_encryption. - /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate - /// that no encryption is needed). - /// - internal bool HasReceivedMetadata - { - get => HasFlag(SqlParameterFlags.HasReceivedMetadata); - set => SetFlag(SqlParameterFlags.HasReceivedMetadata, value); - } - - /// - /// Returns the normalization rule version number as a byte - /// - internal byte NormalizationRuleVersion => CipherMetadata?.NormalizationRuleVersion ?? 0x00; - - /// - [Browsable(false)] - public SqlCompareOptions CompareInfo - { - // Bits 21 through 25 represent the CompareInfo - get - { - SqlCollation collation = _collation; - if (null != collation) - { - return collation.SqlCompareOptions; - } - return SqlCompareOptions.None; - } - set - { - SqlCollation collation = _collation; - - // Copied from SQLString.x_iValidSqlCompareOptionMask - SqlCompareOptions validSqlCompareOptionMask = - SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreWidth | - SqlCompareOptions.IgnoreNonSpace | SqlCompareOptions.IgnoreKanaType | - SqlCompareOptions.BinarySort | SqlCompareOptions.BinarySort2; - - if ((value & validSqlCompareOptionMask) != value) - { - throw ADP.ArgumentOutOfRange(nameof(CompareInfo)); - } - - if (collation == null || collation.SqlCompareOptions != value) - { - _collation = SqlCollation.FromLCIDAndSort(collation?.LCID ?? 0, value); - } - } - } - - /// - [ResCategory("XML")] - public string XmlSchemaCollectionDatabase - { - get => _xmlSchemaCollection?.Database ?? string.Empty; - set => EnsureXmlSchemaCollection().Database = value; - } - - /// - [ResCategory("XML")] - public string XmlSchemaCollectionOwningSchema - { - get => _xmlSchemaCollection?.OwningSchema ?? string.Empty; - set => EnsureXmlSchemaCollection().OwningSchema = value; - } - - /// - [ResCategory("XML")] - public string XmlSchemaCollectionName - { - get => _xmlSchemaCollection?.Name ?? string.Empty; - set => EnsureXmlSchemaCollection().Name = value; - } - - /// - [ - DefaultValue(false), - ResCategory("Data") - ] - public bool ForceColumnEncryption - { - get => HasFlag(SqlParameterFlags.ForceColumnEncryption); - set => SetFlag(SqlParameterFlags.ForceColumnEncryption, value); - } - - /// - public override DbType DbType - { - get => GetMetaTypeOnly().DbType; - set - { - MetaType metatype = _metaType; - if ((null == metatype) || (metatype.DbType != value)) - { - PropertyTypeChanging(); - _metaType = MetaType.GetMetaTypeFromDbType(value); - } - } - } - - /// - public override void ResetDbType() => ResetSqlDbType(); - - /// - [ResCategory("Data")] - public override string ParameterName - { - get => _parameterName ?? string.Empty; - set - { - if ( - string.IsNullOrEmpty(value) || - (value.Length < TdsEnums.MAX_PARAMETER_NAME_LENGTH) || - ( - (value[0] == '@') && - (value.Length <= TdsEnums.MAX_PARAMETER_NAME_LENGTH) - ) - ) - { - if (_parameterName != value) - { - PropertyChanging(); - _parameterName = value; - } - } - else - { - throw SQL.InvalidParameterNameLength(value); - } - } - } - - /// - [Browsable(false)] - public int LocaleId - { - // Lowest 20 bits represent LocaleId - get - { - SqlCollation collation = _collation; - if (null != collation) - { - return collation.LCID; - } - return 0; - } - set - { - SqlCollation collation = _collation; - - if (value != (SqlCollation.MaskLcid & value)) - { - throw ADP.ArgumentOutOfRange(nameof(LocaleId)); - } - - if (collation == null || collation.LCID != value) - { - _collation = SqlCollation.FromLCIDAndSort(value, collation?.SqlCompareOptions ?? SqlCompareOptions.None); - } - } - } - - /// - [ - DefaultValue((byte)0), - ResCategory(StringsHelper.ResourceNames.DataCategory_Data) - ] - public new byte Precision - { - get => PrecisionInternal; - set => PrecisionInternal = value; - } - - private bool ShouldSerializePrecision() => _precision != 0; - - /// - [ - DefaultValue((byte)0), - ResCategory(StringsHelper.ResourceNames.DataCategory_Data) - ] - public new byte Scale - { - get => ScaleInternal; - set => ScaleInternal = value; - } - - internal byte ScaleInternal - { - get - { - byte scale = _scale; - SqlDbType dbtype = GetMetaSqlDbTypeOnly(); - if ((scale == 0) && (dbtype == SqlDbType.Decimal)) - { - scale = ValueScale(SqlValue); - } - return scale; - } - set - { - if (_scale != value || !HasFlag(SqlParameterFlags.HasScale)) - { - PropertyChanging(); - _scale = value; - SetFlag(SqlParameterFlags.HasScale, true); - _actualSize = -1; // Invalidate actual size such that it is re-calculated - } - } - } - - private bool ShouldSerializeScale() => _scale != 0; // V1.0 compat, ignore _hasScale - - /// - [ - RefreshProperties(RefreshProperties.All), - ResCategory("Data"), - DbProviderSpecificTypeProperty(true) - ] - public SqlDbType SqlDbType - { - get => GetMetaTypeOnly().SqlDbType; - set - { - MetaType metatype = _metaType; - // HACK!!! - // We didn't want to expose SmallVarBinary on SqlDbType so we - // stuck it at the end of SqlDbType in v1.0, except that now - // we have new data types after that and it's smack dab in the - // middle of the valid range. To prevent folks from setting - // this invalid value we have to have this code here until we - // can take the time to fix it later. - if (TdsEnums.SmallVarBinary == value) - { - throw SQL.InvalidSqlDbType(value); - } - if ((null == metatype) || (metatype.SqlDbType != value)) - { - PropertyTypeChanging(); - _metaType = MetaType.GetMetaTypeFromSqlDbType(value, value == SqlDbType.Structured); - } - } - } - - private bool ShouldSerializeSqlDbType() => _metaType != null; - - /// - public void ResetSqlDbType() - { - if (_metaType != null) - { - PropertyTypeChanging(); - _metaType = null; - } - } - - /// - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ] - public object SqlValue - { - get - { - if (_udtLoadError != null) - { - throw _udtLoadError; - } - - if (_value != null) - { - if (_value == DBNull.Value) - { - return MetaType.GetNullSqlValue(GetMetaTypeOnly().SqlType); - } - if (_value is INullable) - { - return _value; - } - - // For Date and DateTime2, return the CLR object directly without converting it to a SqlValue - // GetMetaTypeOnly() will convert _value to a string in the case of char or char[], so only check - // the SqlDbType for DateTime. This is the only case when we might return the CLR value directly. - if (_value is DateTime) - { - SqlDbType sqlDbType = GetMetaTypeOnly().SqlDbType; - if (sqlDbType == SqlDbType.Date || sqlDbType == SqlDbType.DateTime2) - { - return _value; - } - } - - return (MetaType.GetSqlValueFromComVariant(_value)); - } - else if (_sqlBufferReturnValue != null) - { - return _sqlBufferReturnValue.SqlValue; - } - return null; - } - set - { - Value = value; - } - } - - /// - [ - Browsable(false), - EditorBrowsable(EditorBrowsableState.Advanced) - ] - public string UdtTypeName - { - get => _udtTypeName ?? string.Empty; - set => _udtTypeName = value; - } - - /// - [ - Browsable(false), - EditorBrowsable(EditorBrowsableState.Advanced) - ] - public string TypeName - { - get => _typeName ?? string.Empty; - set - { - _typeName = value; - IsDerivedParameterTypeName = false; - } - } - - /// - [ - RefreshProperties(RefreshProperties.All), - ResCategory("Data"), - TypeConverter(typeof(StringConverter)), - ] - public override object Value - { - get - { - if (_udtLoadError != null) - { - throw _udtLoadError; - } - - if (_value != null) - { - return _value; - } - else if (_sqlBufferReturnValue != null) - { - if (ParameterIsSqlType) - { - return _sqlBufferReturnValue.SqlValue; - } - return _sqlBufferReturnValue.Value; - } - return null; - } - set - { - _value = value; - _sqlBufferReturnValue = null; - _coercedValue = null; - _valueAsINullable = _value as INullable; - SetFlag(SqlParameterFlags.IsSqlParameterSqlType, _valueAsINullable != null); - SetFlag(SqlParameterFlags.IsNull, (null == _value) || (_value == DBNull.Value) || (HasFlag(SqlParameterFlags.IsSqlParameterSqlType) && _valueAsINullable.IsNull)); - _udtLoadError = null; - _actualSize = -1; - } - } - - /// - [ - RefreshProperties(RefreshProperties.All), - ResCategory("Data"), - ] - public override ParameterDirection Direction - { - get => _direction; - set - { - if (_direction != value) - { - switch (value) - { - case ParameterDirection.Input: - case ParameterDirection.Output: - case ParameterDirection.InputOutput: - case ParameterDirection.ReturnValue: - PropertyChanging(); - _direction = value; - break; - default: - throw ADP.InvalidParameterDirection(value); - } - } - } - } - - /// - public override bool IsNullable - { - get => HasFlag(SqlParameterFlags.IsNullable); - set => SetFlag(SqlParameterFlags.IsNullable, value); - } - - /// - public int Offset - { - get => _offset; - set - { - if (value < 0) - { - throw ADP.InvalidOffsetValue(value); - } - _offset = value; - } - } - - /// - [ResCategory("Data")] - public override int Size - { - get - { - int size = _size; - if (size == 0) - { - size = ValueSize(Value); - } - return size; - } - set - { - if (value != _size) - { - if (value < -1) - { - throw ADP.InvalidSizeValue(value); - } - PropertyChanging(); - _size = value; - } - } - } - - private void ResetSize() - { - if (_size != 0) - { - PropertyChanging(); - _size = 0; - } - } - - private bool ShouldSerializeSize() => _size != 0; - - /// - [ResCategory("Update")] - public override string SourceColumn - { - get => _sourceColumn ?? string.Empty; - set => _sourceColumn = value; - } - - /// - public override bool SourceColumnNullMapping - { - get => HasFlag(SqlParameterFlags.SourceColumnNullMapping); - set => SetFlag(SqlParameterFlags.SourceColumnNullMapping, value); - } - - /// - public override string ToString() => ParameterName; - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_Update)] - public override DataRowVersion SourceVersion - { - get - { - DataRowVersion sourceVersion = _sourceVersion; - return (sourceVersion != 0) ? sourceVersion : DataRowVersion.Current; - } - set - { - switch (value) - { - case DataRowVersion.Original: - case DataRowVersion.Current: - case DataRowVersion.Proposed: - case DataRowVersion.Default: - _sourceVersion = value; - break; - default: - throw ADP.InvalidDataRowVersion(value); - } - } - } - - - /// - object ICloneable.Clone() => new SqlParameter(this); - - - private object CoercedValue - { - get => _coercedValue; - set => _coercedValue = value; - } - - internal bool CoercedValueIsDataFeed - { - get - { - if (null == _coercedValue) - { - GetCoercedValue(); - } - AssertCachedPropertiesAreValid(); - return HasFlag(SqlParameterFlags.CoercedValueIsDataFeed); - } - } - - internal bool CoercedValueIsSqlType - { - get - { - if (_coercedValue == null) - { - GetCoercedValue(); - } - AssertCachedPropertiesAreValid(); - return HasFlag(SqlParameterFlags.CoercedValueIsSqlType); - } - } - - // - // currently the user can't set this value. it gets set by the returnvalue from tds - // - internal SqlCollation Collation - { - get => _collation; - set => _collation = value; - } - - private bool HasFlag(SqlParameterFlags flag) - { - return (_flags & flag) != 0; - } - - internal bool IsNull - { - get - { - // NOTE: Udts can change their value any time - if (_internalMetaType.SqlDbType == SqlDbType.Udt) - { - SetFlag(SqlParameterFlags.IsNull, (_value == null) || (_value == DBNull.Value) || (HasFlag(SqlParameterFlags.IsSqlParameterSqlType) && _valueAsINullable.IsNull)); - } - return HasFlag(SqlParameterFlags.IsNull); - } - } - - internal MetaType InternalMetaType - { - get - { - Debug.Assert(null != _internalMetaType, "null InternalMetaType"); - return _internalMetaType; - } - set => _internalMetaType = value; - } - - internal byte PrecisionInternal - { - get - { - byte precision = _precision; - SqlDbType dbtype = GetMetaSqlDbTypeOnly(); - if ((0 == precision) && (SqlDbType.Decimal == dbtype)) - { - precision = ValuePrecision(SqlValue); - } - return precision; - } - set - { - SqlDbType sqlDbType = SqlDbType; - if (sqlDbType == SqlDbType.Decimal && value > TdsEnums.MAX_NUMERIC_PRECISION) - { - throw SQL.PrecisionValueOutOfRange(value); - } - if (_precision != value) - { - PropertyChanging(); - _precision = value; - } - } - } - - internal bool ParameterIsSqlType - { - get => HasFlag(SqlParameterFlags.IsSqlParameterSqlType); - set => SetFlag(SqlParameterFlags.IsSqlParameterSqlType, value); - } - - internal string ParameterNameFixed - { - get - { - string parameterName = ParameterName; - if ((parameterName.Length > 0) && (parameterName[0] != '@')) - { - parameterName = "@" + parameterName; - } - Debug.Assert(parameterName.Length <= TdsEnums.MAX_PARAMETER_NAME_LENGTH, "parameter name too long"); - return parameterName; - } - } - - internal bool SizeInferred => 0 == _size; - - internal INullable ValueAsINullable => _valueAsINullable; - - internal bool IsDerivedParameterTypeName - { - get => HasFlag(SqlParameterFlags.IsDerivedParameterTypeName); - set => SetFlag(SqlParameterFlags.IsDerivedParameterTypeName, value); - } - - private void CloneHelper(SqlParameter destination) - { - // NOTE: _parent is not cloned - destination._value = _value; - destination._direction = _direction; - destination._size = _size; - destination._offset = _offset; - destination._sourceColumn = _sourceColumn; - destination._sourceVersion = _sourceVersion; - destination._flags = _flags & ( - SqlParameterFlags.SourceColumnNullMapping | - SqlParameterFlags.IsNull | - SqlParameterFlags.IsNullable | - SqlParameterFlags.IsSqlParameterSqlType | - SqlParameterFlags.CoercedValueIsDataFeed | - SqlParameterFlags.CoercedValueIsSqlType | - SqlParameterFlags.ForceColumnEncryption | - SqlParameterFlags.IsDerivedParameterTypeName - // HasScale and HasReceivedMetadata deliberately omitted - ); - destination._metaType = _metaType; - destination._collation = _collation; - if (_xmlSchemaCollection != null) - { - destination.EnsureXmlSchemaCollection().CopyFrom(_xmlSchemaCollection); - } - destination._udtTypeName = _udtTypeName; - destination._typeName = _typeName; - destination._udtLoadError = _udtLoadError; - destination._parameterName = _parameterName; - destination._precision = _precision; - destination._scale = _scale; - destination._sqlBufferReturnValue = _sqlBufferReturnValue; - destination._internalMetaType = _internalMetaType; - destination.CoercedValue = CoercedValue; // copy cached value reference because of XmlReader problem - destination._valueAsINullable = _valueAsINullable; - destination._actualSize = _actualSize; - } - - internal void CopyTo(SqlParameter destination) - { - ADP.CheckArgumentNull(destination, nameof(destination)); - CloneHelper(destination); - } - - internal object CompareExchangeParent(object value, object comparand) - { - // the interlock guarantees same parameter won't belong to multiple collections - // at the same time, but to actually occur the user must really try - // since we never declared thread safety, we don't care at this time - //return System.Threading.Interlocked.CompareExchange(ref _parent, value, comparand); - object parent = _parent; - if (comparand == parent) - { - _parent = value; - } - return parent; - } - - private SqlMetaDataXmlSchemaCollection EnsureXmlSchemaCollection() - { - if (_xmlSchemaCollection is null) - { - _xmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); - } - return _xmlSchemaCollection; - } - - internal void FixStreamDataForNonPLP() - { - object value = GetCoercedValue(); - AssertCachedPropertiesAreValid(); - if (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed)) - { - return; - } - - SetFlag(SqlParameterFlags.CoercedValueIsDataFeed, false); - - if (value is TextDataFeed textFeed) - { - if (Size > 0) - { - char[] buffer = new char[Size]; - int nRead = textFeed._source.ReadBlock(buffer, 0, Size); - CoercedValue = new string(buffer, 0, nRead); - } - else - { - CoercedValue = textFeed._source.ReadToEnd(); - } - return; - } - - if (value is StreamDataFeed streamFeed) - { - if (Size > 0) - { - byte[] buffer = new byte[Size]; - int totalRead = 0; - Stream sourceStream = streamFeed._source; - while (totalRead < Size) - { - int nRead = sourceStream.Read(buffer, totalRead, Size - totalRead); - if (nRead == 0) - { - break; - } - totalRead += nRead; - } - if (totalRead < Size) - { - Array.Resize(ref buffer, totalRead); - } - CoercedValue = buffer; - } - else - { - MemoryStream ms = new MemoryStream(); - streamFeed._source.CopyTo(ms); - CoercedValue = ms.ToArray(); - } - return; - } - - if (value is XmlDataFeed xmlFeed) - { - CoercedValue = MetaType.GetStringFromXml(xmlFeed._source); - return; - } - - // We should have returned before reaching here - Debug.Fail("_coercedValueIsDataFeed was true, but the value was not a known DataFeed type"); - } - - private void GetActualFieldsAndProperties(out List fields, out SmiMetaDataPropertyCollection props, out ParameterPeekAheadValue peekAhead) - { - fields = null; - props = null; - peekAhead = null; - - object value = GetCoercedValue(); - if (value is DataTable dt) - { - if (dt.Columns.Count <= 0) - { - throw SQL.NotEnoughColumnsInStructuredType(); - } - fields = new List(dt.Columns.Count); - bool[] keyCols = new bool[dt.Columns.Count]; - bool hasKey = false; - - // set up primary key as unique key list - // do this prior to general metadata loop to favor the primary key - if (null != dt.PrimaryKey && 0 < dt.PrimaryKey.Length) - { - foreach (DataColumn col in dt.PrimaryKey) - { - keyCols[col.Ordinal] = true; - hasKey = true; - } - } - - for (int i = 0; i < dt.Columns.Count; i++) - { - fields.Add(MetaDataUtilsSmi.SmiMetaDataFromDataColumn(dt.Columns[i], dt)); - - // DataColumn uniqueness is only for a single column, so don't add - // more than one. (keyCols.Count first for assumed minimal perf benefit) - if (!hasKey && dt.Columns[i].Unique) - { - keyCols[i] = true; - hasKey = true; - } - } - - // Add unique key property, if any found. - if (hasKey) - { - props = new SmiMetaDataPropertyCollection(); - props[SmiPropertySelector.UniqueKey] = new SmiUniqueKeyProperty(new List(keyCols)); - } - } - else if (value is SqlDataReader sqlReader) - { - fields = new List(sqlReader.GetInternalSmiMetaData()); - if (fields.Count <= 0) - { - throw SQL.NotEnoughColumnsInStructuredType(); - } - - bool[] keyCols = new bool[fields.Count]; - bool hasKey = false; - for (int i = 0; i < fields.Count; i++) - { - if (fields[i] is SmiQueryMetaData qmd && !qmd.IsKey.IsNull && qmd.IsKey.Value) - { - keyCols[i] = true; - hasKey = true; - } - } - - // Add unique key property, if any found. - if (hasKey) - { - props = new SmiMetaDataPropertyCollection(); - props[SmiPropertySelector.UniqueKey] = new SmiUniqueKeyProperty(new List(keyCols)); - } - } - else if (value is IEnumerable enumerable) - { - // must grab the first record of the enumerator to get the metadata - IEnumerator enumerator = enumerable.GetEnumerator(); - SqlDataRecord firstRecord = null; - try - { - // no need for fields if there's no rows or no columns -- we'll be sending a null instance anyway. - if (enumerator.MoveNext()) - { - firstRecord = enumerator.Current; - int fieldCount = firstRecord.FieldCount; - if (0 < fieldCount) - { - // It's valid! Grab those fields. - bool[] keyCols = new bool[fieldCount]; - bool[] defaultFields = new bool[fieldCount]; - bool[] sortOrdinalSpecified = new bool[fieldCount]; - int maxSortOrdinal = -1; // largest sort ordinal seen, used to optimize locating holes in the list - bool hasKey = false; - bool hasDefault = false; - int sortCount = 0; - SmiOrderProperty.SmiColumnOrder[] sort = new SmiOrderProperty.SmiColumnOrder[fieldCount]; - fields = new List(fieldCount); - for (int i = 0; i < fieldCount; i++) - { - SqlMetaData colMeta = firstRecord.GetSqlMetaData(i); - fields.Add(MetaDataUtilsSmi.SqlMetaDataToSmiExtendedMetaData(colMeta)); - if (colMeta.IsUniqueKey) - { - keyCols[i] = true; - hasKey = true; - } - - if (colMeta.UseServerDefault) - { - defaultFields[i] = true; - hasDefault = true; - } - - PropertyInfo serverTypeNameProperty = colMeta.GetType().GetProperty("SortOrder", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - MethodInfo getter = serverTypeNameProperty.GetGetMethod(nonPublic: true); - SortOrder sortOrder = (SortOrder)getter.Invoke(colMeta, null); - - sort[i]._order = sortOrder; - if (SortOrder.Unspecified != sortOrder) - { - // SqlMetaData takes care of checking for negative sort ordinals with specified sort order - - // bail early if there's no way sort order could be monotonically increasing - if (fieldCount <= colMeta.SortOrdinal) - { - throw SQL.SortOrdinalGreaterThanFieldCount(i, colMeta.SortOrdinal); - } - - // Check to make sure we haven't seen this ordinal before - if (sortOrdinalSpecified[colMeta.SortOrdinal]) - { - throw SQL.DuplicateSortOrdinal(colMeta.SortOrdinal); - } - - sort[i]._sortOrdinal = colMeta.SortOrdinal; - sortOrdinalSpecified[colMeta.SortOrdinal] = true; - if (colMeta.SortOrdinal > maxSortOrdinal) - { - maxSortOrdinal = colMeta.SortOrdinal; - } - sortCount++; - } - } - - if (hasKey) - { - props = new SmiMetaDataPropertyCollection(); - props[SmiPropertySelector.UniqueKey] = new SmiUniqueKeyProperty(new List(keyCols)); - } - - if (hasDefault) - { - // May have already created props list in unique key handling - if (null == props) - { - props = new SmiMetaDataPropertyCollection(); - } - - props[SmiPropertySelector.DefaultFields] = new SmiDefaultFieldsProperty(new List(defaultFields)); - } - - if (0 < sortCount) - { - // validate monotonically increasing sort order. - // Since we already checked for duplicates, we just need - // to watch for values outside of the sortCount range. - if (maxSortOrdinal >= sortCount) - { - // there is at least one hole, find the first one - int i; - for (i = 0; i < sortCount; i++) - { - if (!sortOrdinalSpecified[i]) - { - break; - } - } - Debug.Assert(i < sortCount, "SqlParameter.GetActualFieldsAndProperties: SortOrdinal hole-finding algorithm failed!"); - throw SQL.MissingSortOrdinal(i); - } - - // May have already created props list - if (null == props) - { - props = new SmiMetaDataPropertyCollection(); - } - - props[SmiPropertySelector.SortOrder] = new SmiOrderProperty( - new List(sort)); - } - - // pack it up so we don't have to rewind to send the first value - peekAhead = new ParameterPeekAheadValue() - { - Enumerator = enumerator, - FirstRecord = firstRecord - }; - - // now that it's all packaged, make sure we don't dispose it. - enumerator = null; - } - else - { - throw SQL.NotEnoughColumnsInStructuredType(); - } - } - else - { - throw SQL.IEnumerableOfSqlDataRecordHasNoRows(); - } - } - finally - { - if (enumerator != null) - { - enumerator.Dispose(); - } - } - } - else if (value is DbDataReader dbReader) - { - DataTable schema = dbReader.GetSchemaTable(); - if (schema.Rows.Count <= 0) - { - throw SQL.NotEnoughColumnsInStructuredType(); - } - - int fieldCount = schema.Rows.Count; - fields = new List(fieldCount); - bool[] keyCols = new bool[fieldCount]; - bool hasKey = false; - int ordinalForIsKey = schema.Columns[SchemaTableColumn.IsKey].Ordinal; - int ordinalForColumnOrdinal = schema.Columns[SchemaTableColumn.ColumnOrdinal].Ordinal; - // Extract column metadata - for (int rowOrdinal = 0; rowOrdinal < fieldCount; rowOrdinal++) - { - DataRow row = schema.Rows[rowOrdinal]; - SmiExtendedMetaData candidateMd = MetaDataUtilsSmi.SmiMetaDataFromSchemaTableRow(row); - - // Determine destination ordinal. Allow for ordinal not specified by assuming rowOrdinal *is* columnOrdinal - // in that case, but don't worry about mix-and-match of the two techniques - int columnOrdinal = rowOrdinal; - if (!row.IsNull(ordinalForColumnOrdinal)) - { - columnOrdinal = (int)row[ordinalForColumnOrdinal]; - } - - // After this point, things we are creating (keyCols, fields) should be accessed by columnOrdinal - // while the source should just be accessed via "row". - - // Watch for out-of-range ordinals - if (columnOrdinal >= fieldCount || columnOrdinal < 0) - { - throw SQL.InvalidSchemaTableOrdinals(); - } - - // extend empty space if out-of-order ordinal - while (columnOrdinal > fields.Count) - { - fields.Add(null); - } - - // Now add the candidate to the list - if (fields.Count == columnOrdinal) - { - fields.Add(candidateMd); - } - else - { - // Disallow two columns using the same ordinal (even if due to mixing null and non-null columnOrdinals) - if (fields[columnOrdinal] != null) - { - throw SQL.InvalidSchemaTableOrdinals(); - } - - // Don't use insert, since it shifts all later columns down a notch - fields[columnOrdinal] = candidateMd; - } - - // Propagate key information - if (!row.IsNull(ordinalForIsKey) && (bool)row[ordinalForIsKey]) - { - keyCols[columnOrdinal] = true; - hasKey = true; - } - } - -#if DEBUG - // Check for holes - // Above loop logic prevents holes since: - // 1) loop processes fieldcount # of columns - // 2) no ordinals outside continuous range from 0 to fieldcount - 1 are allowed - // 3) no duplicate ordinals are allowed - // But assert no holes to be sure. - foreach (SmiExtendedMetaData md in fields) - { - Debug.Assert(null != md, "Shouldn't be able to have holes, since original loop algorithm prevents such."); - } -#endif - - // Add unique key property, if any defined. - if (hasKey) - { - props = new SmiMetaDataPropertyCollection(); - props[SmiPropertySelector.UniqueKey] = new SmiUniqueKeyProperty(new List(keyCols)); - } - } - } - - internal byte GetActualScale() - { - if (ShouldSerializeScale()) - { - return ScaleInternal; - } - - // issue: how could a user specify 0 as the actual scale? - if (GetMetaTypeOnly().IsVarTime) - { - return TdsEnums.DEFAULT_VARTIME_SCALE; - } - return ValueScale(CoercedValue); - } - - // - // always returns data in bytes - except for non-unicode chars, which will be in number of chars - // - internal int GetActualSize() - { - MetaType mt = InternalMetaType; - SqlDbType actualType = mt.SqlDbType; - // NOTE: Users can change the Udt at any time, so we may need to recalculate - if ((_actualSize == -1) || (actualType == SqlDbType.Udt)) - { - _actualSize = 0; - object val = GetCoercedValue(); - bool isSqlVariant = false; - - if (IsNull && !mt.IsVarTime) - { - return 0; - } - - // if this is a backend SQLVariant type, then infer the TDS type from the SQLVariant type - if (actualType == SqlDbType.Variant) - { - mt = MetaType.GetMetaTypeFromValue(val, streamAllowed: false); - actualType = MetaType.GetSqlDataType(mt.TDSType, 0 /*no user type*/, 0 /*non-nullable type*/).SqlDbType; - isSqlVariant = true; - } - - if (mt.IsFixed) - { - _actualSize = mt.FixedLength; - } - else - { - // @hack: until we have ForceOffset behavior we have the following semantics: - // @hack: if the user supplies a Size through the Size property or constructor, - // @hack: we only send a MAX of Size bytes over. If the actualSize is < Size, then - // @hack: we send over actualSize - int coercedSize = 0; - - // get the actual length of the data, in bytes - switch (actualType) - { - case SqlDbType.NChar: - case SqlDbType.NVarChar: - case SqlDbType.NText: - case SqlDbType.Xml: - { - coercedSize = ((!HasFlag(SqlParameterFlags.IsNull)) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; - _actualSize = (ShouldSerializeSize() ? Size : 0); - _actualSize = (ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize; - if (_actualSize == -1) - { - _actualSize = coercedSize; - } - _actualSize <<= 1; - } - break; - case SqlDbType.Char: - case SqlDbType.VarChar: - case SqlDbType.Text: - { - // for these types, ActualSize is the num of chars, not actual bytes - since non-unicode chars are not always uniform size - coercedSize = (!HasFlag(SqlParameterFlags.IsNull) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; - _actualSize = (ShouldSerializeSize() ? Size : 0); - _actualSize = (ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize; - if (_actualSize == -1) - { - _actualSize = coercedSize; - } - } - break; - case SqlDbType.Binary: - case SqlDbType.VarBinary: - case SqlDbType.Image: - case SqlDbType.Timestamp: - coercedSize = (!HasFlag(SqlParameterFlags.IsNull) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (BinarySize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; - _actualSize = (ShouldSerializeSize() ? Size : 0); - _actualSize = ((ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize); - if (_actualSize == -1) - { - _actualSize = coercedSize; - } - break; - case SqlDbType.Udt: - if (!IsNull) - { - //call the static function - coercedSize = AssemblyCache.GetLength(val); - } - break; - case SqlDbType.Structured: - coercedSize = -1; - break; - case SqlDbType.Time: - _actualSize = isSqlVariant ? 5 : MetaType.GetTimeSizeFromScale(GetActualScale()); - break; - case SqlDbType.DateTime2: - // Date in number of days (3 bytes) + time - _actualSize = 3 + (isSqlVariant ? 5 : MetaType.GetTimeSizeFromScale(GetActualScale())); - break; - case SqlDbType.DateTimeOffset: - // Date in days (3 bytes) + offset in minutes (2 bytes) + time - _actualSize = 5 + (isSqlVariant ? 5 : MetaType.GetTimeSizeFromScale(GetActualScale())); - break; - default: - Debug.Fail("Unknown variable length type!"); - break; - } - - // don't even send big values over to the variant - if (isSqlVariant && (coercedSize > TdsEnums.TYPE_SIZE_LIMIT)) - { - throw SQL.ParameterInvalidVariant(ParameterName); - } - } - } - - return _actualSize; - } - - internal byte GetActualPrecision() - { - return ShouldSerializePrecision() ? PrecisionInternal : ValuePrecision(CoercedValue); - } - - internal object GetCoercedValue() - { - // NOTE: User can change the Udt at any time - if ((_coercedValue == null) || (_internalMetaType.SqlDbType == SqlDbType.Udt)) - { // will also be set during parameter Validation - bool isDataFeed = Value is DataFeed; - if (IsNull || isDataFeed) - { - // No coercion is done for DataFeeds and Nulls - _coercedValue = Value; - SetFlag(SqlParameterFlags.CoercedValueIsSqlType, _coercedValue != null && HasFlag(SqlParameterFlags.IsSqlParameterSqlType)); // set to null for output parameters that keeps _isSqlParameterSqlType - SetFlag(SqlParameterFlags.CoercedValueIsDataFeed, isDataFeed); - _actualSize = IsNull ? 0 : -1; - } - else - { - _coercedValue = CoerceValue(Value, _internalMetaType, out bool coercedValueIsDataFeed, out bool typeChanged); - SetFlag(SqlParameterFlags.CoercedValueIsDataFeed, coercedValueIsDataFeed); - SetFlag(SqlParameterFlags.CoercedValueIsSqlType, HasFlag(SqlParameterFlags.IsSqlParameterSqlType) && (!typeChanged)); // Type changed always results in a CLR type - _actualSize = -1; - } - } - AssertCachedPropertiesAreValid(); - return _coercedValue; - } - - internal int GetParameterSize() - { - return ShouldSerializeSize() ? Size : ValueSize(CoercedValue); - } - - /// - /// Get SMI Metadata to write out type_info stream. - /// - /// - internal SmiParameterMetaData GetMetadataForTypeInfo() - { - if (_internalMetaType == null) - { - _internalMetaType = GetMetaTypeOnly(); - } - - return MetaDataForSmi(out _); - } - - // IMPORTANT DEVNOTE: This method is being used for parameter encryption functionality, to get the type_info TDS object from SqlParameter. - // Please consider impact to that when changing this method. Refer to the callers of SqlParameter.GetMetadataForTypeInfo(). - internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhead) - { - peekAhead = null; - MetaType mt = ValidateTypeLengths(true /* 2005 or newer */ ); - long actualLen = GetActualSize(); - long maxLen = Size; - - // GetActualSize returns bytes length, but smi expects char length for - // character types, so adjust - if (!mt.IsLong) - { - if (mt.SqlDbType == SqlDbType.NChar || mt.SqlDbType == SqlDbType.NVarChar) - { - actualLen /= sizeof(char); - } - - if (actualLen > maxLen) - { - maxLen = actualLen; - } - } - - // Determine maxLength for types that ValidateTypeLengths won't figure out - if (maxLen == 0) - { - if (mt.SqlDbType == SqlDbType.Binary || mt.SqlDbType == SqlDbType.VarBinary) - { - maxLen = SmiMetaData.MaxBinaryLength; - } - else if (mt.SqlDbType == SqlDbType.Char || mt.SqlDbType == SqlDbType.VarChar) - { - maxLen = SmiMetaData.MaxANSICharacters; - } - else if (mt.SqlDbType == SqlDbType.NChar || mt.SqlDbType == SqlDbType.NVarChar) - { - maxLen = SmiMetaData.MaxUnicodeCharacters; - } - } - else if ( - (maxLen > SmiMetaData.MaxBinaryLength && (SqlDbType.Binary == mt.SqlDbType || SqlDbType.VarBinary == mt.SqlDbType)) || - (maxLen > SmiMetaData.MaxANSICharacters && (SqlDbType.Char == mt.SqlDbType || SqlDbType.VarChar == mt.SqlDbType)) || - (maxLen > SmiMetaData.MaxUnicodeCharacters && (SqlDbType.NChar == mt.SqlDbType || SqlDbType.NVarChar == mt.SqlDbType)) - ) - { - maxLen = -1; - } - - - int localeId = LocaleId; - if (localeId == 0 && mt.IsCharType) - { - if (GetCoercedValue() is SqlString sqlString && !sqlString.IsNull) - { - localeId = sqlString.LCID; - } - else - { - localeId = CultureInfo.CurrentCulture.LCID; - } - } - - SqlCompareOptions compareOpts = CompareInfo; - if (compareOpts == 0 && mt.IsCharType) - { - if (GetCoercedValue() is SqlString sqlString && !sqlString.IsNull) - { - compareOpts = sqlString.SqlCompareOptions; - } - else - { - compareOpts = SmiMetaData.GetDefaultForType(mt.SqlDbType).CompareOptions; - } - } - - string typeSpecificNamePart1 = null; - string typeSpecificNamePart2 = null; - string typeSpecificNamePart3 = null; - - if (SqlDbType.Xml == mt.SqlDbType) - { - typeSpecificNamePart1 = XmlSchemaCollectionDatabase; - typeSpecificNamePart2 = XmlSchemaCollectionOwningSchema; - typeSpecificNamePart3 = XmlSchemaCollectionName; - } - else if (SqlDbType.Udt == mt.SqlDbType || (SqlDbType.Structured == mt.SqlDbType && !string.IsNullOrEmpty(TypeName))) - { - // Split the input name. The type name is specified as single 3 part name. - // NOTE: ParseTypeName throws if format is incorrect - string[] names; - if (mt.SqlDbType == SqlDbType.Udt) - { - names = ParseTypeName(UdtTypeName, true /* is UdtTypeName */); - } - else - { - names = ParseTypeName(TypeName, false /* not UdtTypeName */); - } - - if (names.Length == 1) - { - typeSpecificNamePart3 = names[0]; - } - else if (names.Length == 2) - { - typeSpecificNamePart2 = names[0]; - typeSpecificNamePart3 = names[1]; - } - else if (names.Length == 3) - { - typeSpecificNamePart1 = names[0]; - typeSpecificNamePart2 = names[1]; - typeSpecificNamePart3 = names[2]; - } - else - { - throw ADP.ArgumentOutOfRange(nameof(names)); - } - - if ( - (!string.IsNullOrEmpty(typeSpecificNamePart1) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart1.Length) || - (!string.IsNullOrEmpty(typeSpecificNamePart2) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart2.Length) || - (!string.IsNullOrEmpty(typeSpecificNamePart3) && TdsEnums.MAX_SERVERNAME < typeSpecificNamePart3.Length) - ) - { - throw ADP.ArgumentOutOfRange(nameof(names)); - } - } - - byte precision = GetActualPrecision(); - byte scale = GetActualScale(); - - // precision for decimal types may still need adjustment. - if (mt.SqlDbType == SqlDbType.Decimal) - { - if (precision == 0) - { - precision = TdsEnums.DEFAULT_NUMERIC_PRECISION; - } - } - - // Sub-field determination - List fields = null; - SmiMetaDataPropertyCollection extendedProperties = null; - if (mt.SqlDbType == SqlDbType.Structured) - { - GetActualFieldsAndProperties(out fields, out extendedProperties, out peekAhead); - } - - return new SmiParameterMetaData( - mt.SqlDbType, - maxLen, - precision, - scale, - localeId, - compareOpts, - null, // Udt type not used for parameters - SqlDbType.Structured == mt.SqlDbType, - fields, - extendedProperties, - ParameterNameFixed, - typeSpecificNamePart1, - typeSpecificNamePart2, - typeSpecificNamePart3, - Direction - ); - } - - [Conditional("DEBUG")] - internal void AssertCachedPropertiesAreValid() - { - AssertPropertiesAreValid(_coercedValue, HasFlag(SqlParameterFlags.CoercedValueIsSqlType), HasFlag(SqlParameterFlags.CoercedValueIsDataFeed), IsNull); - } - - [Conditional("DEBUG")] - internal void AssertPropertiesAreValid(object value, bool? isSqlType = null, bool? isDataFeed = null, bool? isNull = null) - { - Debug.Assert(!isSqlType.HasValue || (isSqlType.Value == (value is INullable)), "isSqlType is incorrect"); - Debug.Assert(!isDataFeed.HasValue || (isDataFeed.Value == (value is DataFeed)), "isDataFeed is incorrect"); - Debug.Assert(!isNull.HasValue || (isNull.Value == ADP.IsNull(value)), "isNull is incorrect"); - } - - private SqlDbType GetMetaSqlDbTypeOnly() - { - MetaType metaType = _metaType; - if (null == metaType) - { // infer the type from the value - metaType = MetaType.GetDefaultMetaType(); - } - return metaType.SqlDbType; - } - - // This may not be a good thing to do in case someone overloads the parameter type but I - // don't want to go from SqlDbType -> metaType -> TDSType - private MetaType GetMetaTypeOnly() - { - if (_metaType != null) - { - return _metaType; - } - if (null != _value && DBNull.Value != _value) - { - // We have a value set by the user then just use that value - // char and char[] are not directly supported so we convert those values to string - Type valueType = _value.GetType(); - if (valueType == typeof(char)) - { - _value = _value.ToString(); - valueType = typeof(string); - } - else if (valueType == typeof(char[])) - { - _value = new string((char[])_value); - valueType = typeof(string); - } - return MetaType.GetMetaTypeFromType(valueType); - } - else if (_sqlBufferReturnValue != null) - { // value came back from the server - Type valueType = _sqlBufferReturnValue.GetTypeFromStorageType(HasFlag(SqlParameterFlags.IsSqlParameterSqlType)); - if (valueType != null) - { - return MetaType.GetMetaTypeFromType(valueType); - } - } - return MetaType.GetDefaultMetaType(); - } - - internal void Prepare(SqlCommand cmd) - { - if (_metaType == null) - { - throw ADP.PrepareParameterType(cmd); - } - else if (!ShouldSerializeSize() && !_metaType.IsFixed) - { - throw ADP.PrepareParameterSize(cmd); - } - else if ((!ShouldSerializePrecision() && !ShouldSerializeScale()) && (_metaType.SqlDbType == SqlDbType.Decimal)) - { - throw ADP.PrepareParameterScale(cmd, SqlDbType.ToString()); - } - } - - private void PropertyChanging() - { - _internalMetaType = null; - } - - private void PropertyTypeChanging() - { - PropertyChanging(); - CoercedValue = null; - } - - internal void ResetParent() => _parent = null; - - private void SetFlag(SqlParameterFlags flag, bool value) - { - _flags = value ? _flags | flag : _flags & ~flag; - } - - internal void SetSqlBuffer(SqlBuffer buff) - { - _sqlBufferReturnValue = buff; - _value = null; - _coercedValue = null; - SetFlag(SqlParameterFlags.IsNull, _sqlBufferReturnValue.IsNull); - SetFlag(SqlParameterFlags.CoercedValueIsDataFeed, false); - SetFlag(SqlParameterFlags.CoercedValueIsSqlType, false); - _udtLoadError = null; - _actualSize = -1; - } - - internal void SetUdtLoadError(Exception e) - { - _udtLoadError = e; - } - - internal void Validate(int index, bool isCommandProc) - { - MetaType metaType = GetMetaTypeOnly(); - _internalMetaType = metaType; - - // SqlParameter does a Size Validation check and would fail if the size is 0. - // This condition filters all scenarios where we view a valid size 0. - if ( - ADP.IsDirection(this, ParameterDirection.Output) && - !ADP.IsDirection(this, ParameterDirection.ReturnValue) && - (!metaType.IsFixed) && - !ShouldSerializeSize() && - ((null == _value) || Convert.IsDBNull(_value)) && - (SqlDbType != SqlDbType.Timestamp) && - (SqlDbType != SqlDbType.Udt) && - // BUG: (VSTFDevDiv - 479609): Output parameter with size 0 throws for XML, TEXT, NTEXT, IMAGE. - // NOTE: (VSTFDevDiv - 479609): Not Fixed for TEXT, NTEXT, IMAGE as these are deprecated LOB types. - (SqlDbType != SqlDbType.Xml) && - !metaType.IsVarTime - ) - { - throw ADP.UninitializedParameterSize(index, metaType.ClassType); - } - - if (metaType.SqlDbType != SqlDbType.Udt && Direction != ParameterDirection.Output) - { - GetCoercedValue(); - } - - //check if the UdtTypeName is specified for Udt params - if (metaType.SqlDbType == SqlDbType.Udt) - { - if (string.IsNullOrEmpty(UdtTypeName)) - { - throw SQL.MustSetUdtTypeNameForUdtParams(); - } - } - else if (!string.IsNullOrEmpty(UdtTypeName)) - { - throw SQL.UnexpectedUdtTypeNameForNonUdtParams(); - } - - // Validate structured-type-specific details. - if (metaType.SqlDbType == SqlDbType.Structured) - { - if (!isCommandProc && string.IsNullOrEmpty(TypeName)) - { - throw SQL.MustSetTypeNameForParam(metaType.TypeName, ParameterName); - } - - if (Direction != ParameterDirection.Input) - { - throw SQL.UnsupportedTVPOutputParameter(Direction, ParameterName); - } - - if (GetCoercedValue() == DBNull.Value) - { - throw SQL.DBNullNotSupportedForTVPValues(ParameterName); - } - } - else if (!string.IsNullOrEmpty(TypeName)) - { - throw SQL.UnexpectedTypeNameForNonStructParams(ParameterName); - } - } - - // func will change type to that with a 4 byte length if the type has a two - // byte length and a parameter length > than that expressible in 2 bytes - internal MetaType ValidateTypeLengths(bool is2005OrNewer) - { - MetaType mt = InternalMetaType; - // Since the server will automatically reject any - // char, varchar, binary, varbinary, nchar, or nvarchar parameter that has a - // byte sizeInCharacters > 8000 bytes, we promote the parameter to image, text, or ntext. This - // allows the user to specify a parameter type using a COM+ datatype and be able to - // use that parameter against a BLOB column. - if ((mt.SqlDbType != SqlDbType.Udt) && !mt.IsFixed && !mt.IsLong) - { // if type has 2 byte length - long actualSizeInBytes = GetActualSize(); - long sizeInCharacters = Size; - - // Bug: VSTFDevDiv #636867 - // Notes: - // 'actualSizeInBytes' is the size of value passed; - // 'sizeInCharacters' is the parameter size; - // 'actualSizeInBytes' is in bytes; - // 'this.Size' is in charaters; - // 'sizeInCharacters' is in characters; - // 'TdsEnums.TYPE_SIZE_LIMIT' is in bytes; - // For Non-NCharType and for non-2005 or greater variables, size should be maintained; - // Reverting changes from bug VSTFDevDiv # 479739 as it caused an regression; - // Modifed variable names from 'size' to 'sizeInCharacters', 'actualSize' to 'actualSizeInBytes', and - // 'maxSize' to 'maxSizeInBytes' - // The idea is to - // 1) revert the regression from bug 479739 - // 2) fix as many scenarios as possible including bug 636867 - // 3) cause no additional regression from 3.5 sp1 - // Keeping these goals in mind - the following are the changes we are making - - long maxSizeInBytes; - if (mt.IsNCharType && is2005OrNewer) - { - maxSizeInBytes = ((sizeInCharacters * sizeof(char)) > actualSizeInBytes) ? sizeInCharacters * sizeof(char) : actualSizeInBytes; - } - else - { - // Notes: - // Elevation from (n)(var)char (4001+) to (n)text succeeds without failure only with 2005 and greater. - // it fails in sql server 2000 - maxSizeInBytes = (sizeInCharacters > actualSizeInBytes) ? sizeInCharacters : actualSizeInBytes; - } - - if ( - (maxSizeInBytes > TdsEnums.TYPE_SIZE_LIMIT) || - HasFlag(SqlParameterFlags.CoercedValueIsDataFeed) || - (sizeInCharacters == -1) || - (actualSizeInBytes == -1) - ) - { // is size > size able to be described by 2 bytes - if (is2005OrNewer) - { - // Convert the parameter to its max type - mt = MetaType.GetMaxMetaTypeFromMetaType(mt); - _metaType = mt; - InternalMetaType = mt; - if (!mt.IsPlp) - { - if (mt.SqlDbType == SqlDbType.Xml) - { - throw ADP.InvalidMetaDataValue(); //Xml should always have IsPartialLength = true - } - if ( - mt.SqlDbType == SqlDbType.NVarChar || - mt.SqlDbType == SqlDbType.VarChar || - mt.SqlDbType == SqlDbType.VarBinary - ) - { - Size = (int)SmiMetaData.UnlimitedMaxLengthIndicator; - } - } - } - else - { - switch (mt.SqlDbType) - { // widening the SqlDbType is automatic - case SqlDbType.Binary: - case SqlDbType.VarBinary: - mt = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.Image, false); - _metaType = mt; // do not use SqlDbType property which calls PropertyTypeChanging resetting coerced value - InternalMetaType = mt; - break; - case SqlDbType.Char: - case SqlDbType.VarChar: - mt = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.Text, false); - _metaType = mt; - InternalMetaType = mt; - break; - case SqlDbType.NChar: - case SqlDbType.NVarChar: - mt = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.NText, false); - _metaType = mt; - InternalMetaType = mt; - break; - default: - Debug.Assert(false, "Missed metatype in SqlCommand.BuildParamList()"); - break; - } - } - } - } - return mt; - } - - private byte ValuePrecision(object value) - { - if (value is SqlDecimal sqlDecimal) - { - if (sqlDecimal.IsNull) - { - return 0; - } - return sqlDecimal.Precision; - } - return ValuePrecisionCore(value); - } - - private byte ValueScale(object value) - { - if (value is SqlDecimal sqlDecimal) - { - if (sqlDecimal.IsNull) - { - return 0; - } - return sqlDecimal.Scale; - } - return ValueScaleCore(value); - } - - private int ValueSize(object value) - { - if (value is SqlString sqlString) - { - if (sqlString.IsNull) - { - return 0; - } - return sqlString.Value.Length; - } - if (value is SqlChars sqlChars) - { - if (sqlChars.IsNull) - { - return 0; - } - return sqlChars.Value.Length; - } - - if (value is SqlBinary sqlBinary) - { - if (sqlBinary.IsNull) - { - return 0; - } - return sqlBinary.Length; - } - if (value is SqlBytes sqlBytes) - { - if (sqlBytes.IsNull) - { - return 0; - } - return (int)(sqlBytes.Length); - } - if (value is DataFeed) - { - // Unknown length - return 0; - } - return ValueSizeCore(value); - } - - private byte ValuePrecisionCore(object value) - { - if (value is decimal decimalValue) - { - return ((SqlDecimal)decimalValue).Precision; - } - return 0; - } - - private byte ValueScaleCore(object value) - { - if (value is decimal decimalValue) - { - return (byte)((decimal.GetBits(decimalValue)[3] & 0x00ff0000) >> 0x10); - } - return 0; - } - - private int ValueSizeCore(object value) - { - if (!ADP.IsNull(value)) - { - switch (value) - { - case string svalue: - return svalue.Length; - case byte[] bvalue: - return bvalue.Length; - case char[] cvalue: - return cvalue.Length; - case byte _: - case char _: - return 1; - } - } - return 0; - } - - - // Coerced Value is also used in SqlBulkCopy.ConvertValue(object value, _SqlMetaData metadata) - internal static object CoerceValue(object value, MetaType destinationType, out bool coercedToDataFeed, out bool typeChanged, bool allowStreaming = true) - { - Debug.Assert(!(value is DataFeed), "Value provided should not already be a data feed"); - Debug.Assert(!ADP.IsNull(value), "Value provided should not be null"); - Debug.Assert(null != destinationType, "null destinationType"); - - coercedToDataFeed = false; - typeChanged = false; - Type currentType = value.GetType(); - - if ( - (destinationType.ClassType != typeof(object)) && - (destinationType.ClassType != currentType) && - ( - (destinationType.SqlType != currentType) || - (destinationType.SqlDbType == SqlDbType.Xml) - ) - ) - { // Special case for Xml types (since we need to convert SqlXml into a string) - try - { - // Assume that the type changed - typeChanged = true; - if (destinationType.ClassType == typeof(string)) - { - // For Xml data, destination Type is always string - if (currentType == typeof(SqlXml)) - { - value = MetaType.GetStringFromXml((XmlReader)(((SqlXml)value).CreateReader())); - } - else if (currentType == typeof(SqlString)) - { - typeChanged = false; // Do nothing - } - else if (typeof(XmlReader).IsAssignableFrom(currentType)) - { - if (allowStreaming) - { - coercedToDataFeed = true; - value = new XmlDataFeed((XmlReader)value); - } - else - { - value = MetaType.GetStringFromXml((XmlReader)value); - } - } - else if (currentType == typeof(char[])) - { - value = new string((char[])value); - } - else if (currentType == typeof(SqlChars)) - { - value = new string(((SqlChars)value).Value); - } - else if (value is TextReader textReader && allowStreaming) - { - coercedToDataFeed = true; - value = new TextDataFeed(textReader); - } - else - { - value = Convert.ChangeType(value, destinationType.ClassType, null); - } - } - else if ((destinationType.DbType == DbType.Currency) && (currentType == typeof(string))) - { - value = decimal.Parse((string)value, NumberStyles.Currency, null); - } - else if ((currentType == typeof(SqlBytes)) && (destinationType.ClassType == typeof(byte[]))) - { - typeChanged = false; // Do nothing - } - else if ((currentType == typeof(string)) && (destinationType.SqlDbType == SqlDbType.Time)) - { - value = TimeSpan.Parse((string)value); - } - else if ((currentType == typeof(string)) && (destinationType.SqlDbType == SqlDbType.DateTimeOffset)) - { - value = DateTimeOffset.Parse((string)value, (IFormatProvider)null); - } - else if ((currentType == typeof(DateTime)) && (destinationType.SqlDbType == SqlDbType.DateTimeOffset)) - { - value = new DateTimeOffset((DateTime)value); - } - else if ( - TdsEnums.SQLTABLE == destinationType.TDSType && - ( - value is DataTable || - value is DbDataReader || - value is IEnumerable - ) - ) - { - // no conversion for TVPs. - typeChanged = false; - } - else if (destinationType.ClassType == typeof(byte[]) && allowStreaming && value is Stream stream) - { - coercedToDataFeed = true; - value = new StreamDataFeed(stream); - } - else - { - value = Convert.ChangeType(value, destinationType.ClassType, null); - } - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - - throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); - } - } - - Debug.Assert(allowStreaming || !coercedToDataFeed, "Streaming is not allowed, but type was coerced into a data feed"); - Debug.Assert(value.GetType() == currentType ^ typeChanged, "Incorrect value for typeChanged"); - return value; - } - - private static int StringSize(object value, bool isSqlType) - { - if (isSqlType) - { - Debug.Assert(!((INullable)value).IsNull, "Should not call StringSize on null values"); - if (value is SqlString sqlString) - { - return sqlString.Value.Length; - } - if (value is SqlChars sqlChars) - { - return sqlChars.Value.Length; - } - } - else - { - if (value is string svalue) - { - return svalue.Length; - } - if (value is char[] cvalue) - { - return cvalue.Length; - } - if (value is char) - { - return 1; - } - } - - // Didn't match, unknown size - return 0; - } - - private static int BinarySize(object value, bool isSqlType) - { - if (isSqlType) - { - Debug.Assert(!((INullable)value).IsNull, "Should not call StringSize on null values"); - if (value is SqlBinary sqlBinary) - { - return sqlBinary.Length; - } - if (value is SqlBytes sqlBytes) - { - return sqlBytes.Value.Length; - } - } - else - { - if (value is byte[] bvalue) - { - return bvalue.Length; - } - if (value is byte) - { - return 1; - } - } - - // Didn't match, unknown size - return 0; - } - - // parse an string of the form db.schema.name where any of the three components - // might have "[" "]" and dots within it. - // returns: - // [0] dbname (or null) - // [1] schema (or null) - // [2] name - // NOTE: if perf/space implications of Regex is not a problem, we can get rid - // of this and use a simple regex to do the parsing - internal static string[] ParseTypeName(string typeName, bool isUdtTypeName) - { - Debug.Assert(null != typeName, "null typename passed to ParseTypeName"); - - try - { - string errorMsg = isUdtTypeName ? Strings.SQL_UDTTypeName : Strings.SQL_TypeName; - return MultipartIdentifier.ParseMultipartIdentifier(typeName, "[\"", "]\"", '.', 3, true, errorMsg, true); - } - catch (ArgumentException) - { - if (isUdtTypeName) - { - throw SQL.InvalidUdt3PartNameFormat(); - } - else - { - throw SQL.InvalidParameterTypeNameFormat(); - } - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs similarity index 90% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs index 9845a03aa7..c78ef7dfc7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -44,7 +44,6 @@ internal TextDataFeed(TextReader source) { _source = source; } - internal static UnicodeEncoding DefaultEncoding { get @@ -64,13 +63,10 @@ internal class XmlDataFeed : DataFeed { internal XmlReader _source; - internal XmlDataFeed(XmlReader source) - { - _source = source; - } + internal XmlDataFeed(XmlReader source) => _source = source; } - /// + /// [TypeConverter(typeof(SqlParameter.SqlParameterConverter))] public sealed partial class SqlParameter : DbParameter, IDbDataParameter, ICloneable { @@ -247,7 +243,7 @@ private enum SqlParameterFlags : ushort private DataRowVersion _sourceVersion; private SqlParameterFlags _flags; - /// + /// public SqlParameter() : base() { _flags = SqlParameterFlags.IsNull; @@ -255,14 +251,14 @@ public SqlParameter() : base() _direction = ParameterDirection.Input; } - /// + /// public SqlParameter(string parameterName, SqlDbType dbType) : this() { ParameterName = parameterName; SqlDbType = dbType; } - /// + /// public SqlParameter(string parameterName, object value) : this() { Debug.Assert(!(value is SqlDbType), "use SqlParameter(string, SqlDbType)"); @@ -271,7 +267,7 @@ public SqlParameter(string parameterName, object value) : this() Value = value; } - /// + /// public SqlParameter(string parameterName, SqlDbType dbType, int size) : this() { ParameterName = parameterName; @@ -279,7 +275,7 @@ public SqlParameter(string parameterName, SqlDbType dbType, int size) : this() Size = size; } - /// + /// public SqlParameter(string parameterName, SqlDbType dbType, int size, string sourceColumn) : this() { ParameterName = parameterName; @@ -288,7 +284,7 @@ public SqlParameter(string parameterName, SqlDbType dbType, int size, string sou SourceColumn = sourceColumn; } - /// + /// [EditorBrowsable(EditorBrowsableState.Advanced)] public SqlParameter( string parameterName, @@ -306,13 +302,18 @@ object value { Direction = direction; IsNullable = isNullable; +#if NETFRAMEWORK + PrecisionInternal = precision; + ScaleInternal = scale; +#else Precision = precision; Scale = scale; +#endif SourceVersion = sourceVersion; Value = value; } - /// + /// public SqlParameter( string parameterName, SqlDbType dbType, @@ -334,8 +335,13 @@ string xmlSchemaCollectionName SqlDbType = dbType; Size = size; Direction = direction; +#if NETFRAMEWORK + PrecisionInternal = precision; + ScaleInternal = scale; +#else Precision = precision; Scale = scale; +#endif SourceColumn = sourceColumn; SourceVersion = sourceVersion; SourceColumnNullMapping = sourceColumnNullMapping; @@ -381,7 +387,7 @@ internal bool HasReceivedMetadata /// internal byte NormalizationRuleVersion => CipherMetadata?.NormalizationRuleVersion ?? 0x00; - /// + /// [Browsable(false)] public SqlCompareOptions CompareInfo { @@ -417,34 +423,34 @@ public SqlCompareOptions CompareInfo } } - /// - [ResCategory("XML")] + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Xml)] public string XmlSchemaCollectionDatabase { get => _xmlSchemaCollection?.Database ?? string.Empty; set => EnsureXmlSchemaCollection().Database = value; } - /// - [ResCategory("XML")] + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Xml)] public string XmlSchemaCollectionOwningSchema { get => _xmlSchemaCollection?.OwningSchema ?? string.Empty; set => EnsureXmlSchemaCollection().OwningSchema = value; } - /// - [ResCategory("XML")] + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Xml)] public string XmlSchemaCollectionName { get => _xmlSchemaCollection?.Name ?? string.Empty; set => EnsureXmlSchemaCollection().Name = value; } - /// + /// [ DefaultValue(false), - ResCategory("Data") + ResCategory(StringsHelper.ResourceNames.DataCategory_Data) ] public bool ForceColumnEncryption { @@ -452,7 +458,7 @@ public bool ForceColumnEncryption set => SetFlag(SqlParameterFlags.ForceColumnEncryption, value); } - /// + /// public override DbType DbType { get => GetMetaTypeOnly().DbType; @@ -467,11 +473,11 @@ public override DbType DbType } } - /// + /// public override void ResetDbType() => ResetSqlDbType(); - /// - [ResCategory("Data")] + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] public override string ParameterName { get => _parameterName ?? string.Empty; @@ -499,7 +505,7 @@ public override string ParameterName } } - /// + /// [Browsable(false)] public int LocaleId { @@ -529,10 +535,10 @@ public int LocaleId } } - /// + /// [ DefaultValue((byte)0), - ResCategory("Data") + ResCategory(StringsHelper.ResourceNames.DataCategory_Data) ] public new byte Precision { @@ -542,10 +548,10 @@ public int LocaleId private bool ShouldSerializePrecision() => _precision != 0; - /// + /// [ DefaultValue((byte)0), - ResCategory("Data") + ResCategory(StringsHelper.ResourceNames.DataCategory_Data) ] public new byte Scale { @@ -579,10 +585,10 @@ internal byte ScaleInternal private bool ShouldSerializeScale() => _scale != 0; // V1.0 compat, ignore _hasScale - /// + /// [ RefreshProperties(RefreshProperties.All), - ResCategory("Data"), + ResCategory(StringsHelper.ResourceNames.DataCategory_Data), DbProviderSpecificTypeProperty(true) ] public SqlDbType SqlDbType @@ -612,7 +618,7 @@ public SqlDbType SqlDbType private bool ShouldSerializeSqlDbType() => _metaType != null; - /// + /// public void ResetSqlDbType() { if (_metaType != null) @@ -622,7 +628,7 @@ public void ResetSqlDbType() } } - /// + /// [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), @@ -673,7 +679,7 @@ public object SqlValue } } - /// + /// [ Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced) @@ -684,7 +690,7 @@ public string UdtTypeName set => _udtTypeName = value; } - /// + /// [ Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced) @@ -699,10 +705,10 @@ public string TypeName } } - /// + /// [ RefreshProperties(RefreshProperties.All), - ResCategory("Data"), + ResCategory(StringsHelper.ResourceNames.DataCategory_Data), TypeConverter(typeof(StringConverter)), ] public override object Value @@ -741,10 +747,10 @@ public override object Value } } - /// + /// [ RefreshProperties(RefreshProperties.All), - ResCategory("Data"), + ResCategory(StringsHelper.ResourceNames.DataCategory_Data), ] public override ParameterDirection Direction { @@ -769,14 +775,14 @@ public override ParameterDirection Direction } } - /// + /// public override bool IsNullable { get => HasFlag(SqlParameterFlags.IsNullable); set => SetFlag(SqlParameterFlags.IsNullable, value); } - /// + /// public int Offset { get => _offset; @@ -790,8 +796,8 @@ public int Offset } } - /// - [ResCategory("Data")] + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] public override int Size { get @@ -828,28 +834,30 @@ private void ResetSize() private bool ShouldSerializeSize() => _size != 0; - /// - [ResCategory("Update")] + /// + [ResCategory(StringsHelper.ResourceNames.DataCategory_Update)] public override string SourceColumn { get => _sourceColumn ?? string.Empty; set => _sourceColumn = value; } - /// + /// [ResCategory("DataCategory_Update")] +#if !NETFRAMEWORK [ResDescription(StringsHelper.ResourceNames.SqlParameter_SourceColumnNullMapping)] +#endif public override bool SourceColumnNullMapping { get => HasFlag(SqlParameterFlags.SourceColumnNullMapping); set => SetFlag(SqlParameterFlags.SourceColumnNullMapping, value); } - /// + /// [ResCategory("Data")] public override string ToString() => ParameterName; - /// + /// [ResCategory(StringsHelper.ResourceNames.DataCategory_Update)] public override DataRowVersion SourceVersion { @@ -874,9 +882,11 @@ public override DataRowVersion SourceVersion } } - /// + + /// object ICloneable.Clone() => new SqlParameter(this); + private object CoercedValue { get => _coercedValue; @@ -993,6 +1003,8 @@ internal string ParameterNameFixed } } + internal bool SizeInferred => 0 == _size; + internal INullable ValueAsINullable => _valueAsINullable; internal bool IsDerivedParameterTypeName @@ -1048,6 +1060,10 @@ internal void CopyTo(SqlParameter destination) internal object CompareExchangeParent(object value, object comparand) { + // the interlock guarantees same parameter won't belong to multiple collections + // at the same time, but to actually occur the user must really try + // since we never declared thread safety, we don't care at this time + //return System.Threading.Interlocked.CompareExchange(ref _parent, value, comparand); object parent = _parent; if (comparand == parent) { @@ -1500,7 +1516,7 @@ internal int GetActualSize() case SqlDbType.NText: case SqlDbType.Xml: { - coercedSize = ((!HasFlag(SqlParameterFlags.IsNull)) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; + coercedSize = ((!HasFlag(SqlParameterFlags.IsNull)) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType)) : 0; _actualSize = (ShouldSerializeSize() ? Size : 0); _actualSize = (ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize; if (_actualSize == -1) @@ -1515,7 +1531,7 @@ internal int GetActualSize() case SqlDbType.Text: { // for these types, ActualSize is the num of chars, not actual bytes - since non-unicode chars are not always uniform size - coercedSize = ((!HasFlag(SqlParameterFlags.IsNull)) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; + coercedSize = (!HasFlag(SqlParameterFlags.IsNull) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (StringSize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; _actualSize = (ShouldSerializeSize() ? Size : 0); _actualSize = (ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize; if (_actualSize == -1) @@ -1528,7 +1544,7 @@ internal int GetActualSize() case SqlDbType.VarBinary: case SqlDbType.Image: case SqlDbType.Timestamp: - coercedSize = ((!HasFlag(SqlParameterFlags.IsNull)) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (BinarySize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; + coercedSize = (!HasFlag(SqlParameterFlags.IsNull) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (BinarySize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; _actualSize = (ShouldSerializeSize() ? Size : 0); _actualSize = ((ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize); if (_actualSize == -1) @@ -1539,7 +1555,12 @@ internal int GetActualSize() case SqlDbType.Udt: if (!IsNull) { +#if NETFRAMEWORK + //call the static function + coercedSize = AssemblyCache.GetLength(val); +#else coercedSize = SerializationHelperSql9.SizeInBytes(val); +#endif } break; case SqlDbType.Structured: @@ -1671,6 +1692,7 @@ internal SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhe maxLen = -1; } + int localeId = LocaleId; if (localeId == 0 && mt.IsCharType) { @@ -1866,10 +1888,7 @@ internal void Prepare(SqlCommand cmd) } } - private void PropertyChanging() - { - _internalMetaType = null; - } + private void PropertyChanging() => _internalMetaType = null; private void PropertyTypeChanging() { @@ -1879,6 +1898,11 @@ private void PropertyTypeChanging() internal void ResetParent() => _parent = null; + private void SetFlag(SqlParameterFlags flag, bool value) + { + _flags = value ? _flags | flag : _flags & ~flag; + } + internal void SetSqlBuffer(SqlBuffer buff) { _sqlBufferReturnValue = buff; @@ -1891,15 +1915,7 @@ internal void SetSqlBuffer(SqlBuffer buff) _actualSize = -1; } - private void SetFlag(SqlParameterFlags flag, bool value) - { - _flags = value ? _flags | flag : _flags & ~flag; - } - - internal void SetUdtLoadError(Exception e) - { - _udtLoadError = e; - } + internal void SetUdtLoadError(Exception e) => _udtLoadError = e; internal void Validate(int index, bool isCommandProc) { @@ -1916,6 +1932,8 @@ internal void Validate(int index, bool isCommandProc) ((null == _value) || Convert.IsDBNull(_value)) && (SqlDbType != SqlDbType.Timestamp) && (SqlDbType != SqlDbType.Udt) && + // BUG: (VSTFDevDiv - 479609): Output parameter with size 0 throws for XML, TEXT, NTEXT, IMAGE. + // NOTE: (VSTFDevDiv - 479609): Not Fixed for TEXT, NTEXT, IMAGE as these are deprecated LOB types. (SqlDbType != SqlDbType.Xml) && !metaType.IsVarTime ) @@ -1980,6 +1998,24 @@ internal MetaType ValidateTypeLengths() long actualSizeInBytes = GetActualSize(); long sizeInCharacters = Size; + // Bug: VSTFDevDiv #636867 + // Notes: + // 'actualSizeInBytes' is the size of value passed; + // 'sizeInCharacters' is the parameter size; + // 'actualSizeInBytes' is in bytes; + // 'this.Size' is in charaters; + // 'sizeInCharacters' is in characters; + // 'TdsEnums.TYPE_SIZE_LIMIT' is in bytes; + // For Non-NCharType and for non-2005 or greater variables, size should be maintained; + // Reverting changes from bug VSTFDevDiv # 479739 as it caused an regression; + // Modifed variable names from 'size' to 'sizeInCharacters', 'actualSize' to 'actualSizeInBytes', and + // 'maxSize' to 'maxSizeInBytes' + // The idea is to + // 1) revert the regression from bug 479739 + // 2) fix as many scenarios as possible including bug 636867 + // 3) cause no additional regression from 3.5 sp1 + // Keeping these goals in mind - the following are the changes we are making + long maxSizeInBytes; if (mt.IsNCharType) { @@ -1998,9 +2034,8 @@ internal MetaType ValidateTypeLengths() HasFlag(SqlParameterFlags.CoercedValueIsDataFeed) || (sizeInCharacters == -1) || (actualSizeInBytes == -1) - ) - { // is size > size able to be described by 2 bytes - // Convert the parameter to its max type + ) + { mt = MetaType.GetMaxMetaTypeFromMetaType(mt); _metaType = mt; InternalMetaType = mt; @@ -2131,6 +2166,7 @@ private int ValueSizeCore(object value) return 0; } + // Coerced Value is also used in SqlBulkCopy.ConvertValue(object value, _SqlMetaData metadata) internal static object CoerceValue(object value, MetaType destinationType, out bool coercedToDataFeed, out bool typeChanged, bool allowStreaming = true) { From 9b1996a768afad0f29ae692189ffef8b91770907 Mon Sep 17 00:00:00 2001 From: David Engel Date: Fri, 3 Jun 2022 13:45:09 -0700 Subject: [PATCH 13/35] Fixing transaction doc samples (#1632) BeginTransaction - no parameters BeginTransaction2 - named transaction/savepoint BeginTransaction3 - named transaction/savepoint + isolation level --- doc/samples/SqlConnection_BeginTransaction.cs | 2 +- doc/samples/SqlConnection_BeginTransaction2.cs | 2 +- doc/samples/SqlConnection_BeginTransaction3.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/samples/SqlConnection_BeginTransaction.cs b/doc/samples/SqlConnection_BeginTransaction.cs index 9665c33c12..13f9c0414d 100644 --- a/doc/samples/SqlConnection_BeginTransaction.cs +++ b/doc/samples/SqlConnection_BeginTransaction.cs @@ -24,7 +24,7 @@ private static void ExecuteSqlTransaction(string connectionString) SqlTransaction transaction; // Start a local transaction. - transaction = connection.BeginTransaction("SampleTransaction"); + transaction = connection.BeginTransaction(); // Must assign both transaction object and connection // to Command object for a pending local transaction diff --git a/doc/samples/SqlConnection_BeginTransaction2.cs b/doc/samples/SqlConnection_BeginTransaction2.cs index 9665c33c12..1be9f5987d 100644 --- a/doc/samples/SqlConnection_BeginTransaction2.cs +++ b/doc/samples/SqlConnection_BeginTransaction2.cs @@ -52,7 +52,7 @@ private static void ExecuteSqlTransaction(string connectionString) // Attempt to roll back the transaction. try { - transaction.Rollback(); + transaction.Rollback("SampleTransaction"); } catch (Exception ex2) { diff --git a/doc/samples/SqlConnection_BeginTransaction3.cs b/doc/samples/SqlConnection_BeginTransaction3.cs index 44eb020ff7..c68284f6d9 100644 --- a/doc/samples/SqlConnection_BeginTransaction3.cs +++ b/doc/samples/SqlConnection_BeginTransaction3.cs @@ -47,7 +47,7 @@ private static void ExecuteSqlTransaction(string connectionString) { try { - transaction.Rollback(); + transaction.Rollback("SampleTransaction"); } catch (SqlException ex) { From 031afe853c6a00b6093254b675954b7eabdbf75e Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:10:46 -0700 Subject: [PATCH 14/35] Fix | Handle NRE on Azure federated authentication (#1625) --- .../SqlClient/SqlInternalConnectionTds.cs | 29 +++++++---------- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 4 +++ .../netcore/src/Resources/Strings.Designer.cs | 31 +++++++----------- .../netcore/src/Resources/Strings.resx | 9 ++---- .../SqlClient/SqlInternalConnectionTds.cs | 28 +++++++--------- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 5 +++ .../netfx/src/Resources/Strings.Designer.cs | 11 ++++++- .../netfx/src/Resources/Strings.resx | 3 ++ .../src/Microsoft/Data/Common/AdapterUtil.cs | 32 +++++++++++++++++-- 9 files changed, 89 insertions(+), 63 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index ac3aa1f30b..6e25b56516 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -103,6 +103,9 @@ public void AssertUnrecoverableStateCountIsCorrect() internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable { + // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 + internal const int MsalHttpRetryStatusCode = 429; + // CONNECTION AND STATE VARIABLES private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance private TdsParser _parser; @@ -2421,7 +2424,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // Deal with Msal service exceptions first, retry if 429 received. catch (MsalServiceException serviceException) { - if (429 == serviceException.StatusCode) + if (serviceException.StatusCode == MsalHttpRetryStatusCode) { RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter; if (retryAfter.Delta.HasValue) @@ -2440,9 +2443,15 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) } else { - break; + SqlClientEventSource.Log.TryTraceEvent(" Timeout: {0}", serviceException.ErrorCode); + throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), serviceException.ErrorCode, serviceException); } } + else + { + SqlClientEventSource.Log.TryTraceEvent(" {0}", serviceException.ErrorCode); + throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username); + } } // Deal with normal MsalExceptions. catch (MsalException msalException) @@ -2453,21 +2462,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { SqlClientEventSource.Log.TryTraceEvent(" {0}", msalException.ErrorCode); - // Error[0] - SqlErrorCollection sqlErs = new(); - sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, StringsHelper.GetString(Strings.SQL_MSALFailure, username, ConnectionOptions.Authentication.ToString("G")), ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); - - // Error[1] - string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode); - sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); - - // Error[2] - if (!string.IsNullOrEmpty(msalException.Message)) - { - sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, msalException.Message, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); - } - SqlException exc = SqlException.CreateException(sqlErs, "", this); - throw exc; + throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); } SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs index 6c9247b9ef..4aebe4b518 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -510,6 +510,10 @@ internal static Exception ActiveDirectoryDeviceFlowTimeout() return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication); } + internal static Exception ActiveDirectoryTokenRetrievingTimeout(string authenticaton, string errorCode, Exception exception) + { + return ADP.TimeoutException(StringsHelper.GetString(Strings.AAD_Token_Retrieving_Timeout, authenticaton, errorCode, exception?.Message), exception); + } // // SQL.DataCommand diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs index 0629ceb1ac..9f3d2fec91 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace System { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -60,6 +60,15 @@ internal Strings() { } } + /// + /// Looks up a localized string similar to Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}. + /// + internal static string AAD_Token_Retrieving_Timeout { + get { + return ResourceManager.GetString("AAD_Token_Retrieving_Timeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to Specified parameter name '{0}' is not valid.. /// @@ -941,25 +950,7 @@ internal static string Data_InvalidOffsetLength { return ResourceManager.GetString("Data_InvalidOffsetLength", resourceCulture); } } - - /// - /// Looks up a localized string similar to Update. - /// - internal static string DataCategory_Update { - get { - return ResourceManager.GetString("DataCategory_Update", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to XML. - /// - internal static string DataCategory_Xml { - get { - return ResourceManager.GetString("DataCategory_Xml", resourceCulture); - } - } - + /// /// Looks up a localized string similar to Internal error occurred when retrying the download of the HGS root certificate after the initial request failed. Contact Customer Support Services.. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx index 6be833f7ee..433c6a7684 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx @@ -1932,10 +1932,7 @@ Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. - - Update + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} - - XML - - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 529b38f2d3..47f4b29810 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -105,6 +105,8 @@ public void AssertUnrecoverableStateCountIsCorrect() sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable { + // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 + internal const int MsalHttpRetryStatusCode = 429; // Connection re-route limit internal const int _maxNumberOfRedirectRoute = 10; @@ -2870,7 +2872,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // Deal with Msal service exceptions first, retry if 429 received. catch (MsalServiceException serviceException) { - if (429 == serviceException.StatusCode) + if (serviceException.StatusCode == MsalHttpRetryStatusCode) { RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter; if (retryAfter.Delta.HasValue) @@ -2889,9 +2891,15 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) } else { - break; + SqlClientEventSource.Log.TryTraceEvent(" Timeout: {0}", serviceException.ErrorCode); + throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), serviceException.ErrorCode, serviceException); } } + else + { + SqlClientEventSource.Log.TryTraceEvent(" {0}", serviceException.ErrorCode); + throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username); + } } // Deal with normal MsalExceptions. catch (MsalException msalException) @@ -2902,21 +2910,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { SqlClientEventSource.Log.TryTraceEvent(" {0}", msalException.ErrorCode); - // Error[0] - SqlErrorCollection sqlErs = new SqlErrorCollection(); - sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, StringsHelper.GetString(Strings.SQL_MSALFailure, username, ConnectionOptions.Authentication.ToString("G")), ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); - - // Error[1] - string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode); - sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); - - // Error[2] - if (!string.IsNullOrEmpty(msalException.Message)) - { - sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, msalException.Message, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); - } - SqlException exc = SqlException.CreateException(sqlErs, "", this); - throw exc; + throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); } SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs index 26f1e59fe2..4cd10a988b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -656,6 +656,11 @@ static internal Exception ActiveDirectoryDeviceFlowTimeout() return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication); } + internal static Exception ActiveDirectoryTokenRetrievingTimeout(string authenticaton, string errorCode, Exception exception) + { + return ADP.TimeoutException(StringsHelper.GetString(Strings.AAD_Token_Retrieving_Timeout, authenticaton, errorCode, exception?.Message), exception); + } + // // SQL.DataCommand // diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index 6e145ddb30..81ad596a68 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace System { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -60,6 +60,15 @@ internal Strings() { } } + /// + /// Looks up a localized string similar to Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}. + /// + internal static string AAD_Token_Retrieving_Timeout { + get { + return ResourceManager.GetString("AAD_Token_Retrieving_Timeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to Data adapter mapping error.. /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index a2b8f976e2..15459d6059 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -4617,4 +4617,7 @@ Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index 290bed05c5..9f59d4a08a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -23,6 +23,7 @@ using Microsoft.Data.SqlClient; using Microsoft.Win32; using IsolationLevel = System.Data.IsolationLevel; +using Microsoft.Identity.Client; #if NETFRAMEWORK using Microsoft.SqlServer.Server; @@ -214,9 +215,9 @@ internal static OverflowException Overflow(string error, Exception inner) return e; } - internal static TimeoutException TimeoutException(string error) + internal static TimeoutException TimeoutException(string error, Exception inner = null) { - TimeoutException e = new(error); + TimeoutException e = new(error, inner); TraceExceptionAsReturnValue(e); return e; } @@ -416,6 +417,33 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int => Argument(StringsHelper.GetString(Strings.ADP_InvalidArgumentLength, argumentName, limit)); internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName)); + + internal static Exception CreateSqlException(MsalException msalException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username) + { + // Error[0] + SqlErrorCollection sqlErs = new(); + + sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, + connectionOptions.DataSource, + StringsHelper.GetString(Strings.SQL_MSALFailure, username, connectionOptions.Authentication.ToString("G")), + ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + + // Error[1] + string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode); + sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, + connectionOptions.DataSource, errorMessage1, + ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + + // Error[2] + if (!string.IsNullOrEmpty(msalException.Message)) + { + sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, + connectionOptions.DataSource, msalException.Message, + ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + } + return SqlException.CreateException(sqlErs, "", sender); + } + #endregion #region CommandBuilder, Command, BulkCopy From 2b364f478cb544d2367a61e616bae255d85bc60d Mon Sep 17 00:00:00 2001 From: Wraith Date: Tue, 7 Jun 2022 19:49:38 +0100 Subject: [PATCH 15/35] fix naming, order and formatting for diagnostics (#1637) --- .../Microsoft/Data/SqlClient/SqlCommand.cs | 206 +++++++++--------- 1 file changed, 108 insertions(+), 98 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index 66bacefd98..6c8140000e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -114,7 +114,7 @@ protected override void AfterCleared(SqlCommand owner) private static bool _forceInternalEndQuery = false; #endif - private static readonly SqlDiagnosticListener _diagnosticListener = new SqlDiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); + private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); private bool _parentOperationStarted = false; internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; @@ -554,7 +554,7 @@ internal SqlStatistics Statistics if (null != _activeConnection) { if (_activeConnection.StatisticsEnabled || - _diagnosticListener.IsEnabled(SqlClientDiagnosticListenerExtensions.SqlAfterExecuteCommand)) + s_diagnosticListener.IsEnabled(SqlClientDiagnosticListenerExtensions.SqlAfterExecuteCommand)) { return _activeConnection.Statistics; } @@ -1082,7 +1082,7 @@ public override object ExecuteScalar() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - using (DiagnosticScope diagnosticScope = _diagnosticListener.CreateCommandScope(this, _transaction)) + using (DiagnosticScope diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction)) using (TryEventScope.Create("SqlCommand.ExecuteScalar | API | ObjectId {0}", ObjectID)) { SqlStatistics statistics = null; @@ -1162,7 +1162,7 @@ public override int ExecuteNonQuery() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - using (var diagnosticScope = _diagnosticListener.CreateCommandScope(this, _transaction)) + using (var diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction)) using (TryEventScope.Create("SqlCommand.ExecuteNonQuery | API | Object Id {0}", ObjectID)) { SqlStatistics statistics = null; @@ -1670,7 +1670,7 @@ public XmlReader ExecuteXmlReader() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - using (DiagnosticScope diagnosticScope = _diagnosticListener.CreateCommandScope(this, _transaction)) + using (DiagnosticScope diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction)) using (TryEventScope.Create("SqlCommand.ExecuteXmlReader | API | Object Id {0}", ObjectID)) { SqlStatistics statistics = null; @@ -2002,7 +2002,7 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) bool success = false; int? sqlExceptionNumber = null; Exception e = null; - Guid operationId = _diagnosticListener.WriteCommandBefore(this, _transaction); + Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); using (TryEventScope.Create("SqlCommand.ExecuteReader | API | Object Id {0}", ObjectID)) { @@ -2030,11 +2030,11 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous: true); if (e != null) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); } else { - _diagnosticListener.WriteCommandAfter(operationId, this, _transaction); + s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); } } } @@ -2129,12 +2129,16 @@ private void CleanupExecuteReaderAsync(Task task, TaskCompletionS Exception e = task.Exception.InnerException; if (!_parentOperationStarted) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); } source.SetException(e); } else { + if (!_parentOperationStarted) + { + s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); + } if (task.IsCanceled) { source.SetCanceled(); @@ -2143,10 +2147,6 @@ private void CleanupExecuteReaderAsync(Task task, TaskCompletionS { source.SetResult(task.Result); } - if (!_parentOperationStarted) - { - _diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - } } } @@ -2524,7 +2524,7 @@ private Task InternalExecuteNonQueryWithRetryAsync(CancellationToken cancel private Task InternalExecuteNonQueryAsync(CancellationToken cancellationToken) { SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.InternalExecuteNonQueryAsync | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}, Command Text '{3}'", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId, CommandText); - Guid operationId = _diagnosticListener.WriteCommandBefore(this, _transaction); + Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); TaskCompletionSource source = new TaskCompletionSource(); @@ -2544,32 +2544,35 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok { returnedTask = RegisterForConnectionCloseNotification(returnedTask); - Task.Factory.FromAsync(BeginExecuteNonQueryAsync, EndExecuteNonQueryAsync, null).ContinueWith((t) => - { - registration.Dispose(); - if (t.IsFaulted) - { - Exception e = t.Exception.InnerException; - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - source.SetException(e); - } - else + Task.Factory.FromAsync(BeginExecuteNonQueryAsync, EndExecuteNonQueryAsync, null) + .ContinueWith((Task task) => { - if (t.IsCanceled) + registration.Dispose(); + if (task.IsFaulted) { - source.SetCanceled(); + Exception e = task.Exception.InnerException; + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + source.SetException(e); } else { - source.SetResult(t.Result); + s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); + if (task.IsCanceled) + { + source.SetCanceled(); + } + else + { + source.SetResult(task.Result); + } } - _diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - } - }, TaskScheduler.Default); + }, + TaskScheduler.Default + ); } catch (Exception e) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); source.SetException(e); } @@ -2628,7 +2631,7 @@ private Task InternalExecuteReaderAsync(CommandBehavior behavior, Guid operationId = default(Guid); if (!_parentOperationStarted) { - operationId = _diagnosticListener.WriteCommandBefore(this, _transaction); + operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); } TaskCompletionSource source = new TaskCompletionSource(); @@ -2673,7 +2676,7 @@ private Task InternalExecuteReaderAsync(CommandBehavior behavior, { if (!_parentOperationStarted) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); } source.SetException(e); @@ -2700,7 +2703,7 @@ private Task InternalExecuteScalarAsync(CancellationToken cancellationTo SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.InternalExecuteScalarAsync | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}, Command Text '{3}'", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId, CommandText); SqlClientEventSource.Log.TryTraceEvent("SqlCommand.InternalExecuteScalarAsync | API> {0}, Client Connection Id {1}, Command Text = '{2}'", ObjectID, Connection?.ClientConnectionId, CommandText); _parentOperationStarted = true; - Guid operationId = _diagnosticListener.WriteCommandBefore(this, _transaction); + Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); return ExecuteReaderAsync(cancellationToken).ContinueWith((executeTask) => { @@ -2711,68 +2714,71 @@ private Task InternalExecuteScalarAsync(CancellationToken cancellationTo } else if (executeTask.IsFaulted) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, executeTask.Exception.InnerException); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, executeTask.Exception.InnerException); source.SetException(executeTask.Exception.InnerException); } else { SqlDataReader reader = executeTask.Result; - reader.ReadAsync(cancellationToken).ContinueWith((readTask) => - { - try + reader.ReadAsync(cancellationToken) + .ContinueWith((Task readTask) => { - if (readTask.IsCanceled) - { - reader.Dispose(); - source.SetCanceled(); - } - else if (readTask.IsFaulted) - { - reader.Dispose(); - _diagnosticListener.WriteCommandError(operationId, this, _transaction, readTask.Exception.InnerException); - source.SetException(readTask.Exception.InnerException); - } - else + try { - Exception exception = null; - object result = null; - try - { - bool more = readTask.Result; - if (more && reader.FieldCount > 0) - { - try - { - result = reader.GetValue(0); - } - catch (Exception e) - { - exception = e; - } - } - } - finally + if (readTask.IsCanceled) { reader.Dispose(); + source.SetCanceled(); } - if (exception != null) + else if (readTask.IsFaulted) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, exception); - source.SetException(exception); + reader.Dispose(); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, readTask.Exception.InnerException); + source.SetException(readTask.Exception.InnerException); } else { - _diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - source.SetResult(result); + Exception exception = null; + object result = null; + try + { + bool more = readTask.Result; + if (more && reader.FieldCount > 0) + { + try + { + result = reader.GetValue(0); + } + catch (Exception e) + { + exception = e; + } + } + } + finally + { + reader.Dispose(); + } + if (exception != null) + { + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, exception); + source.SetException(exception); + } + else + { + s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); + source.SetResult(result); + } } } - } - catch (Exception e) - { - // exception thrown by Dispose... - source.SetException(e); - } - }, TaskScheduler.Default); + catch (Exception e) + { + // exception thrown by Dispose... + source.SetException(e); + } + }, + TaskScheduler.Default + ); } _parentOperationStarted = false; return source.Task; @@ -2797,7 +2803,7 @@ private Task InternalExecuteXmlReaderWithRetryAsync(CancellationToken private Task InternalExecuteXmlReaderAsync(CancellationToken cancellationToken) { SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.InternalExecuteXmlReaderAsync | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}, Command Text '{3}'", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId, CommandText); - Guid operationId = _diagnosticListener.WriteCommandBefore(this, _transaction); + Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); TaskCompletionSource source = new TaskCompletionSource(); @@ -2817,32 +2823,36 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella { returnedTask = RegisterForConnectionCloseNotification(returnedTask); - Task.Factory.FromAsync(BeginExecuteXmlReaderAsync, EndExecuteXmlReaderAsync, null).ContinueWith((t) => - { - registration.Dispose(); - if (t.IsFaulted) + Task.Factory.FromAsync(BeginExecuteXmlReaderAsync, EndExecuteXmlReaderAsync, null) + .ContinueWith((Task task) => { - Exception e = t.Exception.InnerException; - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - source.SetException(e); - } - else - { - if (t.IsCanceled) + registration.Dispose(); + if (task.IsFaulted) { - source.SetCanceled(); + Exception e = task.Exception.InnerException; + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + source.SetException(e); } else { - source.SetResult(t.Result); + s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); + if (task.IsCanceled) + { + source.SetCanceled(); + } + else + { + source.SetResult(task.Result); + } + } - _diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - } - }, TaskScheduler.Default); + }, + TaskScheduler.Default + ); } catch (Exception e) { - _diagnosticListener.WriteCommandError(operationId, this, _transaction, e); + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); source.SetException(e); } From 155a535609f5d200389a57d2c9e493541070cbd0 Mon Sep 17 00:00:00 2001 From: Wraith Date: Tue, 7 Jun 2022 19:52:57 +0100 Subject: [PATCH 16/35] Rework `TdsParserStateObjectManaged` with nullable annotations (#1555) --- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../SqlClient/TdsParserStateObjectManaged.cs | 199 +++++++++++------- .../netcore/src/Resources/Strings.Designer.cs | 9 + .../netcore/src/Resources/Strings.resx | 3 + .../src/Microsoft/Data/Common/AdapterUtil.cs | 3 + .../src/System/Diagnostics/CodeAnalysis.cs | 96 +++++++++ 6 files changed, 238 insertions(+), 75 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs 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 cffb127ec7..bd8a798f88 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -505,6 +505,9 @@ Resources\StringsHelper.cs + + Common\System\Diagnostics\CodeAnalysis.cs + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs index 4c3ad107b4..e69e8c3fab 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -2,18 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient.SNI { - internal class TdsParserStateObjectManaged : TdsParserStateObject + internal sealed class TdsParserStateObjectManaged : TdsParserStateObject { - private SNIMarsConnection _marsConnection; - private SNIHandle _sessionHandle; - private SspiClientContextStatus _sspiClientContextStatus; + private SNIMarsConnection? _marsConnection; + private SNIHandle? _sessionHandle; + private SspiClientContextStatus? _sspiClientContextStatus; public TdsParserStateObjectManaged(TdsParser parser) : base(parser) { } @@ -21,8 +26,6 @@ internal TdsParserStateObjectManaged(TdsParser parser, TdsParserStateObject phys base(parser, physicalConnection, async) { } - internal SNIHandle Handle => _sessionHandle; - internal override uint Status => _sessionHandle != null ? _sessionHandle.Status : TdsEnums.SNI_UNINITIALIZED; internal override SessionHandle SessionHandle => SessionHandle.FromManagedSession(_sessionHandle); @@ -36,14 +39,24 @@ protected override bool CheckPacket(PacketHandle packet, TaskCompletionSource _sessionHandle.Status != TdsEnums.SNI_SUCCESS; - - internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) + internal override bool IsFailedHandle() { - SNIHandle handle = Handle; - if (handle == null) + SNIHandle? sessionHandle = _sessionHandle; + if (sessionHandle is not null) { - throw ADP.ClosedConnectionError(); + return sessionHandle.Status != TdsEnums.SNI_SUCCESS; } + return true; + } + - error = handle.Receive(out SNIPacket packet, timeoutRemaining); + internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) + { + SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + + error = sessionHandle.Receive(out SNIPacket packet, timeoutRemaining); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | Info | State Object Id {0}, Session Id {1}", _objectID, _sessionHandle?.ConnectionId); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | Info | State Object Id {0}, Session Id {1}", _objectID, sessionHandle.ConnectionId); #if DEBUG - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | TRC | State Object Id {0}, Session Id {1}, Packet {2} received, Packet owner Id {3}, Packet dataLeft {4}", _objectID, _sessionHandle?.ConnectionId, packet?._id, packet?._owner.ConnectionId, packet?.DataLeft); + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | TRC | State Object Id {0}, Session Id {1}, Packet {2} received, Packet owner Id {3}, Packet dataLeft {4}", _objectID, sessionHandle.ConnectionId, packet?._id, packet?._owner.ConnectionId, packet?.DataLeft); #endif return PacketHandle.FromManagedPacket(packet); } @@ -195,22 +219,31 @@ internal override void ReleasePacket(PacketHandle syncReadPacket) #if DEBUG SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObjectManaged.ReleasePacket | TRC | State Object Id {0}, Session Id {1}, Packet {2} will be released, Packet Owner Id {3}, Packet dataLeft {4}", _objectID, _sessionHandle?.ConnectionId, packet?._id, packet?._owner.ConnectionId, packet?.DataLeft); #endif - if (packet != null) + if (packet is not null) { - SNIHandle handle = Handle; - handle.ReturnPacket(packet); + SNIHandle? sessionHandle = _sessionHandle; + if (sessionHandle is not null) + { + sessionHandle.ReturnPacket(packet); + } + else + { + // clear the packet and drop it to GC because we no longer know how to return it to the correct owner + // this can only happen if a packet is in-flight when the _sessionHandle is cleared + packet.Release(); + } } } internal override uint CheckConnection() { - SNIHandle handle = Handle; - return handle == null ? TdsEnums.SNI_SUCCESS : handle.CheckConnection(); + SNIHandle? handle = GetSessionSNIHandleHandleOrThrow(); + return handle is null ? TdsEnums.SNI_SUCCESS : handle.CheckConnection(); } internal override PacketHandle ReadAsync(SessionHandle handle, out uint error) { - SNIPacket packet = null; + SNIPacket? packet = null; error = handle.ManagedHandle.ReceiveAsync(ref packet); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReadAsync | Info | State Object Id {0}, Session Id {1}, Packet DataLeft {2}", _objectID, _sessionHandle?.ConnectionId, packet?.DataLeft); @@ -232,20 +265,21 @@ internal override PacketHandle CreateAndSetAttentionPacket() internal override uint WritePacket(PacketHandle packetHandle, bool sync) { - uint result; - SNIHandle handle = Handle; - SNIPacket packet = packetHandle.ManagedPacket; + uint result = TdsEnums.SNI_UNINITIALIZED; + SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SNIPacket? packet = packetHandle.ManagedPacket; + if (sync) { - result = handle.Send(packet); - handle.ReturnPacket(packet); + result = sessionHandle.Send(packet); + sessionHandle.ReturnPacket(packet); } else { - result = handle.SendAsync(packet); + result = sessionHandle.SendAsync(packet); } - - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.WritePacket | Info | Session Id {0}, SendAsync Result {1}", handle?.ConnectionId, result); + + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.WritePacket | Info | Session Id {0}, SendAsync Result {1}", sessionHandle.ConnectionId, result); return result; } @@ -264,12 +298,12 @@ internal override bool IsValidPacket(PacketHandle packet) internal override PacketHandle GetResetWritePacket(int dataSize) { - SNIHandle handle = Handle; - SNIPacket packet = handle.RentPacket(headerSize: handle.ReserveHeaderSize, dataSize: dataSize); + SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SNIPacket packet = sessionHandle.RentPacket(headerSize: sessionHandle.ReserveHeaderSize, dataSize: dataSize); #if DEBUG Debug.Assert(packet.IsActive, "packet is not active, a serious pooling error may have occurred"); #endif - Debug.Assert(packet.ReservedHeaderSize == handle.ReserveHeaderSize, "failed to reserve header"); + Debug.Assert(packet.ReservedHeaderSize == sessionHandle.ReserveHeaderSize, "failed to reserve header"); return PacketHandle.FromManagedPacket(packet); } @@ -285,23 +319,24 @@ internal override void SetPacketData(PacketHandle packet, byte[] buffer, int byt internal override uint SniGetConnectionId(ref Guid clientConnectionId) { - clientConnectionId = Handle.ConnectionId; + clientConnectionId = GetSessionSNIHandleHandleOrThrow().ConnectionId; SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.GetConnectionId | Info | Session Id {0}", clientConnectionId); return TdsEnums.SNI_SUCCESS; } internal override uint DisableSsl() { - SNIHandle handle = Handle; - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.DisableSsl | Info | Session Id {0}", handle?.ConnectionId); - handle.DisableSsl(); + SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.DisableSsl | Info | Session Id {0}", sessionHandle.ConnectionId); + sessionHandle.DisableSsl(); return TdsEnums.SNI_SUCCESS; } internal override uint EnableMars(ref uint info) { - _marsConnection = new SNIMarsConnection(Handle); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableMars | Info | State Object Id {0}, Session Id {1}", _objectID, _sessionHandle?.ConnectionId); + SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + _marsConnection = new SNIMarsConnection(sessionHandle); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableMars | Info | State Object Id {0}, Session Id {1}", _objectID, sessionHandle.ConnectionId); if (_marsConnection.StartReceive() == TdsEnums.SNI_SUCCESS_IO_PENDING) { @@ -313,28 +348,28 @@ internal override uint EnableMars(ref uint info) internal override uint EnableSsl(ref uint info) { - SNIHandle handle = Handle; + SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); try { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableSsl | Info | Session Id {0}", handle?.ConnectionId); - return handle.EnableSsl(info); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableSsl | Info | Session Id {0}", sessionHandle.ConnectionId); + return sessionHandle.EnableSsl(info); } catch (Exception e) { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableSsl | Err | Session Id {0}, SNI Handshake failed with exception: {1}", handle?.ConnectionId, e?.Message); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableSsl | Err | Session Id {0}, SNI Handshake failed with exception: {1}", sessionHandle.ConnectionId, e.Message); return SNICommon.ReportSNIError(SNIProviders.SSL_PROV, SNICommon.HandshakeFailureError, e); } } internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) { - Handle.SetBufferSize((int)unsignedPacketSize); + GetSessionSNIHandleHandleOrThrow().SetBufferSize((int)unsignedPacketSize); return TdsEnums.SNI_SUCCESS; } internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) { - if (_sspiClientContextStatus == null) + if (_sspiClientContextStatus is null) { _sspiClientContextStatus = new SspiClientContextStatus(); } @@ -347,8 +382,22 @@ internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint recei internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) { - protocolVersion = Handle.ProtocolVersion; + protocolVersion = GetSessionSNIHandleHandleOrThrow().ProtocolVersion; return 0; } + + private SNIHandle GetSessionSNIHandleHandleOrThrow() + { + SNIHandle? sessionHandle = _sessionHandle; + if (sessionHandle is null) + { + ThrowClosedConnection(); + } + return sessionHandle; + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] // this forces the exception throwing code not to be inlined for performance + private void ThrowClosedConnection() => throw ADP.ClosedConnectionError(); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs index 9f3d2fec91..7a7ff4a367 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -1914,6 +1914,15 @@ internal static string SNI_ERROR_9 { } } + /// + /// Looks up a localized string similar to Incorrect physicalConnection type. + /// + internal static string SNI_IncorrectPhysicalConnectionType { + get { + return ResourceManager.GetString("SNI_IncorrectPhysicalConnectionType", resourceCulture); + } + } + /// /// Looks up a localized string similar to HTTP Provider. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx index 433c6a7684..a77dd0aef1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx @@ -1935,4 +1935,7 @@ Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + + Incorrect physicalConnection type. + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index 9f59d4a08a..e6b23a5c68 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1565,6 +1565,9 @@ internal static ArgumentOutOfRangeException InvalidIsolationLevel(IsolationLevel return InvalidEnumerationValue(typeof(IsolationLevel), (int)value); } + // ConnectionUtil + internal static Exception IncorrectPhysicalConnectionType() => new ArgumentException(StringsHelper.GetString(StringsHelper.SNI_IncorrectPhysicalConnectionType)); + // IDataParameter.Direction internal static ArgumentOutOfRangeException InvalidParameterDirection(ParameterDirection value) { diff --git a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs new file mode 100644 index 0000000000..256f7cd1e0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Diagnostics.CodeAnalysis +{ +#if NETSTANDARD2_0 + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)] + internal sealed class AllowNullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)] + internal sealed class DisallowNullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class DoesNotReturnAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + public bool ParameterValue { get; } + } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] + internal sealed class MaybeNullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + public bool ReturnValue { get; } + } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] + internal sealed class NotNullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + public string ParameterName { get; } + } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + public bool ReturnValue { get; } + } +#endif + +#if !NET5_0_OR_GREATER + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = false)] + internal sealed class MemberNotNullAttribute : Attribute + { + public MemberNotNullAttribute(string member) => Members = new string[] + { + member + }; + + public MemberNotNullAttribute(params string[] members) => Members = members; + + public string[] Members { get; } + } + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = false)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new string[1] { member }; + } + + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + public bool ReturnValue { get; } + + public string[] Members { get; } + } +#endif +} From 51f46a4eb66d08de0b1442399c05f5914378444e Mon Sep 17 00:00:00 2001 From: SqlClient DevOps Date: Wed, 8 Jun 2022 03:09:02 +0000 Subject: [PATCH 17/35] [Scheduled Run] Localized resource files from OneLocBuild --- .../netfx/src/Resources/Strings.de.resx | 3 +++ .../netfx/src/Resources/Strings.es.resx | 3 +++ .../netfx/src/Resources/Strings.fr.resx | 3 +++ .../netfx/src/Resources/Strings.it.resx | 3 +++ .../netfx/src/Resources/Strings.ja.resx | 3 +++ .../netfx/src/Resources/Strings.ko.resx | 3 +++ .../netfx/src/Resources/Strings.pt-BR.resx | 3 +++ .../netfx/src/Resources/Strings.ru.resx | 3 +++ .../netfx/src/Resources/Strings.zh-Hans.resx | 3 +++ .../netfx/src/Resources/Strings.zh-Hant.resx | 3 +++ 10 files changed, 30 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index a2250fb0fa..34cd269e19 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -4617,4 +4617,7 @@ Der Parameter "{0}" kann keine Ausgaberichtung oder InputOutput aufweisen, wenn EnableOptimizedParameterBinding für den übergeordneten Befehl aktiviert ist. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index b10f09920b..3b89fa8953 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -4617,4 +4617,7 @@ El parámetro “{0}” no puede tener la Dirección de salida ni InputOutput cuando EnableOptimizedParameterBinding está habilitado en el comando primario. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index da42dc4c9c..642d36e58e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -4617,4 +4617,7 @@ Le paramètre « {0} » ne peut pas avoir Direction Output ou InputOutput lorsque EnableOptimizedParameterBinding est activé sur la commande parente. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index 768da0ccf6..c098da5ac4 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -4617,4 +4617,7 @@ Il parametro '{0}' non può includere Output o InputOutput come valore di Direction quando nel comando padre è abilitato EnableOptimizedParameterBinding. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index 699b91b04e..20490f2dfa 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -4617,4 +4617,7 @@ 親コマンドで EnableOptimizedParameterBinding が有効になっている場合、パラメーター '{0}' に Direction 出力または InputOutput は指定できません。 + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index dba638f495..d15e7470a1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -4617,4 +4617,7 @@ 부모 명령에서 EnableOptimizedParameterBinding을 사용하는 경우 매개 변수 '{0}'에는 Direction Output 또는 InputOutput을 사용할 수 없습니다. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index 63ef4ed0a4..4312c5e1ab 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -4617,4 +4617,7 @@ O parâmetro '{0}' não pode ter a saída de direção ou InputOutput quando EnableOptimizedParameterBinding está habilitado no comando pai. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index ad2bca5bae..fa44bce13f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -4617,4 +4617,7 @@ У параметра "{0}" не может быть направления вывода или InputOutput, если EnableOptimizedParameterBinding включен в родительской команде. + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index c33748761f..10411eaae7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -4617,4 +4617,7 @@ 当在父命令上启用 EnableOptimizedParameterBinding 时,参数“{0}”不能具有 Direction Output 或 InputOutput。 + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index c117ed18f5..203de4b3b8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -4617,4 +4617,7 @@ 在父命令上啟用 EnableOptimizedParameterBinding 時,參數 '{0}' 不能具有方向輸出或 InputOutput。 + + Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + \ No newline at end of file From c45c50776608bf88523d6d378234f050c0cc0058 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Thu, 9 Jun 2022 10:51:40 -0700 Subject: [PATCH 18/35] Fix | Add StructuredTypeMembers in ref files (#1639) --- .../netcore/ref/Microsoft.Data.SqlClient.cs | 2 ++ .../netfx/ref/Microsoft.Data.SqlClient.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 12e8094ff0..1ad71cac75 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -470,6 +470,8 @@ public static partial class SqlClientMetaDataCollectionNames public static readonly string AllColumns; /// public static readonly string ColumnSetColumns; + /// + public static readonly string StructuredTypeMembers; } #if NETCOREAPP || NETSTANDARD21_AND_ABOVE /// diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index d5ac6b0611..360d1c9c41 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -433,6 +433,8 @@ public static partial class SqlClientMetaDataCollectionNames public static readonly string AllColumns; /// public static readonly string ColumnSetColumns; + /// + public static readonly string StructuredTypeMembers; } /// public sealed partial class SqlClientPermission : System.Data.Common.DBDataPermission From 3082ae55013425dc09604a1960fb48ebd8f844fb Mon Sep 17 00:00:00 2001 From: David Engel Date: Thu, 9 Jun 2022 13:17:38 -0700 Subject: [PATCH 19/35] Add support for aliases on netcore+Windows (#1588) * Add support for aliases on netcore+Windows * Add test * Revert spacing * Better partition Windows/Unix code * Add missing files * Fix attempt * Re-work new test * Applying review suggestions * Deduplicate code Co-authored-by: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> --- .../src/Microsoft.Data.SqlClient.csproj | 12 +- .../SqlClient/SqlInternalConnectionTds.cs | 35 ++++- .../netfx/src/Microsoft.Data.SqlClient.csproj | 7 +- .../Microsoft/Data/Common/AdapterUtil.Unix.cs | 24 ++++ .../Data/Common/AdapterUtil.Windows.cs | 49 +++++++ .../src/Microsoft/Data/Common/AdapterUtil.cs | 27 +--- .../Data/SqlClient/SqlConnectionString.cs | 29 +++-- .../Data/SqlClient/TdsParserStaticMethods.cs | 121 ++++++++++++------ .../ManualTests/DataCommon/DataTestUtility.cs | 41 +++++- .../SQL/ConnectivityTests/ConnectivityTest.cs | 74 +++++++++++ .../SQL/InstanceNameTest/InstanceNameTest.cs | 36 +----- 11 files changed, 330 insertions(+), 125 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs rename src/Microsoft.Data.SqlClient/{netfx => }/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs (67%) 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 bd8a798f88..e0063743c4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -172,7 +172,7 @@ Microsoft\Data\SqlClient\RowsCopiedEventHandler.cs - + Microsoft\Data\SqlClient\SqlSequentialTextReader.cs @@ -481,6 +481,9 @@ Microsoft\Data\SqlClient\TdsParameterSetter.cs + + Microsoft\Data\SqlClient\TdsParserStaticMethods.cs + Microsoft\Data\SqlClient\TdsRecordBufferSetter.cs @@ -656,10 +659,12 @@ - + + Microsoft\Data\Common\AdapterUtil.Windows.cs + Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs @@ -672,6 +677,9 @@ + + Microsoft\Data\Common\AdapterUtil.Unix.cs + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 6e25b56516..ddd7ebdd16 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1856,11 +1856,36 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, string host = serverInfo.UserServerName; string protocol = serverInfo.UserProtocol; - //TODO: fix local host enforcement with datadirectory and failover - if (options.EnforceLocalHost) - { - // verify LocalHost for |DataDirectory| usage - SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/); + if (aliasLookup) + { // We skip this for UserInstances... + // Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias. + // Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from + // _reconnectSessonData of the previous connection + if (_currentSessionData != null && !string.IsNullOrEmpty(host)) + { + Tuple hostPortPair; + if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair)) + { + host = hostPortPair.Item1; + protocol = hostPortPair.Item2; + } + else + { + TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol); + _currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple(host, protocol)); + } + } + else + { + TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol); + } + + //TODO: fix local host enforcement with datadirectory and failover + if (options.EnforceLocalHost) + { + // verify LocalHost for |DataDirectory| usage + SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/); + } } serverInfo.SetDerivedNames(protocol, host); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index ab43046cb3..6a9738e612 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -95,6 +95,9 @@ Microsoft\Data\Common\AdapterUtil.cs + + Microsoft\Data\Common\AdapterUtil.Windows.cs + Microsoft\Data\Common\DbConnectionStringCommon.cs @@ -545,6 +548,9 @@ Microsoft\Data\SqlClient\TdsParameterSetter.cs + + Microsoft\Data\SqlClient\TdsParserStaticMethods.cs + Microsoft\Data\SqlClient\TdsRecordBufferSetter.cs @@ -639,7 +645,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs new file mode 100644 index 0000000000..8b84feecfc --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.Common +{ + /// + /// The class ADP defines the exceptions that are specific to the Adapters. + /// The class contains functions that take the proper informational variables and then construct + /// the appropriate exception with an error string obtained from the resource framework. + /// The exception is then returned to the caller, so that the caller may then throw from its + /// location so that the catcher of the exception will have the appropriate call stack. + /// This class is used so that there will be compile time checking of error messages. + /// The resource Framework.txt will ensure proper string text based on the appropriate locale. + /// + internal static partial class ADP + { + internal static object LocalMachineRegistryValue(string subkey, string queryvalue) + { + // No registry in non-Windows environments + return null; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs new file mode 100644 index 0000000000..c9d0f8d91a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security; +using System.Security.Permissions; +using Microsoft.Win32; + +namespace Microsoft.Data.Common +{ + /// + /// The class ADP defines the exceptions that are specific to the Adapters. + /// The class contains functions that take the proper informational variables and then construct + /// the appropriate exception with an error string obtained from the resource framework. + /// The exception is then returned to the caller, so that the caller may then throw from its + /// location so that the catcher of the exception will have the appropriate call stack. + /// This class is used so that there will be compile time checking of error messages. + /// The resource Framework.txt will ensure proper string text based on the appropriate locale. + /// + internal static partial class ADP + { + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + internal static object LocalMachineRegistryValue(string subkey, string queryvalue) + { // MDAC 77697 + (new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_LOCAL_MACHINE\\" + subkey)).Assert(); // MDAC 62028 + try + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(subkey, false)) + { + return key?.GetValue(queryvalue); + } + } + catch (SecurityException e) + { + // Even though we assert permission - it's possible there are + // ACL's on registry that cause SecurityException to be thrown. + ADP.TraceExceptionWithoutRethrow(e); + return null; + } + finally + { + RegistryPermission.RevertAssert(); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index e6b23a5c68..c5ae9853df 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -43,7 +43,7 @@ namespace Microsoft.Data.Common /// This class is used so that there will be compile time checking of error messages. /// The resource Framework.txt will ensure proper string text based on the appropriate locale. /// - internal static class ADP + internal static partial class ADP { // NOTE: Initializing a Task in SQL CLR requires the "UNSAFE" permission set (http://msdn.microsoft.com/en-us/library/ms172338.aspx) // Therefore we are lazily initializing these Tasks to avoid forcing customers to use the "UNSAFE" set when they are actually using no Async features @@ -1478,31 +1478,6 @@ internal static string GetComputerNameDnsFullyQualified() return value; } - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] - internal static object LocalMachineRegistryValue(string subkey, string queryvalue) - { // MDAC 77697 - (new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_LOCAL_MACHINE\\" + subkey)).Assert(); // MDAC 62028 - try - { - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(subkey, false)) - { - return key?.GetValue(queryvalue); - } - } - catch (SecurityException e) - { - // Even though we assert permission - it's possible there are - // ACL's on registry that cause SecurityException to be thrown. - ADP.TraceExceptionWithoutRethrow(e); - return null; - } - finally - { - RegistryPermission.RevertAssert(); - } - } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] internal static IntPtr IntPtrOffset(IntPtr pbase, int offset) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index f1a488c4b6..aac7c04913 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -1102,20 +1102,6 @@ internal PoolBlockingPeriod ConvertValueToPoolBlockingPeriod() } } -#if NETFRAMEWORK - protected internal override PermissionSet CreatePermissionSet() - { - PermissionSet permissionSet = new(PermissionState.None); - permissionSet.AddPermission(new SqlClientPermission(this)); - return permissionSet; - } - - internal bool ConvertValueToEncrypt() - { - bool defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : true; - return ConvertValueToBoolean(KEY.Encrypt, defaultEncryptValue); - } - static internal Hashtable NetlibMapping() { const int NetLibCount = 8; @@ -1166,6 +1152,21 @@ internal static class NETLIB } private static Hashtable s_netlibMapping; + +#if NETFRAMEWORK + protected internal override PermissionSet CreatePermissionSet() + { + PermissionSet permissionSet = new(PermissionState.None); + permissionSet.AddPermission(new SqlClientPermission(this)); + return permissionSet; + } + + internal bool ConvertValueToEncrypt() + { + bool defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : true; + return ConvertValueToBoolean(KEY.Encrypt, defaultEncryptValue); + } + private readonly bool _connectionReset; private readonly bool _contextConnection; private readonly bool _transparentNetworkIPResolution; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs similarity index 67% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs index 7be7f61bb4..93aac3fdea 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -3,10 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using Microsoft.Data.Common; using System.Globalization; +using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Security.Permissions; +using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { @@ -88,12 +88,19 @@ static internal void AliasRegistryLookup(ref string host, ref string protocol) } } - // Encrypt password to be sent to SQL Server + // Obfuscate password to be sent to SQL Server + // Blurb from the TDS spec at https://msdn.microsoft.com/en-us/library/dd304523.aspx + // "Before submitting a password from the client to the server, for every byte in the password buffer + // starting with the position pointed to by IbPassword, the client SHOULD first swap the four high bits + // with the four low bits and then do a bit-XOR with 0xA5 (10100101). After reading a submitted password, + // for every byte in the password buffer starting with the position pointed to by IbPassword, the server SHOULD + // first do a bit-XOR with 0xA5 (10100101) and then swap the four high bits with the four low bits." + // The password exchange during Login phase happens over a secure channel i.e. SSL/TLS // Note: The same logic is used in SNIPacketSetData (SniManagedWrapper) to encrypt passwords stored in SecureString // If this logic changed, SNIPacketSetData needs to be changed as well internal static byte[] ObfuscatePassword(string password) { - byte[] bEnc = new byte[password.Length << 1]; + byte[] bObfuscated = new byte[password.Length << 1]; int s; byte bLo; byte bHi; @@ -103,62 +110,92 @@ internal static byte[] ObfuscatePassword(string password) s = (int)password[i]; bLo = (byte)(s & 0xff); bHi = (byte)((s >> 8) & 0xff); - bEnc[i << 1] = (byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5); - bEnc[(i << 1) + 1] = (byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5); + bObfuscated[i << 1] = (byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5); + bObfuscated[(i << 1) + 1] = (byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5); } - return bEnc; + return bObfuscated; } - [ResourceExposure(ResourceScope.None)] // SxS: we use this method for TDS login only - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - static internal int GetCurrentProcessIdForTdsLoginOnly() + internal static byte[] ObfuscatePassword(byte[] password) { - return Common.SafeNativeMethods.GetCurrentProcessId(); + byte bLo; + byte bHi; + + for (int i = 0; i < password.Length; i++) + { + bLo = (byte)(password[i] & 0x0f); + bHi = (byte)(password[i] & 0xf0); + password[i] = (byte)(((bHi >> 4) | (bLo << 4)) ^ 0xa5); + } + return password; + } + + private const int NoProcessId = -1; + private static int s_currentProcessId = NoProcessId; + internal static int GetCurrentProcessIdForTdsLoginOnly() + { + if (s_currentProcessId == NoProcessId) + { + // Pick up the process Id from the current process instead of randomly generating it. + // This would be helpful while tracing application related issues. + int processId; + using (System.Diagnostics.Process p = System.Diagnostics.Process.GetCurrentProcess()) + { + processId = p.Id; + } + System.Threading.Volatile.Write(ref s_currentProcessId, processId); + } + return s_currentProcessId; } - [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)] - [ResourceExposure(ResourceScope.None)] // SxS: we use this method for TDS login only - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - static internal Int32 GetCurrentThreadIdForTdsLoginOnly() + internal static int GetCurrentThreadIdForTdsLoginOnly() { -#pragma warning disable 618 - return AppDomain.GetCurrentThreadId(); // don't need this to be support fibres; -#pragma warning restore 618 + return Environment.CurrentManagedThreadId; } + private static byte[] s_nicAddress = null; [ResourceExposure(ResourceScope.None)] // SxS: we use MAC address for TDS login only [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly() { - // NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key - // has a value that is not zero, then we cannot use the NetworkAddress key and must - // instead generate a random one. I do not fully understand why, this is simply what - // the native providers do. As for generation, I use a random number generator, which - // means that different processes on the same machine will have different NIC address - // values on the server. It is not ideal, but native does not have the same value for - // different processes either. - - const string key = "NetworkAddress"; - const string localKey = "NetworkAddressLocal"; - const string folder = "SOFTWARE\\Description\\Microsoft\\Rpc\\UuidTemporaryData"; - - int result = 0; - byte[] nicAddress = null; - - object temp = ADP.LocalMachineRegistryValue(folder, localKey); - if (temp is int) + if (s_nicAddress != null) { - result = (int)temp; + return s_nicAddress; } - if (result <= 0) + byte[] nicAddress = null; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - temp = ADP.LocalMachineRegistryValue(folder, key); - if (temp is byte[]) + // NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key + // has a value that is not zero, then we cannot use the NetworkAddress key and must + // instead generate a random one. I do not fully understand why, this is simply what + // the native providers do. As for generation, I use a random number generator, which + // means that different processes on the same machine will have different NIC address + // values on the server. It is not ideal, but native does not have the same value for + // different processes either. + + const string key = "NetworkAddress"; + const string localKey = "NetworkAddressLocal"; + const string folder = "SOFTWARE\\Description\\Microsoft\\Rpc\\UuidTemporaryData"; + + int result = 0; + + object temp = ADP.LocalMachineRegistryValue(folder, localKey); + if (temp is int) { - nicAddress = (byte[])temp; + result = (int)temp; + } + + if (result <= 0) + { + temp = ADP.LocalMachineRegistryValue(folder, key); + if (temp is byte[]) + { + nicAddress = (byte[])temp; + } } } @@ -169,7 +206,9 @@ static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly() random.NextBytes(nicAddress); } - return nicAddress; + System.Threading.Interlocked.CompareExchange(ref s_nicAddress, nicAddress, null); + + return s_nicAddress; } // translates remaining time in stateObj (from user specified timeout) to timeout value for SNI diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index b6d2f73532..9e5b10cdd7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -15,8 +15,8 @@ using System.Security; using System.Threading; using System.Threading.Tasks; -using Microsoft.Identity.Client; using Microsoft.Data.SqlClient.TestUtilities; +using Microsoft.Identity.Client; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -296,6 +296,11 @@ public static bool AreConnStringsSetup() return !string.IsNullOrEmpty(NPConnectionString) && !string.IsNullOrEmpty(TCPConnectionString); } + public static bool IsTCPConnStringSetup() + { + return !string.IsNullOrEmpty(TCPConnectionString); + } + // Synapse: Always Encrypted is not supported with Azure Synapse. // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse public static bool AreConnStringSetupForAE() @@ -828,6 +833,40 @@ public static string RetrieveValueFromConnStr(string connStr, string[] keywords) return res; } + public static bool ParseDataSource(string dataSource, out string hostname, out int port, out string instanceName) + { + hostname = string.Empty; + port = -1; + instanceName = string.Empty; + + if (dataSource.Contains(",") && dataSource.Contains("\\")) + return false; + + if (dataSource.Contains(":")) + { + dataSource = dataSource.Substring(dataSource.IndexOf(":") + 1); + } + + if (dataSource.Contains(",")) + { + if (!Int32.TryParse(dataSource.Substring(dataSource.LastIndexOf(",") + 1), out port)) + { + return false; + } + dataSource = dataSource.Substring(0, dataSource.IndexOf(",") - 1); + } + + if (dataSource.Contains("\\")) + { + instanceName = dataSource.Substring(dataSource.LastIndexOf("\\") + 1); + dataSource = dataSource.Substring(0, dataSource.LastIndexOf("\\")); + } + + hostname = dataSource; + + return hostname.Length > 0 && hostname.IndexOfAny(new char[] { '\\', ':', ',' }) == -1; + } + public class AKVEventListener : BaseEventListener { public override string Name => AKVEventSourceName; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs index 41a52f48ac..32115b96d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -5,7 +5,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Principal; using System.Threading; +using Microsoft.Win32; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -368,5 +371,76 @@ public static void ConnectionOpenDisableRetry() duration = timer.Elapsed; Assert.True(duration.Seconds > 5, $"Connection Open() with retries took less time than expected. Expect > 5 sec with transient fault handling. Took {duration.Seconds} sec."); // sqlConnection.Open(); } + + private const string ConnectToPath = "SOFTWARE\\Microsoft\\MSSQLServer\\Client\\ConnectTo"; + private static bool CanCreateAliases() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || + !DataTestUtility.IsTCPConnStringSetup()) + { + return false; + } + + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + WindowsPrincipal principal = new(identity); + if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + return false; + } + } + + using RegistryKey key = Registry.LocalMachine.OpenSubKey(ConnectToPath, true); + if (key == null) + { + // Registry not writable + return false; + } + + SqlConnectionStringBuilder b = new(DataTestUtility.TCPConnectionString); + if (!DataTestUtility.ParseDataSource(b.DataSource, out string hostname, out int port, out string instanceName) || + !string.IsNullOrEmpty(instanceName)) + { + return false; + } + + return true; + } + + [PlatformSpecific(TestPlatforms.Windows)] + [ConditionalFact(nameof(CanCreateAliases))] + public static void ConnectionAliasTest() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new Exception("Alias test only valid on Windows"); + } + + if (!CanCreateAliases()) + { + throw new Exception("Unable to create aliases in this environment. Windows + Admin + non-instance data source required."); + } + + SqlConnectionStringBuilder b = new(DataTestUtility.TCPConnectionString); + if (!DataTestUtility.ParseDataSource(b.DataSource, out string hostname, out int port, out string instanceName) || + !string.IsNullOrEmpty(instanceName)) + { + // Only works with connection strings that parse successfully and don't include an instance name + throw new Exception("Unable to create aliases in this configuration. Parsable data source without instance required."); + } + + b.DataSource = "TESTALIAS-" + Guid.NewGuid().ToString().Replace("-", ""); + using RegistryKey key = Registry.LocalMachine.OpenSubKey(ConnectToPath, true); + key.SetValue(b.DataSource, "DBMSSOCN," + hostname + "," + (port == -1 ? 1433 : port)); + try + { + using SqlConnection sqlConnection = new(b.ConnectionString); + sqlConnection.Open(); + } + finally + { + key.DeleteValue(b.DataSource); + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs index 8079530796..1c1869f7f1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -47,7 +47,7 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove builder.MultiSubnetFailover = useMultiSubnetFailover; builder.IPAddressPreference = ipPreference; - Assert.True(ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName), "Invalid data source."); + Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName)); if (IsBrowserAlive(hostname) && IsValidInstance(hostname, instanceName)) { @@ -69,40 +69,6 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove } } - private static bool ParseDataSource(string dataSource, out string hostname, out int port, out string instanceName) - { - hostname = string.Empty; - port = -1; - instanceName = string.Empty; - - if (dataSource.Contains(",") && dataSource.Contains("\\")) - return false; - - if (dataSource.Contains(":")) - { - dataSource = dataSource.Substring(dataSource.IndexOf(":") + 1); - } - - if (dataSource.Contains(",")) - { - if (!int.TryParse(dataSource.Substring(dataSource.LastIndexOf(",") + 1), out port)) - { - return false; - } - dataSource = dataSource.Substring(0, dataSource.IndexOf(",") - 1); - } - - if (dataSource.Contains("\\")) - { - instanceName = dataSource.Substring(dataSource.LastIndexOf("\\") + 1); - dataSource = dataSource.Substring(0, dataSource.LastIndexOf("\\")); - } - - hostname = dataSource; - - return hostname.Length > 0 && hostname.IndexOfAny(new char[] { '\\', ':', ',' }) == -1; - } - private static bool IsBrowserAlive(string browserHostname) { const byte ClntUcastEx = 0x03; From 966fc6eab94e193aa893d960dfc8dc0dd2236029 Mon Sep 17 00:00:00 2001 From: Parminder Kaur <88398605+Kaur-Parminder@users.noreply.github.com> Date: Thu, 9 Jun 2022 15:26:07 -0700 Subject: [PATCH 20/35] Move to Shared - SqlTransaction (#1353) --- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/SqlClient/SqlTransaction.cs | 162 +-------------- .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/SqlClient/SqlTransaction.cs | 189 ++---------------- .../Data/SqlClient/SqlTransaction.Common.cs | 145 ++++++++++++++ 5 files changed, 168 insertions(+), 334 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs 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 e0063743c4..71a3b17641 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -256,6 +256,9 @@ Microsoft\Data\SqlClient\Server\SqlRecordBuffer.cs + + Microsoft\Data\SqlClient\SqlTransaction.Common.cs + Microsoft\Data\SqlClient\Server\SqlUserDefinedAggregateAttribute.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs index 2eecfdc8cc..1c3df017dd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs @@ -4,136 +4,22 @@ using System; using System.ComponentModel; -using System.Data; using System.Data.Common; -using System.Diagnostics; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { /// - public sealed class SqlTransaction : DbTransaction + public sealed partial class SqlTransaction : DbTransaction { - private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); - private static int _objectTypeCount; // EventSource Counter - internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - internal readonly IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted; - - private SqlInternalTransaction _internalTransaction; - private SqlConnection _connection; - - private bool _isFromAPI; - - internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, - IsolationLevel iso, SqlInternalTransaction internalTransaction) - { - _isolationLevel = iso; - _connection = con; - - if (internalTransaction == null) - { - _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); - } - else - { - Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); - _internalTransaction = internalTransaction; - _internalTransaction.InitParent(this); - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - // PROPERTIES - //////////////////////////////////////////////////////////////////////////////////////// - - /// - new public SqlConnection Connection - { - get - { - if (IsZombied) - { - return null; - } - else - { - return _connection; - } - } - } - - /// - override protected DbConnection DbConnection - { - get - { - return Connection; - } - } - - internal SqlInternalTransaction InternalTransaction - { - get - { - return _internalTransaction; - } - } - - /// - override public IsolationLevel IsolationLevel - { - get - { - ZombieCheck(); - return _isolationLevel; - } - } - - private bool Is2005PartialZombie - { - get - { - return (null != _internalTransaction && _internalTransaction.IsCompleted); - } - } - - internal bool IsZombied - { - get - { - return (null == _internalTransaction || _internalTransaction.IsCompleted); - } - } - - internal int ObjectID - { - get - { - return _objectID; - } - } - - internal SqlStatistics Statistics - { - get - { - if (null != _connection) - { - if (_connection.StatisticsEnabled) - { - return _connection.Statistics; - } - } - return null; - } - } + private static readonly SqlDiagnosticListener s_diagnosticListener = new(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); //////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS //////////////////////////////////////////////////////////////////////////////////////// /// - override public void Commit() + public override void Commit() { using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionCommitScope(_isolationLevel, _connection, InternalTransaction)) { @@ -191,7 +77,7 @@ protected override void Dispose(bool disposing) } /// - override public void Rollback() + public override void Rollback() { using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(_isolationLevel, _connection, InternalTransaction, null)) { @@ -284,45 +170,5 @@ public void Save(string savePointName) } } } - - //////////////////////////////////////////////////////////////////////////////////////// - // INTERNAL METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - internal void Zombie() - { - // For 2005, we have to defer "zombification" until - // we get past the users' next rollback, else we'll - // throw an exception there that is a breaking change. - // Of course, if the connection is already closed, - // then we're free to zombify... - SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection); - if (null != internalConnection && !_isFromAPI) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Zombie | ADV | Object Id {0} 2005 deferred zombie", ObjectID); - } - else - { - _internalTransaction = null; // pre-2005 zombification - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - // PRIVATE METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - private void ZombieCheck() - { - // If this transaction has been completed, throw exception since it is unusable. - if (IsZombied) - { - if (Is2005PartialZombie) - { - _internalTransaction = null; // 2005 zombification - } - - throw ADP.TransactionZombied(this); - } - } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 6a9738e612..4d7e512b40 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -323,6 +323,9 @@ Microsoft\Data\SqlClient\Server\SqlRecordBuffer.cs + + Microsoft\Data\SqlClient\SqlTransaction.Common.cs + Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs index 6b10dc40ef..d5a93b1b86 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs @@ -3,139 +3,21 @@ // See the LICENSE file in the project root for more information. using System.ComponentModel; -using System.Data; using System.Data.Common; -using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Data.Common; - namespace Microsoft.Data.SqlClient { /// - public sealed class SqlTransaction : DbTransaction + public sealed partial class SqlTransaction : DbTransaction { - private static int _objectTypeCount; // EventSource Counter - internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - internal readonly IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted; - - private SqlInternalTransaction _internalTransaction; - private SqlConnection _connection; - - private bool _isFromAPI; - - internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, - IsolationLevel iso, SqlInternalTransaction internalTransaction) - { - SqlConnection.VerifyExecutePermission(); - - _isolationLevel = iso; - _connection = con; - - if (internalTransaction == null) - { - _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); - } - else - { - Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); - _internalTransaction = internalTransaction; - _internalTransaction.InitParent(this); - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - // PROPERTIES - //////////////////////////////////////////////////////////////////////////////////////// - - /// - new public SqlConnection Connection - { // MDAC 66655 - get - { - if (IsZombied) - { - return null; - } - else - { - return _connection; - } - } - } - - /// - override protected DbConnection DbConnection - { - get - { - return Connection; - } - } - - internal SqlInternalTransaction InternalTransaction - { - get - { - return _internalTransaction; - } - } - - /// - override public IsolationLevel IsolationLevel - { - get - { - ZombieCheck(); - return _isolationLevel; - } - } - - private bool Is2005PartialZombie - { - get - { - return (null != _internalTransaction && _internalTransaction.IsCompleted); - } - } - - internal bool IsZombied - { - get - { - return (null == _internalTransaction || _internalTransaction.IsCompleted); - } - } - - internal int ObjectID - { - get - { - return _objectID; - } - } - - internal SqlStatistics Statistics - { - get - { - if (null != _connection) - { - if (_connection.StatisticsEnabled) - { - return _connection.Statistics; - } - } - return null; - } - } - //////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS //////////////////////////////////////////////////////////////////////////////////////// /// - override public void Commit() + public override void Commit() { SqlConnection.ExecutePermission.Demand(); // MDAC 81476 @@ -151,14 +33,14 @@ override public void Commit() try { #if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + TdsParser.ReliabilitySection tdsReliabilitySection = new(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); statistics = SqlStatistics.StartTimer(Statistics); @@ -195,8 +77,7 @@ override public void Commit() // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request, // this connection may not be in reusable state. // We will abort this connection and make sure it does not go back to the pool. - var innerException = e.InnerException as Win32Exception; - if (innerException != null && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT) + if (e.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT) { _connection.Abort(e); } @@ -221,7 +102,7 @@ protected override void Dispose(bool disposing) try { #if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + TdsParser.ReliabilitySection tdsReliabilitySection = new(); RuntimeHelpers.PrepareConstrainedRegions(); try @@ -264,7 +145,7 @@ protected override void Dispose(bool disposing) } /// - override public void Rollback() + public override void Rollback() { if (Is2005PartialZombie) { @@ -287,14 +168,14 @@ override public void Rollback() try { #if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + TdsParser.ReliabilitySection tdsReliabilitySection = new(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); statistics = SqlStatistics.StartTimer(Statistics); @@ -351,14 +232,14 @@ public void Rollback(string transactionName) try { #if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + TdsParser.ReliabilitySection tdsReliabilitySection = new(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); statistics = SqlStatistics.StartTimer(Statistics); @@ -415,14 +296,14 @@ public void Save(string savePointName) try { #if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + TdsParser.ReliabilitySection tdsReliabilitySection = new(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); statistics = SqlStatistics.StartTimer(Statistics); @@ -458,49 +339,5 @@ public void Save(string savePointName) } } } - - //////////////////////////////////////////////////////////////////////////////////////// - // INTERNAL METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - internal void Zombie() - { - // SQLBUDT #402544 For 2005, we have to defer "zombification" until - // we get past the users' next rollback, else we'll - // throw an exception there that is a breaking change. - // Of course, if the connection is aready closed, - // then we're free to zombify... - SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection); - - if (null != internalConnection && internalConnection.Is2005OrNewer && !_isFromAPI) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} 2005 deferred zombie", ObjectID); - } - else - { - _internalTransaction = null; // pre-2005 zombification - } - - } - - //////////////////////////////////////////////////////////////////////////////////////// - // PRIVATE METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - private void ZombieCheck() - { - // If this transaction has been completed, throw exception since it is unusable. - if (IsZombied) - { - - if (Is2005PartialZombie) - { - _internalTransaction = null; // 2005 zombification - } - - throw ADP.TransactionZombied(this); - } - } } } - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs new file mode 100644 index 0000000000..19996684b9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Data; +using System.Data.Common; +using System.Diagnostics; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + /// + public sealed partial class SqlTransaction : DbTransaction + { + private static int s_objectTypeCount; // EventSource Counter + internal readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount); + internal readonly IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted; + + private SqlInternalTransaction _internalTransaction; + private readonly SqlConnection _connection; + + private bool _isFromAPI; + + internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, + IsolationLevel iso, SqlInternalTransaction internalTransaction) + { +#if NETFRAMEWORK + SqlConnection.VerifyExecutePermission(); +#endif + _isolationLevel = iso; + _connection = con; + + if (internalTransaction == null) + { + _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); + } + else + { + Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); + _internalTransaction = internalTransaction; + _internalTransaction.InitParent(this); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // PROPERTIES + //////////////////////////////////////////////////////////////////////////////////////// + + /// + public new SqlConnection Connection + {// MDAC 66655 + get + { + if (IsZombied) + { + return null; + } + else + { + return _connection; + } + } + } + + /// + protected override DbConnection DbConnection => Connection; + + internal SqlInternalTransaction InternalTransaction => _internalTransaction; + + /// + public override IsolationLevel IsolationLevel + { + get + { + ZombieCheck(); + return _isolationLevel; + } + } + + private bool Is2005PartialZombie => _internalTransaction !=null && _internalTransaction.IsCompleted; + + internal bool IsZombied => _internalTransaction == null || _internalTransaction.IsCompleted; + + internal int ObjectID => _objectID; + + internal SqlStatistics Statistics + { + get + { + if (null != _connection) + { + if (_connection.StatisticsEnabled) + { + return _connection.Statistics; + } + } + return null; + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // INTERNAL METHODS + //////////////////////////////////////////////////////////////////////////////////////// + + internal void Zombie() + { + // For Yukon, we have to defer "zombification" until + // we get past the users' next rollback, else we'll + // throw an exception there that is a breaking change. + // Of course, if the connection is already closed, + // then we're free to zombify... + SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection); + if (null != internalConnection +#if NETFRAMEWORK + && internalConnection.Is2005OrNewer +#endif + && !_isFromAPI) + { + SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Zombie | ADV | Object Id {0} yukon deferred zombie", ObjectID); + } + else + { + _internalTransaction = null; // pre SQL 2005 zombification + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + //////////////////////////////////////////////////////////////////////////////////////// + + private void ZombieCheck() + { + // If this transaction has been completed, throw exception since it is unusable. + if (IsZombied) + { + if (Is2005PartialZombie) + { + _internalTransaction = null; // SQL 2005 zombification + } + + throw ADP.TransactionZombied(this); + } + } + } +} From c9fc8680882586a994b352de26bba650a51123be Mon Sep 17 00:00:00 2001 From: SqlClient DevOps Date: Sat, 11 Jun 2022 03:11:19 +0000 Subject: [PATCH 21/35] [Scheduled Run] Localized resource files from OneLocBuild --- .../netfx/src/Resources/Strings.de.resx | 2 +- .../netfx/src/Resources/Strings.es.resx | 2 +- .../netfx/src/Resources/Strings.fr.resx | 2 +- .../netfx/src/Resources/Strings.it.resx | 2 +- .../netfx/src/Resources/Strings.ja.resx | 2 +- .../netfx/src/Resources/Strings.ko.resx | 2 +- .../netfx/src/Resources/Strings.pt-BR.resx | 2 +- .../netfx/src/Resources/Strings.ru.resx | 2 +- .../netfx/src/Resources/Strings.zh-Hans.resx | 2 +- .../netfx/src/Resources/Strings.zh-Hant.resx | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index 34cd269e19..e6f2f7ce90 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -4618,6 +4618,6 @@ Der Parameter "{0}" kann keine Ausgaberichtung oder InputOutput aufweisen, wenn EnableOptimizedParameterBinding für den übergeordneten Befehl aktiviert ist. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + Timeout bei der Verbindung beim Abrufen eines Zugriffstokens mithilfe der Authentifizierungsmethode "{0}". Letzter Fehler: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index 3b89fa8953..83f27d186e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -4618,6 +4618,6 @@ El parámetro “{0}” no puede tener la Dirección de salida ni InputOutput cuando EnableOptimizedParameterBinding está habilitado en el comando primario. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + Se agotó el tiempo de espera de la conexión al recuperar un token de acceso mediante el método de autenticación "{0}". Último error: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index 642d36e58e..4974b284a9 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -4618,6 +4618,6 @@ Le paramètre « {0} » ne peut pas avoir Direction Output ou InputOutput lorsque EnableOptimizedParameterBinding est activé sur la commande parente. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + La connexion a expiré lors de la récupération d’un jeton d’accès à l’aide de '{0}' méthode d’authentification. Dernière erreur : {1} : {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index c098da5ac4..112ebc5cd1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -4618,6 +4618,6 @@ Il parametro '{0}' non può includere Output o InputOutput come valore di Direction quando nel comando padre è abilitato EnableOptimizedParameterBinding. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + Timeout della connessione durante il recupero di un token di accesso tramite il metodo di autenticazione '{0}'. Ultimo errore: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index 20490f2dfa..fb1ed6d61d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -4618,6 +4618,6 @@ 親コマンドで EnableOptimizedParameterBinding が有効になっている場合、パラメーター '{0}' に Direction 出力または InputOutput は指定できません。 - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + 認証方法 '{0}' によるアクセス トークンの取得中に接続がタイムアウトしました。前回のエラー: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index d15e7470a1..8b2b05f456 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -4618,6 +4618,6 @@ 부모 명령에서 EnableOptimizedParameterBinding을 사용하는 경우 매개 변수 '{0}'에는 Direction Output 또는 InputOutput을 사용할 수 없습니다. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + '{0}' 인증 방법을 사용하여 액세스 토큰을 검색하는 동안 연결 시간이 초과되었습니다. 마지막 오류: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index 4312c5e1ab..c85c238184 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -4618,6 +4618,6 @@ O parâmetro '{0}' não pode ter a saída de direção ou InputOutput quando EnableOptimizedParameterBinding está habilitado no comando pai. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + A conexão expirou ao recuperar um token de acesso usando o método de autenticação '{0}'. Último erro: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index fa44bce13f..692ed92ac7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -4618,6 +4618,6 @@ У параметра "{0}" не может быть направления вывода или InputOutput, если EnableOptimizedParameterBinding включен в родительской команде. - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + Истекло время ожидания подключения при получении маркера доступа с помощью метода проверки подлинности "{0}". Последняя ошибка: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index 10411eaae7..d9a9338af5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -4618,6 +4618,6 @@ 当在父命令上启用 EnableOptimizedParameterBinding 时,参数“{0}”不能具有 Direction Output 或 InputOutput。 - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + 使用“{0}”身份验证方法检索访问令牌时连接超时。最后一个错误: {1}: {2} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index 203de4b3b8..5074e60cfc 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -4618,6 +4618,6 @@ 在父命令上啟用 EnableOptimizedParameterBinding 時,參數 '{0}' 不能具有方向輸出或 InputOutput。 - Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + 使用 '{0}' 驗證方法擷取存取權杖時已逾時。上次錯誤: {1}: {2} \ No newline at end of file From 73eba15c548e5b89ad6b5147d8d5c7c8412baa02 Mon Sep 17 00:00:00 2001 From: David Engel Date: Mon, 13 Jun 2022 17:08:01 -0700 Subject: [PATCH 22/35] Strings for localization (#1641) * Strings for localization * Adding initial English strings to be translated --- .../netcore/src/Resources/Strings.Designer.cs | 9 +++++++++ .../netcore/src/Resources/Strings.resx | 3 +++ .../netfx/src/Resources/Strings.Designer.cs | 18 ++++++++++++++++++ .../netfx/src/Resources/Strings.de.resx | 6 ++++++ .../netfx/src/Resources/Strings.es.resx | 6 ++++++ .../netfx/src/Resources/Strings.fr.resx | 6 ++++++ .../netfx/src/Resources/Strings.it.resx | 6 ++++++ .../netfx/src/Resources/Strings.ja.resx | 6 ++++++ .../netfx/src/Resources/Strings.ko.resx | 6 ++++++ .../netfx/src/Resources/Strings.pt-BR.resx | 6 ++++++ .../netfx/src/Resources/Strings.resx | 6 ++++++ .../netfx/src/Resources/Strings.ru.resx | 6 ++++++ .../netfx/src/Resources/Strings.zh-Hans.resx | 6 ++++++ .../netfx/src/Resources/Strings.zh-Hant.resx | 6 ++++++ 14 files changed, 96 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs index 7a7ff4a367..ccbc9db180 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -3246,6 +3246,15 @@ internal static string SQL_StreamWriteNotSupported { } } + /// + /// Looks up a localized string similar to Encrypt=Strict is not supported when targeting .NET Standard 2.0. Use .NET Standard 2.1, .NET Framework, or .NET.. + /// + internal static string SQL_TDS8_NotSupported_Netstandard2_0 { + get { + return ResourceManager.GetString("SQL_TDS8_NotSupported_Netstandard2.0", resourceCulture); + } + } + /// /// Looks up a localized string similar to Processing of results from SQL Server failed because of an invalid multipart name. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx index a77dd0aef1..f268baa2de 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx @@ -1938,4 +1938,7 @@ Incorrect physicalConnection type. + + Encrypt=Strict is not supported when targeting .NET Standard 2.0. Use .NET Standard 2.1, .NET Framework, or .NET. + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index 81ad596a68..fdbc830cb6 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -5703,6 +5703,15 @@ internal static string DbConnectionString_FailoverPartner { } } + /// + /// Looks up a localized string similar to The service principal name (SPN) of the failover partner.. + /// + internal static string DbConnectionString_FailoverPartnerSPN { + get { + return ResourceManager.GetString("DbConnectionString_FailoverPartnerSPN", resourceCulture); + } + } + /// /// Looks up a localized string similar to The UDL file to use when connecting to the Data Source.. /// @@ -5856,6 +5865,15 @@ internal static string DbConnectionString_Replication { } } + /// + /// Looks up a localized string similar to The service principal name (SPN) of the server.. + /// + internal static string DbConnectionString_ServerSPN { + get { + return ResourceManager.GetString("DbConnectionString_ServerSPN", resourceCulture); + } + } + /// /// Looks up a localized string similar to Indicates binding behavior of connection to a System.Transactions Transaction when enlisted.. /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index e6f2f7ce90..240957a68f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -4620,4 +4620,10 @@ Timeout bei der Verbindung beim Abrufen eines Zugriffstokens mithilfe der Authentifizierungsmethode "{0}". Letzter Fehler: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index 83f27d186e..919748b717 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -4620,4 +4620,10 @@ Se agotó el tiempo de espera de la conexión al recuperar un token de acceso mediante el método de autenticación "{0}". Último error: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index 4974b284a9..c0dbfd4057 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -4620,4 +4620,10 @@ La connexion a expiré lors de la récupération d’un jeton d’accès à l’aide de '{0}' méthode d’authentification. Dernière erreur : {1} : {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index 112ebc5cd1..911bcf77b6 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -4620,4 +4620,10 @@ Timeout della connessione durante il recupero di un token di accesso tramite il metodo di autenticazione '{0}'. Ultimo errore: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index fb1ed6d61d..80c8acb028 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -4620,4 +4620,10 @@ 認証方法 '{0}' によるアクセス トークンの取得中に接続がタイムアウトしました。前回のエラー: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index 8b2b05f456..d9d5c3cf30 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -4620,4 +4620,10 @@ '{0}' 인증 방법을 사용하여 액세스 토큰을 검색하는 동안 연결 시간이 초과되었습니다. 마지막 오류: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index c85c238184..b06de583b0 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -4620,4 +4620,10 @@ A conexão expirou ao recuperar um token de acesso usando o método de autenticação '{0}'. Último erro: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index 15459d6059..66683219cc 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -4620,4 +4620,10 @@ Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index 692ed92ac7..51703de962 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -4620,4 +4620,10 @@ Истекло время ожидания подключения при получении маркера доступа с помощью метода проверки подлинности "{0}". Последняя ошибка: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index d9a9338af5..4a6c08d720 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -4620,4 +4620,10 @@ 使用“{0}”身份验证方法检索访问令牌时连接超时。最后一个错误: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index 5074e60cfc..f4bc5c2f41 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -4620,4 +4620,10 @@ 使用 '{0}' 驗證方法擷取存取權杖時已逾時。上次錯誤: {1}: {2} + + The service principal name (SPN) of the failover partner. + + + The service principal name (SPN) of the server. + \ No newline at end of file From d600f8921e193c72b37d728b8a76cee2cfece9ac Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:10:56 -0700 Subject: [PATCH 23/35] Feature | Project Microsoft.SqlServer.Server separation phase 2 (MDS consumes MSS using netcore) (#1585) --- build.proj | 4 +- doc/samples/IBinarySerialize.cs | 6 +- doc/samples/SqlFunctionAttribute.cs | 2 +- doc/samples/SqlUserDefinedAggregate.cs | 8 +- doc/samples/SqlUserDefinedType1.cs | 4 +- .../DataAccessKind.xml | 26 ---- .../Format.xml | 51 ------- .../IBinarySerialize.xml | 54 ------- .../InvalidUdtException.xml | 22 --- .../SqlFacetAttribute.xml | 124 ---------------- .../SqlFunctionAttribute.xml | 125 ---------------- .../SqlMethodAttribute.xml | 68 --------- .../SqlUserDefinedAggregateAttribute.xml | 132 ----------------- .../SqlUserDefinedTypeAttribute.xml | 137 ----------------- .../SystemDataAccessKind.xml | 26 ---- src/Directory.Build.props | 1 + .../netcore/ref/Microsoft.Data.SqlClient.cs | 132 ----------------- .../src/Microsoft.Data.SqlClient.csproj | 24 +-- .../Microsoft/Data/SqlClient/SqlConnection.cs | 6 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../netfx/ref/Microsoft.Data.SqlClient.cs | 136 ----------------- .../Microsoft/Data/SqlClient/SqlConnection.cs | 10 +- .../src/Microsoft/Data/Common/AdapterUtil.cs | 13 +- .../Data/SqlClient/Server/IBinarySerialize.cs | 21 --- .../SqlClient/Server/InvalidUdtException.cs | 52 ------- .../SqlClient/Server/SqlFacetAttribute.cs | 51 ------- .../SqlClient/Server/SqlFunctionAttribute.cs | 104 ------------- .../SqlClient/Server/SqlMethodAttribute.cs | 47 ------ .../Microsoft/Data/SqlClient/Server/SqlSer.cs | 2 - .../SqlUserDefinedAggregateAttribute.cs | 134 ----------------- .../Server/SqlUserDefinedTypeAttribute.cs | 140 ------------------ .../src/Microsoft/Data/SqlClient/SqlEnums.cs | 3 - .../Microsoft/Data/SqlClient/SqlUdtInfo.cs | 4 - .../Microsoft.Data.SqlClient.Tests.csproj | 1 + .../FunctionalTests/SqlDataRecordTest.cs | 4 - .../FunctionalTests/SqlFacetAttributeTest.cs | 4 - .../tests/FunctionalTests/SqlMetaDataTest.cs | 17 ++- ....Data.SqlClient.ManualTesting.Tests.csproj | 2 +- .../SQL/UdtTest/UDTs/Address/Address.cs | 4 - .../SQL/UdtTest/UDTs/Address/Address.csproj | 3 +- .../SQL/UdtTest/UDTs/Circle/Circle.cs | 4 - .../SQL/UdtTest/UDTs/Circle/Circle.csproj | 3 +- .../SQL/UdtTest/UDTs/Circle/Point1.cs | 4 - .../SQL/UdtTest/UDTs/Shapes/Line.cs | 4 - .../SQL/UdtTest/UDTs/Shapes/Point.cs | 4 - .../SQL/UdtTest/UDTs/Shapes/Shapes.csproj | 3 +- .../SQL/UdtTest/UDTs/Utf8String/Utf8String.cs | 4 - .../UdtTest/UDTs/Utf8String/Utf8String.csproj | 3 +- .../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 8 +- tools/props/Versions.props | 1 - tools/specs/Microsoft.Data.SqlClient.nuspec | 3 + 51 files changed, 56 insertions(+), 1691 deletions(-) delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/DataAccessKind.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/Format.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/IBinarySerialize.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/InvalidUdtException.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/SqlFacetAttribute.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/SqlFunctionAttribute.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/SqlMethodAttribute.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedAggregateAttribute.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedTypeAttribute.xml delete mode 100644 doc/snippets/Microsoft.Data.SqlClient.Server/SystemDataAccessKind.xml delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/IBinarySerialize.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/InvalidUdtException.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFacetAttribute.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFunctionAttribute.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMethodAttribute.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedAggregateAttribute.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedTypeAttribute.cs diff --git a/build.proj b/build.proj index 124837c2d2..14efda3f4a 100644 --- a/build.proj +++ b/build.proj @@ -71,7 +71,7 @@ - + @@ -113,7 +113,9 @@ + + diff --git a/doc/samples/IBinarySerialize.cs b/doc/samples/IBinarySerialize.cs index 749ee5f3f6..64f314f86d 100644 --- a/doc/samples/IBinarySerialize.cs +++ b/doc/samples/IBinarySerialize.cs @@ -2,7 +2,7 @@ using System.IO; using System.Data; using System.Data.SqlTypes; -using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Server; using System.Text; namespace test @@ -45,7 +45,7 @@ static void Main(string[] args) // Bytes 0 - 19: string text, padded to the right with null characters // Bytes 20+: Double value - // using Microsoft.Data.SqlClient.Server; + // using Microsoft.SqlServer.Server; public void Read(System.IO.BinaryReader r) { @@ -84,7 +84,7 @@ public void Read(System.IO.BinaryReader r) // Bytes 0 - 19: string text, padded to the right with null characters // Bytes 20+: Double value - // using Microsoft.Data.SqlClient.Server; + // using Microsoft.SqlServer.Server; public void Write(System.IO.BinaryWriter w) { int maxStringSize = 20; diff --git a/doc/samples/SqlFunctionAttribute.cs b/doc/samples/SqlFunctionAttribute.cs index 6f20986cf6..28a027caad 100644 --- a/doc/samples/SqlFunctionAttribute.cs +++ b/doc/samples/SqlFunctionAttribute.cs @@ -2,7 +2,7 @@ using System.IO; using System.Collections; // -using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Server; public class Class1 { diff --git a/doc/samples/SqlUserDefinedAggregate.cs b/doc/samples/SqlUserDefinedAggregate.cs index 45a45dff7c..7f3792c133 100644 --- a/doc/samples/SqlUserDefinedAggregate.cs +++ b/doc/samples/SqlUserDefinedAggregate.cs @@ -1,20 +1,20 @@ using System; // -using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Server; using System.IO; using System.Data.Sql; using System.Data.SqlTypes; using System.Text; [Serializable] -[Microsoft.Data.SqlClient.Server.SqlUserDefinedAggregate( - Microsoft.Data.SqlClient.Server.Format.UserDefined, +[Microsoft.SqlServer.Server.SqlUserDefinedAggregate( + Microsoft.SqlServer.Server.Format.UserDefined, IsInvariantToNulls = true, IsInvariantToDuplicates = false, IsInvariantToOrder = false, MaxByteSize = 8000) ] -public class Concatenate : Microsoft.Data.SqlClient.Server.IBinarySerialize +public class Concatenate : Microsoft.SqlServer.Server.IBinarySerialize { public void Read(BinaryReader r) diff --git a/doc/samples/SqlUserDefinedType1.cs b/doc/samples/SqlUserDefinedType1.cs index 5601a016b1..8f440648c2 100644 --- a/doc/samples/SqlUserDefinedType1.cs +++ b/doc/samples/SqlUserDefinedType1.cs @@ -2,7 +2,7 @@ // using System; using System.Data.SqlTypes; -using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Server; [Serializable()] [SqlUserDefinedType(Format.Native)] @@ -133,7 +133,7 @@ public SqlString Quadrant() //----------------------------------------------------------------------------- // -// using Microsoft.Data.SqlClient.Server; +// using Microsoft.SqlServer.Server; [SqlUserDefinedType(Format.Native, MaxByteSize = 8000)] public class SampleType diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/DataAccessKind.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/DataAccessKind.xml deleted file mode 100644 index d429e11dd9..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/DataAccessKind.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Describes the type of access to user data for a user-defined method or function. - - and to indicate whether the method or function uses ADO.NET to connect back to the database using the "context connection." - - Note that methods and functions are not allowed to make changes to the database, so the options for this enumeration are `None` (meaning no data-access performed by the method or function) and `Read` (meaning that the method or function perform read-only data-access operations, such as executing SELECT statements). - - ]]> - - - - The method or function does not access user data. - - - The method or function reads user data. - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/Format.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/Format.xml deleted file mode 100644 index b15cd6eadb..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/Format.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Used by and to indicate the serialization format of a user-defined type (UDT) or aggregate. - - and to indicate the serialization format of a user-defined type (UDT) or aggregate. Use of the `Native` and `UserDefined` enumeration members has special requirements. -- `Format.Native` - The requirements for the `Format.Native` format are: - - - The with a property value of must be applied to the aggregate or UDT if it is defined in a class and not a structure. This controls the physical layout of the data fields and is used to force the members to be laid out sequentially in the order they appear. SQL Server uses this attribute to determine the field order for UDTs with multiple fields. - - - The type must contain at least one member (serialized values cannot be zero bytes in size). - - - All the fields of the aggregate must be *blittable*; that is, they must have a common representation in both managed and unmanaged memory and not require special handling by the interop marshaler. - - - All the fields of the UDT should be of one of the following types that can be serialized: `bool`, `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, , , , , , , , , or other value types defined by the user that contain fields of one of these types. - - The aggregate must not specify a value for `MaxByteSize`. - - - The aggregate must not have any [NonSerialized] fields. - - - Fields must not be marked as an explicit layout (with a of ). -- `Format.UserDefined` - The requirements for the `Format.UserDefined` format are: - - The aggregate must specify a value for `MaxByteSize`. - - - Specify the attribute property. The default value is `false`. - - - If you omit any field in the or methods, the state of that field is not serialized. -## Examples -The following example shows the `UserDefinedType` attribute of the Point UDT. The UDT is byte-ordered, is named "Point", has a validation method named "ValidatePoint", and uses the native serialization format. - -[!code-csharp[SqlUserDefinedType Example#1](~/../sqlclient/doc/samples/SqlUserDefinedType.cs#1)] - - ]]> - - - - This serialization format uses a very simple algorithm that enables SQL Server to store an efficient representation of the UDT on disk. Types marked for serialization can only have value types (structs in Microsoft Visual C# and structures in Microsoft Visual Basic .NET) as members. Members of reference types (such as classes in Visual C# and Visual Basic), either user-defined or those existing in .NET class libraries (such as ), are not supported. - - - The serialization format is unknown. - - - This serialization format gives the developer full control over the binary format through the and methods. - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/IBinarySerialize.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/IBinarySerialize.xml deleted file mode 100644 index a84e479859..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/IBinarySerialize.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - Provides custom implementation for user-defined type (UDT) and user-defined aggregate serialization and deserialization. - - .`Native` or .`UserDefined`. - - .`Native` allows SQL Server to handle serialization and deserialization automatically, but the format has restrictions on the kind of types it can handle. .`UserDefined` allows user-defined types and aggregates to handle their own serialization. User-defined types and aggregates must be marked with .`UserDefined` in the `SqlUserDefinedType` or `SqlUserDefinedAggregate` attribute, and must implement the interface. - - Note that even with custom serialization, the total size of each instance must be under the maximum allowed limit, currently 8000 bytes. - - ]]> - - - - The stream from which the object is deserialized. - Generates a user-defined type (UDT) or user-defined aggregate from its binary form. - - method must reconstitute your object using the information written by the method. - -## Examples - The following example shows the implementation of the method of a UDT, which uses a to de-serialize a previously persisted UDT. This example assumes that the UDT has two data properties: `StringValue` and `DoubleValue`. - - [!code-csharp[IBinarySerialize Samples#1](~/../sqlclient/doc/samples/IBinarySerialize.cs#1)] - - ]]> - - - - The stream to which the UDT or user-defined aggregate is serialized. - Converts a user-defined type (UDT) or user-defined aggregate into its binary format so that it may be persisted. - - method to reconstitute your UDT or user-defined aggregate. - -## Examples - The following example shows the implementation of the method of a UDT, which uses a to serialize the UDT in the user-defined binary format. The purpose of the null character padding is to ensure that the string value is completely separated from the double value, so that one UDT is compared to another in Transact-SQL code, string bytes are compared to string bytes and double bytes are compared to double bytes. - - [!code-csharp[IBinarySerialize Samples#2](~/../sqlclient/doc/samples/IBinarySerialize.cs#2)] - - ]]> - - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/InvalidUdtException.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/InvalidUdtException.xml deleted file mode 100644 index 70bfd160f4..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/InvalidUdtException.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - Thrown when SQL Server or the ADO.NET provider detects an invalid user-defined type (UDT). - To be added. - - - The object. - The object. - Streams all the properties into the class for the given . - - class to make the class serializable. - - ]]> - - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlFacetAttribute.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/SqlFacetAttribute.xml deleted file mode 100644 index bbe76cdd22..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlFacetAttribute.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - Annotates the returned result of a user-defined type (UDT) with additional information that can be used in Transact-SQL. - - may only be specified on non-void return values. - - is used only to derive information about the return type, and is not intended to be a constraint specification on what can be stored in the type. Thus, if a field has a indicating its size to be 2 characters, then the SQL Server type of the field access expression is of size 2, but assignments into the field are not restricted by this facet. - - The table below captures the matrix of valid values for the various properties for specific field types. In this table, "Y" indicates that the property is valid, and "N" indicates that the property is not valid. - - The specified must be compatible with the field type. If the property is not valid, type registration will report an error if the user specifies a non-default value for the property. The maximum values for and properties are 38. For the property, the value should be in the range of 1-8000 for binary and non-Unicode data, 1-4000 for Unicode data, or -1. All other values are not valid. - -|Type|IsFixedLength|MaxSize|Precision|Scale|IsNullable| -|----------|-------------------|-------------|---------------|-----------|----------------| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|N|N|Y| -||N|N|Y|Y|Y| -||Y|Y|N|N|Y| -||Y|Y|N|N|Y| -||N|N|N|N|Y| -||Y|Y|N|N|Y| -||Y|Y|N|N|Y| -|Embedded UDTs|N|N|N|N|Y| -||Y|Y|N|N|Y| -|Byte[]|Y|Y|N|N|Y| -|Char[]|Y|Y|N|N|Y| -||N|N|N|Y1|N| -||N|N|Y|Y|Y| - - (1) Specifying the scale on a DateTime type will cause the value to be returned to Transact-SQL as a DateTime2 type with the specified scale. - - ]]> - - - - An optional attribute on a user-defined type (UDT) return type, used to annotate the returned result with additional information that can be used in Transact-SQL. - To be added. - - - Indicates whether the return type of the user-defined type is of a fixed length. - - if the return type is of a fixed length; otherwise . - - property is set to 1. - -The default value is `false`. - -]]> - - - - Indicates whether the return type of the user-defined type can be . - - if the return type of the user-defined type can be ; otherwise . - - - - - - The maximum size, in logical units, of the underlying field type of the user-defined type. - An representing the maximum size, in logical units, of the underlying field type. - - - - - - The precision of the return type of the user-defined type. - An representing the precision of the return type. - - property is valid only for numeric types. The property must also be specified when setting the property. - - The maximum value of the property is 38; the default value is 38. - - ]]> - - - - The scale of the return type of the user-defined type. - An representing the scale of the return type. - - property is valid only for decimal types. The property must also be specified when setting the property. - - The maximum value of the property is 38; the default value is 0. - - ]]> - - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlFunctionAttribute.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/SqlFunctionAttribute.xml deleted file mode 100644 index 7221363628..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlFunctionAttribute.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - Used to mark a method definition of a user-defined aggregate as a function in SQL Server. The properties on the attribute reflect the physical characteristics used when the type is registered with SQL Server. - - - - - - An optional attribute on a user-defined aggregate, used to indicate that the method should be registered in SQL Server as a function. Also used to set the , , , , , , and properties of the function attribute. - To be added. - - - Indicates whether the function involves access to user data stored in the local instance of SQL Server. - - .: Does not access data. .: Only reads user data. - - . is also required when connecting to remote servers if transactions integration is required (the default). - -If a Transact-SQL query is executed from inside a table-valued function (TVF), the property should be set. - -]]> - - - - The name of a method in the same class which is used to fill a row of data in the table returned by the table-valued function. - A value representing the name of a method in the same class which is used to fill a row of data in the table returned by the table-valued function. - - - Indicates whether the user-defined function is deterministic. - - if the function is deterministic; otherwise . - - property is also useful for indexing the result of the function in the form of indexed computed columns and indexed views. If this property is not specified, the function is assumed to be non-deterministic. - -Functions that access local data can be deterministic. The data access characteristic is captured separately by the and properties. - -Note that data access to remote servers (for example, using a to connect to another SQL Server instance) is available in user-defined functions. However, you must still honor the declaration. If the common language runtime (CLR) function is marked as deterministic, it should not cause side-effects in the remote server. While side-effects against the context connection are restricted, SQL Server will not enforce the restriction for side-effects over remote connections. - -The default value of this attribute is `false`. - -Do not mark a function as deterministic if the function does not always produce the same output values, given the same input values and the same database state. Marking a function as deterministic when the function is not truly deterministic can result in corrupted indexed views and computed columns. - -]]> - - - - Indicates whether the function involves imprecise computations, such as floating point operations. - - if the function involves precise computations; otherwise . - - - - - - The name under which the function should be registered in SQL Server. - A value representing the name under which the function should be registered. - - - - - - Indicates whether the function requires access to data stored in the system catalogs or virtual system tables of SQL Server. - - .: Does not access system data. .: Only reads system data. - - . - -]]> - - - - A string that represents the table definition of the results, if the method is used as a table-valued function (TVF). - A value representing the table definition of the results. - - - - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlMethodAttribute.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/SqlMethodAttribute.xml deleted file mode 100644 index 95f73cda49..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlMethodAttribute.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - Indicates the determinism and data access properties of a method or property on a user-defined type (UDT). The properties on the attribute reflect the physical characteristics that are used when the type is registered with SQL Server. - - should be used on the setter or the getter directly. - - inherits from a , so inherits the `FillRowMethodName` and `TableDefinition` fields from . Note that it is not possible to write a table-valued method, although the names of these fields might suggest that it is possible. - -## Examples -The following example shows a UDT method that is attributed to indicate that the method will not be invoked on null instances of the type, that the method will not change the state of the type, and that the method will not be called when `null` parameters are supplied to the method invocation. - -[!code-csharp[SqlMethod Sample#1](~/../sqlclient/doc/samples/SqlMethod.cs#1)] - -]]> - - - - An attribute on a user-defined type (UDT), used to indicate the determinism and data access properties of a method or a property on a UDT. - To be added. - - - Indicates whether SQL Server should invoke the method on null instances. - if SQL Server should invoke the method on null instances; otherwise, . If the method cannot be invoked (because of an attribute on the method), the SQL Server is returned. - - - - - - Indicates whether a method on a user-defined type (UDT) is a mutator. - if the method is a mutator; otherwise . - - property is set to `true` and the return type of the method is `void`, SQL Server marks the method as a mutator. A mutator method is one that causes a state change in the UDT instance. Mutator methods can be called in assignment statements or data modification statements, but cannot be used in queries. If a method is marked as a mutator but does not return void, then CREATE TYPE does not fail with an error. Even though a returned value other than `void` does not raise an error, the returned value is not accessible and cannot be used. - -The default value of the property is `false`. - -A property can be a mutator if is used on the setter and is set to `true`. However, a property setter is implicitly treated as a mutator, so it is not necessary to set the property of the to `true`. - -]]> - - - - Indicates whether the method on a user-defined type (UDT) is called when input arguments are specified in the method invocation. - if the method is called when input arguments are specified in the method invocation; if the method returns a value when any of its input parameters are . If the method cannot be invoked (because of an attribute on the method), the SQL Server is returned. - - property is `true`. - -]]> - - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedAggregateAttribute.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedAggregateAttribute.xml deleted file mode 100644 index 5822bac69d..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedAggregateAttribute.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - Indicates that the type should be registered as a user-defined aggregate. The properties on the attribute reflect the physical attributes used when the type is registered with SQL Server. This class cannot be inherited. - - custom attribute. Every user-defined aggregate must be annotated with this attribute. - -See "CLR User-Defined Aggregates" in SQL Server 2005 Books Online for more information on user-defined aggregates and examples. - -## Examples -The following example shows the attribute for a user-defined aggregate. The aggregate uses custom serialization, has a maximum size of 8000 bytes when serialized, and is invariant to nulls, duplicates, and order. - -[!code-csharp[SqlUserDefinedAggregate Sample#1](~/../sqlclient/doc/samples/SqlUserDefinedAggregate.cs#1)] - -]]> - - - - One of the values representing the serialization format of the aggregate. - A required attribute on a user-defined aggregate, used to indicate that the given type is a user-defined aggregate and the storage format of the user-defined aggregate. - - - The serialization format as a . - A representing the serialization format. - - - - - - Indicates whether the aggregate is invariant to duplicates. - if the aggregate is invariant to duplicates; otherwise . - - - - - - Indicates whether the aggregate is invariant to nulls. - if the aggregate is invariant to nulls; otherwise . - - - - - - Indicates whether the aggregate is invariant to order. - if the aggregate is invariant to order; otherwise . - - - - - - Indicates whether the aggregate returns if no values have been accumulated. - if the aggregate returns if no values have been accumulated; otherwise . - - - - - - The maximum size, in bytes, of the aggregate instance. - An value representing the maximum size of the aggregate instance. - - property with the UserDefined serialization . - -The maximum allowed value for this property is specified by the field. - -The maximum size allowed is 2 gigabytes (GB). You can specify a number from 1 to 8000 bytes, or -1 to represent a value larger than 8000 bytes, up to 2 gigabytes. - -For an aggregate with user-defined serialization specified, refers to the total size of the serialized data. Consider an aggregate serializing a string of 10 characters (). When the string is serialized using a , the total size of the serialized string is 22 bytes: 2 bytes per Unicode UTF-16 character, multiplied by the maximum number of characters, plus 2 control bytes of overhead incurred from serializing a binary stream. So, when determining the value of , the total size of the serialized data must be considered: the size of the data serialized in binary form plus the overhead incurred by serialization. - -]]> - - - - The maximum size, in bytes, required to store the state of this aggregate instance during computation. - - value representing the maximum size of the aggregate instance. - -]]> - - - - The name of the aggregate. - A value representing the name of the aggregate. - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedTypeAttribute.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedTypeAttribute.xml deleted file mode 100644 index 14bc23a2bd..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/SqlUserDefinedTypeAttribute.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - Used to mark a type definition in an assembly as a user-defined type (UDT) in SQL Server. The properties on the attribute reflect the physical characteristics used when the type is registered with SQL Server. This class cannot be inherited. - - custom attribute. Every UDT must be annotated with this attribute. See [CLR User-Defined Types](https://go.microsoft.com/fwlink/?LinkId=128028) for more information about UDTs, including an example of a UDT. - -## Examples -The following example shows the `UserDefinedType` attribute of the Point UDT. The UDT is byte-ordered, is named "Point", has a validation method named "ValidatePoint", and uses the native serialization format. - -[!code-csharp[SqlUserDefinedType Example#1](~/../sqlclient/doc/samples/SqlUserDefinedType.cs#1)] - -]]> - - - - One of the values representing the serialization format of the type. - A required attribute on a user-defined type (UDT), used to confirm that the given type is a UDT and to indicate the storage format of the UDT. - - - - - - The serialization format as a . - A value representing the serialization format. - - - Indicates whether the user-defined type is byte ordered. - - if the user-defined type is byte ordered; otherwise . - - property in effect guarantees that the serialized binary data can be used for semantic ordering of the information. Thus, each instance of a byte-ordered UDT object can only have one serialized representation. When a comparison operation is performed in SQL Server on the serialized bytes, its results should be the same as if the same comparison operation had taken place in managed code. - -The following features are supported when is set to `true`: - -- The ability to create indexes on columns of this type. - -- The ability to create primary and foreign keys as well as CHECK and UNIQUE constraints on columns of this type. - -- The ability to use Transact-SQL ORDER BY, GROUP BY, and PARTITION BY clauses. In these cases, the binary representation of the type is used to determine the order. - -- The ability to use comparison operators in Transact-SQL statements. - -- The ability to persist computed columns of this type. - -Note that both the `Native` and `UserDefined` serialization formats support the following comparison operators when is set to `true`: - -- Equal to (=) - -- Not equal to (!=) - -- Greater than (>) - -- Less than (\<) - -- Greater than or equal to (>=) - -- Less than or equal to (<=) - -]]> - - - - Indicates whether all instances of this user-defined type are the same length. - - if all instances of this type are the same length; otherwise . - - - . This attribute is only relevant for UDTs with `UserDefined` serialization . - -]]> - - - - The maximum size of the instance, in bytes. - An value representing the maximum size of the instance. - - property with the `UserDefined` serialization . - -When connecting to SQL Server 2005 or earlier, must be between 1 and 8000. - -When connecting to SQL Server 2008 or later, set between 1 and 8000, for a type whose instances are always 8,000 bytes or less. For types that can have instances larger than 8000, specify -1. - -For a UDT with user-defined serialization specified, refers to the total size of the UDT in its serialized form as defined by the user. Consider a UDT with a property of a string of 10 characters (). When the UDT is serialized using a , the total size of the serialized string is 22 bytes: 2 bytes per Unicode UTF-16 character, multiplied by the maximum number of characters, plus 2 control bytes of overhead incurred from serializing a binary stream. So, when determining the value of , the total size of the serialized UDT must be considered: the size of the data serialized in binary form plus the overhead incurred by serialization. - -This property should not be used with `Native` serialization . - -]]> - - - - The SQL Server name of the user-defined type. - A value representing the SQL Server name of the user-defined type. - - property is not used within SQL Server, but is used by the Microsoft Visual Studio .NET Integrated Development Environment (IDE). - -]]> - - - - The name of the method used to validate instances of the user-defined type. - A representing the name of the method used to validate instances of the user-defined type. - - - - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient.Server/SystemDataAccessKind.xml b/doc/snippets/Microsoft.Data.SqlClient.Server/SystemDataAccessKind.xml deleted file mode 100644 index e2209278d7..0000000000 --- a/doc/snippets/Microsoft.Data.SqlClient.Server/SystemDataAccessKind.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Describes the type of access to system data for a user-defined method or function. - - and to indicate what type of access to system data the method or function has. - - Note that methods and functions are not allowed to make changes to the database, so the options for this enumeration are `None` (meaning no data-access performed by the method or function) and `Read` (meaning that the method or function performs read-only data-access operations, such as executing SELECT statements). - - ]]> - - - - The method or function does not access system data. - - - The method or function reads system data. - - - diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0cca6a4cb5..afcf503118 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -46,6 +46,7 @@ $(ManagedSourceCode)netfx\ $(ManagedSourceCode)netfx\src\Resources\ $(ManagedSourceCode)add-ons\ + $(RepoRoot)src\Microsoft.SqlServer.Server\ $(Artifacts)obj\ $(NetCoreSource)src\Common\src $(NetCoreSource)src\Common\tests diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 1ad71cac75..1c7d0e0571 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -1832,37 +1832,6 @@ public sealed class SqlConfigurableRetryFactory } namespace Microsoft.Data.SqlClient.Server { - /// - public enum DataAccessKind - { - /// - None = 0, - /// - Read = 1 - } - /// - public enum Format - { - /// - Unknown = 0, - /// - Native = 1, - /// - UserDefined = 2 - } - /// - public interface IBinarySerialize - { - /// - void Read(System.IO.BinaryReader r); - /// - void Write(System.IO.BinaryWriter w); - } - /// - public sealed partial class InvalidUdtException : System.SystemException - { - internal InvalidUdtException() { } - } /// public partial class SqlDataRecord : System.Data.IDataRecord { @@ -2033,44 +2002,6 @@ public virtual void SetValue(int ordinal, object value) { } /// public virtual int SetValues(params object[] values) { throw null; } } - /// - [System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] - public partial class SqlFacetAttribute : System.Attribute - { - /// - public SqlFacetAttribute() { } - /// - public bool IsFixedLength { get { throw null; } set { } } - /// - public bool IsNullable { get { throw null; } set { } } - /// - public int MaxSize { get { throw null; } set { } } - /// - public int Precision { get { throw null; } set { } } - /// - public int Scale { get { throw null; } set { } } - } - /// - [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false), System.SerializableAttribute] - public partial class SqlFunctionAttribute : System.Attribute - { - /// - public SqlFunctionAttribute() { } - /// - public bool IsDeterministic { get { throw null; } set { } } - /// - public DataAccessKind DataAccess { get { throw null; } set { } } - /// - public SystemDataAccessKind SystemDataAccess { get { throw null; } set { } } - /// - public bool IsPrecise { get { throw null; } set { } } - /// - public string Name { get { throw null; } set { } } - /// - public string TableDefinition { get { throw null; } set { } } - /// - public string FillRowMethodName { get { throw null; } set { } } - } /// public sealed partial class SqlMetaData { @@ -2209,69 +2140,6 @@ public SqlMetaData(string name, System.Data.SqlDbType dbType, System.Type userDe /// public static Microsoft.Data.SqlClient.Server.SqlMetaData InferFromValue(object value, string name) { throw null; } } - /// - [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false), System.SerializableAttribute] - public sealed partial class SqlMethodAttribute : SqlFunctionAttribute - { - /// - public SqlMethodAttribute() { } - /// - public bool OnNullCall { get { throw null; } set { } } - /// - public bool IsMutator { get { throw null; } set { } } - /// - public bool InvokeIfReceiverIsNull { get { throw null; } set { } } - } - /// - [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] - public sealed partial class SqlUserDefinedAggregateAttribute : System.Attribute - { - /// - public const int MaxByteSizeValue = 8000; - /// - public SqlUserDefinedAggregateAttribute(Format format) { } - /// - public int MaxByteSize { get { throw null; } set { } } - /// - public bool IsInvariantToDuplicates { get { throw null; } set { } } - /// - public bool IsInvariantToNulls { get { throw null; } set { } } - /// - public bool IsInvariantToOrder { get { throw null; } set { } } - /// - public bool IsNullIfEmpty { get { throw null; } set { } } - /// - public Format Format { get { throw null; } } - /// - public string Name { get { throw null; } set { } } - } - /// - [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] - public sealed partial class SqlUserDefinedTypeAttribute : System.Attribute - { - /// - public SqlUserDefinedTypeAttribute(Format format) { } - /// - public int MaxByteSize { get { throw null; } set { } } - /// - public bool IsFixedLength { get { throw null; } set { } } - /// - public bool IsByteOrdered { get { throw null; } set { } } - /// - public Format Format { get { throw null; } } - /// - public string ValidationMethodName { get { throw null; } set { } } - /// - public string Name { get { throw null; } set { } } - } - /// - public enum SystemDataAccessKind - { - /// - None = 0, - /// - Read = 1 - } } namespace Microsoft.Data.SqlClient.DataClassification { 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 71a3b17641..7612d1c7de 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -178,12 +178,6 @@ Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs - - Microsoft\Data\SqlClient\Server\IBinarySerialize.cs - - - Microsoft\Data\SqlClient\Server\InvalidUdtException.cs - Microsoft\Data\SqlClient\Server\ITypedGetters.cs @@ -238,18 +232,9 @@ Microsoft\Data\SqlClient\Server\SqlDataRecord.netcore.cs - - Microsoft\Data\SqlClient\Server\SqlFacetAttribute.cs - - - Microsoft\Data\SqlClient\Server\SqlFunctionAttribute.cs - Microsoft\Data\SqlClient\Server\SqlMetaData.cs - - Microsoft\Data\SqlClient\Server\SqlMethodAttribute.cs - Microsoft\Data\SqlClient\Server\SqlNormalizer.cs @@ -259,12 +244,6 @@ Microsoft\Data\SqlClient\SqlTransaction.Common.cs - - Microsoft\Data\SqlClient\Server\SqlUserDefinedAggregateAttribute.cs - - - Microsoft\Data\SqlClient\Server\SqlUserDefinedTypeAttribute.cs - Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs @@ -960,6 +939,9 @@ Microsoft.Data.SqlClient.SqlMetaData.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 08f32ef0bf..660b5934e0 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 @@ -21,7 +21,7 @@ using System.Threading.Tasks; using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Server; namespace Microsoft.Data.SqlClient { @@ -2347,7 +2347,7 @@ internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnD MemoryStream stm = new MemoryStream((byte[])value); - o = SerializationHelperSql9.Deserialize(stm, metaData.udt?.Type); + o = Server.SerializationHelperSql9.Deserialize(stm, metaData.udt?.Type); Debug.Assert(o != null, "object could NOT be created"); return o; @@ -2375,7 +2375,7 @@ internal byte[] GetBytes(object o, out Format format, out int maxSize) using (MemoryStream stm = new MemoryStream(maxSize < 0 ? 0 : maxSize)) { - SerializationHelperSql9.Serialize(stm, o); + Server.SerializationHelperSql9.Serialize(stm, o); retval = stm.ToArray(); } return retval; 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 ec1e898691..4b03949d93 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 @@ -9418,7 +9418,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet int maxSupportedSize = Is2008OrNewer ? int.MaxValue : short.MaxValue; byte[] udtVal = null; - Format format = Format.Native; + SqlServer.Server.Format format = SqlServer.Server.Format.Native; if (string.IsNullOrEmpty(param.UdtTypeName)) { diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 360d1c9c41..1bf34b20e8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -1897,41 +1897,6 @@ public sealed class SqlConfigurableRetryFactory } namespace Microsoft.Data.SqlClient.Server { - /// - public enum DataAccessKind - { - /// - None = 0, - /// - Read = 1 - } - /// - public enum Format - { - /// - Unknown = 0, - /// - Native = 1, - /// - UserDefined = 2 - } - /// - public interface IBinarySerialize - { - /// - void Read(System.IO.BinaryReader r); - /// - void Write(System.IO.BinaryWriter w); - } - - /// - [System.Serializable] - public sealed partial class InvalidUdtException : System.SystemException - { - internal InvalidUdtException() { } - private InvalidUdtException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - - } /// public partial class SqlDataRecord : System.Data.IDataRecord { @@ -2102,44 +2067,6 @@ public virtual void SetValue(int ordinal, object value) { } /// public virtual int SetValues(params object[] values) { throw null; } } - /// - [System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] - public partial class SqlFacetAttribute : System.Attribute - { - /// - public SqlFacetAttribute() { } - /// - public bool IsFixedLength { get { throw null; } set { } } - /// - public bool IsNullable { get { throw null; } set { } } - /// - public int MaxSize { get { throw null; } set { } } - /// - public int Precision { get { throw null; } set { } } - /// - public int Scale { get { throw null; } set { } } - } - /// - [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false), System.SerializableAttribute] - public partial class SqlFunctionAttribute : System.Attribute - { - /// - public SqlFunctionAttribute() { } - /// - public bool IsDeterministic { get { throw null; } set { } } - /// - public DataAccessKind DataAccess { get { throw null; } set { } } - /// - public SystemDataAccessKind SystemDataAccess { get { throw null; } set { } } - /// - public bool IsPrecise { get { throw null; } set { } } - /// - public string Name { get { throw null; } set { } } - /// - public string TableDefinition { get { throw null; } set { } } - /// - public string FillRowMethodName { get { throw null; } set { } } - } /// public sealed partial class SqlMetaData { @@ -2278,69 +2205,6 @@ public SqlMetaData(string name, System.Data.SqlDbType dbType, System.Type userDe /// public static Microsoft.Data.SqlClient.Server.SqlMetaData InferFromValue(object value, string name) { throw null; } } - /// - [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false), System.SerializableAttribute] - public sealed partial class SqlMethodAttribute : SqlFunctionAttribute - { - /// - public SqlMethodAttribute() { } - /// - public bool OnNullCall { get { throw null; } set { } } - /// - public bool IsMutator { get { throw null; } set { } } - /// - public bool InvokeIfReceiverIsNull { get { throw null; } set { } } - } - /// - [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] - public sealed partial class SqlUserDefinedAggregateAttribute : System.Attribute - { - /// - public const int MaxByteSizeValue = 8000; - /// - public SqlUserDefinedAggregateAttribute(Format format) { } - /// - public int MaxByteSize { get { throw null; } set { } } - /// - public bool IsInvariantToDuplicates { get { throw null; } set { } } - /// - public bool IsInvariantToNulls { get { throw null; } set { } } - /// - public bool IsInvariantToOrder { get { throw null; } set { } } - /// - public bool IsNullIfEmpty { get { throw null; } set { } } - /// - public Format Format { get { throw null; } } - /// - public string Name { get { throw null; } set { } } - } - /// - [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] - public sealed partial class SqlUserDefinedTypeAttribute : System.Attribute - { - /// - public SqlUserDefinedTypeAttribute(Format format) { } - /// - public int MaxByteSize { get { throw null; } set { } } - /// - public bool IsFixedLength { get { throw null; } set { } } - /// - public bool IsByteOrdered { get { throw null; } set { } } - /// - public Format Format { get { throw null; } } - /// - public string ValidationMethodName { get { throw null; } set { } } - /// - public string Name { get { throw null; } set { } } - } - /// - public enum SystemDataAccessKind - { - /// - None = 0, - /// - Read = 1 - } } namespace Microsoft.Data.SqlClient.DataClassification { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index ae1c8d50db..04acfcb612 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -26,7 +26,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Server; [assembly: InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey=" + Microsoft.Data.SqlClient.AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166 // NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available. @@ -2956,7 +2956,7 @@ internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnD MemoryStream stm = new MemoryStream((byte[])value); - o = SerializationHelperSql9.Deserialize(stm, metaData.udt?.Type); + o = Server.SerializationHelperSql9.Deserialize(stm, metaData.udt?.Type); Debug.Assert(o != null, "object could NOT be created"); return o; @@ -2965,12 +2965,12 @@ internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnD internal byte[] GetBytes(object o) { - Microsoft.SqlServer.Server.Format format = Microsoft.SqlServer.Server.Format.Native; + Format format = Format.Native; int maxSize = 0; return GetBytes(o, out format, out maxSize); } - internal byte[] GetBytes(object o, out Microsoft.SqlServer.Server.Format format, out int maxSize) + internal byte[] GetBytes(object o, out Format format, out int maxSize) { SqlUdtInfo attr = AssemblyCache.GetInfoFromType(o.GetType()); maxSize = attr.MaxByteSize; @@ -2985,7 +2985,7 @@ internal byte[] GetBytes(object o, out Microsoft.SqlServer.Server.Format format, using (MemoryStream stm = new MemoryStream(maxSize < 0 ? 0 : maxSize)) { - SerializationHelperSql9.Serialize(stm, o); + Server.SerializationHelperSql9.Serialize(stm, o); retval = stm.ToArray(); } return retval; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index c5ae9853df..1866aa7fb3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -11,9 +11,7 @@ using System.Globalization; using System.IO; using System.Runtime.CompilerServices; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; + using System.Security; using System.Security.Permissions; using System.Text; @@ -21,15 +19,16 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.SqlClient; -using Microsoft.Win32; using IsolationLevel = System.Data.IsolationLevel; using Microsoft.Identity.Client; +using Microsoft.SqlServer.Server; #if NETFRAMEWORK -using Microsoft.SqlServer.Server; +using Microsoft.Win32; using System.Reflection; -#else -using Microsoft.Data.SqlClient.Server; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; #endif namespace Microsoft.Data.Common diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/IBinarySerialize.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/IBinarySerialize.cs deleted file mode 100644 index 9b08f58d39..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/IBinarySerialize.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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.IO; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - // This interface is used by types that want full control over the - // binary serialization format. - public interface IBinarySerialize - { - /// - // Read from the specified binary reader. - void Read(BinaryReader r); - /// - // Write to the specified binary writer. - void Write(BinaryWriter w); - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/InvalidUdtException.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/InvalidUdtException.cs deleted file mode 100644 index 8a82a0868f..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/InvalidUdtException.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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 System.Runtime.Serialization; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - [Serializable] - public sealed class InvalidUdtException : SystemException - { - private const int InvalidUdtHResult = unchecked((int)0x80131937); - - internal InvalidUdtException() : base() - { - HResult = InvalidUdtHResult; - } - - internal InvalidUdtException(string message) : base(message) - { - HResult = InvalidUdtHResult; - } - - internal InvalidUdtException(string message, Exception innerException) : base(message, innerException) - { - HResult = InvalidUdtHResult; - } - - private InvalidUdtException(SerializationInfo si, StreamingContext sc) : base(si, sc) - { - } - - /// - [System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Flags = System.Security.Permissions.SecurityPermissionFlag.SerializationFormatter)] - public override void GetObjectData(SerializationInfo si, StreamingContext context) - { - base.GetObjectData(si, context); - } - - internal static InvalidUdtException Create(Type udtType, string resourceReason) - { - string reason = StringsHelper.GetString(resourceReason); - string message = StringsHelper.GetString(Strings.SqlUdt_InvalidUdtMessage, udtType.FullName, reason); - InvalidUdtException e = new InvalidUdtException(message); - ADP.TraceExceptionAsReturnValue(e); - return e; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFacetAttribute.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFacetAttribute.cs deleted file mode 100644 index da80e953b8..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFacetAttribute.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] - public class SqlFacetAttribute : Attribute - { - /// - public SqlFacetAttribute() { } - - /// - public bool IsFixedLength - { - get; - set; - } - - /// - public int MaxSize - { - get; - set; - } - - /// - public int Precision - { - get; - set; - } - - /// - public int Scale - { - get; - set; - } - - /// - public bool IsNullable - { - get; - set; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFunctionAttribute.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFunctionAttribute.cs deleted file mode 100644 index 87b84c7ba1..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlFunctionAttribute.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - [Serializable] - public enum DataAccessKind - { - /// - None = 0, - /// - Read = 1, - } - - /// - [Serializable] - public enum SystemDataAccessKind - { - /// - None = 0, - /// - Read = 1, - } - - /// - // sql specific attribute - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false), Serializable] - public class SqlFunctionAttribute : Attribute - { - private bool _isDeterministic; - private DataAccessKind _dataAccess; - private SystemDataAccessKind _systemDataAccess; - private bool _isPrecise; - private string _name; - private string _tableDefinition; - private string _fillRowMethodName; - - /// - public SqlFunctionAttribute() - { - // default values - _isDeterministic = false; - _dataAccess = DataAccessKind.None; - _systemDataAccess = SystemDataAccessKind.None; - _isPrecise = false; - _name = null; - _tableDefinition = null; - _fillRowMethodName = null; - } - - /// - public bool IsDeterministic - { - get => _isDeterministic; - set => _isDeterministic = value; - } - - /// - public DataAccessKind DataAccess - { - get => _dataAccess; - set => _dataAccess = value; - } - - /// - public SystemDataAccessKind SystemDataAccess - { - get => _systemDataAccess; - set => _systemDataAccess = value; - } - - /// - public bool IsPrecise - { - get => _isPrecise; - set => _isPrecise = value; - } - - /// - public string Name - { - get => _name; - set => _name = value; - } - - /// - public string TableDefinition - { - get => _tableDefinition; - set => _tableDefinition = value; - } - - /// - public string FillRowMethodName - { - get => _fillRowMethodName; - set => _fillRowMethodName = value; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMethodAttribute.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMethodAttribute.cs deleted file mode 100644 index 4b29c6df55..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMethodAttribute.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false), Serializable] - public sealed class SqlMethodAttribute : SqlFunctionAttribute - { - private bool _isCalledOnNullInputs; - private bool _isMutator; - private bool _shouldInvokeIfReceiverIsNull; - - /// - public SqlMethodAttribute() - { - // default values - _isCalledOnNullInputs = true; - _isMutator = false; - _shouldInvokeIfReceiverIsNull = false; - } - - /// - public bool OnNullCall - { - get => _isCalledOnNullInputs; - set => _isCalledOnNullInputs = value; - } - - /// - public bool IsMutator - { - get => _isMutator; - set => _isMutator = value; - } - - /// - public bool InvokeIfReceiverIsNull - { - get => _shouldInvokeIfReceiverIsNull; - set => _shouldInvokeIfReceiverIsNull = value; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs index 9af5e5ef86..1f9a4181f0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs @@ -7,9 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using Microsoft.Data.Common; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#endif namespace Microsoft.Data.SqlClient.Server { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedAggregateAttribute.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedAggregateAttribute.cs deleted file mode 100644 index 861cc8cef0..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedAggregateAttribute.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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 Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] - public sealed class SqlUserDefinedAggregateAttribute : Attribute - { - private int _maxByteSize; - private bool _isInvariantToDup; - private bool _isInvariantToNulls; - private bool _isInvariantToOrder = true; - private bool _isNullIfEmpty; - private Format _format; - private string _name; - - /// - // The maximum value for the maxbytesize field, in bytes. - public const int MaxByteSizeValue = 8000; - - /// - // A required attribute on all UD Aggs, used to indicate that the - // given type is a UD Agg, and its storage format. - public SqlUserDefinedAggregateAttribute(Format format) - { - switch (format) - { - case Format.Unknown: - throw ADP.NotSupportedUserDefinedTypeSerializationFormat(format, nameof(format)); - case Format.Native: - case Format.UserDefined: - _format = format; - break; - default: - throw ADP.InvalidUserDefinedTypeSerializationFormat(format); - } - } - - /// - // The maximum size of this instance, in bytes. Does not have to be - // specified for Native format serialization. The maximum value - // for this property is specified by MaxByteSizeValue. - public int MaxByteSize - { - get - { - return _maxByteSize; - } - set - { - // MaxByteSize of -1 means 2GB and is valid, as well as 0 to MaxByteSizeValue - if (value < -1 || value > MaxByteSizeValue) - { - throw ADP.ArgumentOutOfRange(StringsHelper.GetString(Strings.SQLUDT_MaxByteSizeValue), nameof(MaxByteSize), value); - } - _maxByteSize = value; - } - } - - /// - public bool IsInvariantToDuplicates - { - get - { - return _isInvariantToDup; - } - set - { - _isInvariantToDup = value; - } - } - - /// - public bool IsInvariantToNulls - { - get - { - return _isInvariantToNulls; - } - set - { - _isInvariantToNulls = value; - } - } - - /// - public bool IsInvariantToOrder - { - get - { - return _isInvariantToOrder; - } - set - { - _isInvariantToOrder = value; - } - } - - /// - public bool IsNullIfEmpty - { - get - { - return _isNullIfEmpty; - } - set - { - _isNullIfEmpty = value; - } - } - - /// - // The on-disk format for this type. - public Format Format => _format; - - /// - public string Name - { - get - { - return _name; - } - set - { - _name = value; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedTypeAttribute.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedTypeAttribute.cs deleted file mode 100644 index 1a399d2749..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlUserDefinedTypeAttribute.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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 Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - public enum Format - { - /// - Unknown = 0, - /// - Native = 1, - /// - UserDefined = 2, - } - - /// - // This custom attribute indicates that the given type is - // a SqlServer udt. The properties on the attribute reflect the - // physical attributes that will be used when the type is registered - // with SqlServer. - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] - public sealed class SqlUserDefinedTypeAttribute : Attribute - { - private int _maxByteSize; - private bool _isFixedLength; - private bool _isByteOrdered; - private Format _format; - private string _name; - - // The maximum value for the maxbytesize field, in bytes. - internal const int Sql2005MaxByteSizeValue = 8000; - private string _validationMethodName = null; - - /// - // A required attribute on all udts, used to indicate that the - // given type is a udt, and its storage format. - public SqlUserDefinedTypeAttribute(Format format) - { - switch (format) - { - case Format.Unknown: - throw ADP.NotSupportedUserDefinedTypeSerializationFormat(format, nameof(format)); - case Format.Native: - case Format.UserDefined: - _format = format; - break; - default: - throw ADP.InvalidUserDefinedTypeSerializationFormat(format); - } - } - - /// - // The maximum size of this instance, in bytes. Does not have to be - // specified for Native serialization. The maximum value - // for this property is specified by MaxByteSizeValue. - public int MaxByteSize - { - get - { - return _maxByteSize; - } - set - { - if (value < -1) - { - throw ADP.ArgumentOutOfRange(nameof(MaxByteSize)); - } - _maxByteSize = value; - } - } - - /// - // Are all instances of this udt the same size on disk? - public bool IsFixedLength - { - get - { - return _isFixedLength; - } - set - { - _isFixedLength = value; - } - } - - /// - // Is this type byte ordered, i.e. is the on disk representation - // consistent with the ordering semantics for this type? - // If true, the binary representation of the type will be used - // in comparison by SqlServer. This property enables indexing on the - // udt and faster comparisons. - public bool IsByteOrdered - { - get - { - return _isByteOrdered; - } - set - { - _isByteOrdered = value; - } - } - - /// - // The on-disk format for this type. - public Format Format => _format; - - /// - // An Optional method used to validate this UDT - public string ValidationMethodName - { - get - { - return _validationMethodName; - } - set - { - _validationMethodName = value; - } - } - - /// - public string Name - { - get - { - return _name; - } - set - { - _name = value; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs index a498ddc54d..bb474a0465 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs @@ -15,10 +15,7 @@ using System.IO; using System.Xml; using Microsoft.Data.Common; -using Microsoft.Data.SqlClient.Server; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#endif namespace Microsoft.Data.SqlClient { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs index c15808bc8a..4ac81a9844 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs @@ -5,11 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.Data.Common; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif namespace Microsoft.Data.SqlClient { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index 721d8a5c91..5b1ec82808 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -97,6 +97,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs index 99bfc55140..77546de525 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs @@ -390,10 +390,6 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } -#if NETFRAMEWORK [SqlServer.Server.SqlUserDefinedType(SqlServer.Server.Format.UserDefined)] -#else - [SqlUserDefinedType(Format.UserDefined)] -#endif public class TestUdt {} } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlFacetAttributeTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlFacetAttributeTest.cs index d37e20667d..d57cf853e9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlFacetAttributeTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlFacetAttributeTest.cs @@ -2,11 +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. -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif using Xunit; namespace Microsoft.Data.SqlClient.Tests diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs index 8d3c46b75a..a9123e1582 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs @@ -511,6 +511,11 @@ public void DecimalConstructorWithPrecisionOutofRange2_Throws() [InlineData(SqlDbType.Udt, typeof(Address))] public void GenericConstructorWithoutXmlSchema(SqlDbType dbType, Type udt) { + if (udt != null) + { + Type t = udt.GetInterface("IBinarySerialize", true); + Assert.Equal(typeof(Microsoft.SqlServer.Server.IBinarySerialize), t); + } SqlMetaData metaData = new SqlMetaData("col2", dbType, 16, 2, 2, 2, SqlCompareOptions.IgnoreCase, udt, true, true, SortOrder.Ascending, 0); Assert.Equal(dbType, metaData.SqlDbType); Assert.True(metaData.UseServerDefault); @@ -715,14 +720,10 @@ public void UdtConstructorTest() [Fact] public static void InvalidUdtEcxeption_Throws() { - var e = Assert.Throws -#if NETFRAMEWORK - -#else - -#endif - (() => new SqlMetaData("col1", SqlDbType.Udt, typeof(int), "UdtTestDb.dbo.Address")); - Assert.Equal("'System.Int32' is an invalid user defined type, reason: no UDT attribute.", e.Message); + SqlServer.Server.InvalidUdtException e = + Assert.Throws (() => new SqlMetaData("col1", SqlDbType.Udt, typeof(int), "UdtTestDb.dbo.Address")); + + Assert.Equal("'System.Int32' is an invalid user defined type, reason: no UDT attribute.", e.Message); } [Fact] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 48288c80ca..aef505ed1b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -302,6 +302,7 @@ + @@ -320,7 +321,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.cs index 01057ebb2e..ed1e683002 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.cs @@ -5,11 +5,7 @@ using System; using System.Data.SqlTypes; using System.IO; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif [Serializable] [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = 500)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj index 5fcf9a6d08..350733ed9b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj @@ -12,6 +12,7 @@ + - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.cs index 0964e14847..e44f8d3abe 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.cs @@ -6,11 +6,7 @@ using System.Data.SqlTypes; using System.IO; using System.Text; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif [Serializable] [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = 30)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj index 5b0d1542d3..5f61786bb3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj @@ -12,6 +12,7 @@ + - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Point1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Point1.cs index 5392e57818..7ccc5c6d4b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Point1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Point1.cs @@ -6,11 +6,7 @@ using System.Data.SqlTypes; using System.IO; using System.Text; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif [Serializable] [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = true, MaxByteSize = 9)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Line.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Line.cs index addc6e266f..d6efa7ddf5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Line.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Line.cs @@ -6,11 +6,7 @@ using System.Data.SqlTypes; using System.IO; using System.Text; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif [Serializable] [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = 20)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Point.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Point.cs index b1b7a4ea7f..2dbf5e510c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Point.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Point.cs @@ -6,11 +6,7 @@ using System.Data.SqlTypes; using System.IO; using System.Text; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif [Serializable] [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = true, MaxByteSize = 9)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj index b2394ad310..e0ea118c00 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj @@ -12,6 +12,7 @@ + - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.cs index 4bba8c88ba..e026674602 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.cs @@ -5,11 +5,7 @@ using System; using System.Data.SqlTypes; using System.Globalization; -#if NETFRAMEWORK using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif namespace Microsoft.Samples.SqlServer { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj index bf3da8f47a..5e8ce85bd4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj @@ -12,6 +12,7 @@ + - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs index e0e30dd2e5..1f07242ee3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs @@ -6,12 +6,8 @@ using System.Data; using System.Data.SqlTypes; using System.Text; -#if NETFRAMEWORK -using Microsoft.SqlServer.Server; -#else -using Microsoft.Data.SqlClient.Server; -#endif using Xunit; +using Microsoft.SqlServer.Server; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -574,7 +570,7 @@ public void TestSchemaTable() public void TestSqlUserDefinedAggregateAttributeMaxByteSize() { Func create - = (size) => new SqlUserDefinedAggregateAttribute(Format.UserDefined) { MaxByteSize = size }; + = (size) => new(Format.UserDefined) { MaxByteSize = size }; SqlUserDefinedAggregateAttribute attribute1 = create(-1); SqlUserDefinedAggregateAttribute attribute2 = create(0); diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 95122fdfc6..1aa6b58178 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -64,7 +64,6 @@ 3.1.1 5.2.6 15.9.0 - 3.1.0 13.0.1 4.3.0 4.3.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 2759aafe6c..ea863b6f83 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -47,6 +47,7 @@ When using NuGet 3.x this package requires at least version 3.4. + @@ -65,6 +66,7 @@ When using NuGet 3.x this package requires at least version 3.4. + @@ -83,6 +85,7 @@ When using NuGet 3.x this package requires at least version 3.4. + From 10e373a571b67bb9b5a1a8191fb8bdd8f631b273 Mon Sep 17 00:00:00 2001 From: SqlClient DevOps Date: Wed, 15 Jun 2022 16:52:28 +0000 Subject: [PATCH 24/35] [Scheduled Run] Localized resource files from OneLocBuild --- .../netfx/src/Resources/Strings.it.resx | 4 ++-- .../netfx/src/Resources/Strings.ja.resx | 4 ++-- .../netfx/src/Resources/Strings.ru.resx | 4 ++-- .../netfx/src/Resources/Strings.zh-Hant.resx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index 911bcf77b6..1ea27733d9 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -4621,9 +4621,9 @@ Timeout della connessione durante il recupero di un token di accesso tramite il metodo di autenticazione '{0}'. Ultimo errore: {1}: {2} - The service principal name (SPN) of the failover partner. + Nome principale dell'entità servizio (SPN) del partner di failover. - The service principal name (SPN) of the server. + Nome principale dell'entità servizio (SPN) del server. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index 80c8acb028..10916f4e40 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -4621,9 +4621,9 @@ 認証方法 '{0}' によるアクセス トークンの取得中に接続がタイムアウトしました。前回のエラー: {1}: {2} - The service principal name (SPN) of the failover partner. + フェールオーバー パートナーのサービス プリンシパル名 (SPN)。 - The service principal name (SPN) of the server. + サーバーのサービス プリンシパル名 (SPN)。 \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index 51703de962..511fdd00ef 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -4621,9 +4621,9 @@ Истекло время ожидания подключения при получении маркера доступа с помощью метода проверки подлинности "{0}". Последняя ошибка: {1}: {2} - The service principal name (SPN) of the failover partner. + Имя субъекта-службы (SPN) партнера по обеспечению отработки отказа. - The service principal name (SPN) of the server. + Имя субъекта-службы (SPN) сервера. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index f4bc5c2f41..efc451484a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -4621,9 +4621,9 @@ 使用 '{0}' 驗證方法擷取存取權杖時已逾時。上次錯誤: {1}: {2} - The service principal name (SPN) of the failover partner. + 容錯移轉夥伴的服務主體名稱 (SPN)。 - The service principal name (SPN) of the server. + 伺服器的服務主體名稱 (SPN)。 \ No newline at end of file From 778f86e54f5b88e7e063b2bc345b75800c874f60 Mon Sep 17 00:00:00 2001 From: Javad Date: Wed, 15 Jun 2022 13:44:12 -0700 Subject: [PATCH 25/35] Feature | Adding TDS 8 support (#1608) --- .../SqlConnection.xml | 5 +- .../SqlConnectionEncryptOption.xml | 46 +++ .../SqlConnectionStringBuilder.xml | 8 +- src/Microsoft.Data.SqlClient.sln | 1 + .../netcore/ref/Microsoft.Data.SqlClient.cs | 34 +- .../Interop/SNINativeMethodWrapper.Windows.cs | 44 ++- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../Microsoft/Data/SqlClient/SNI/SNIHandle.cs | 32 +- .../Data/SqlClient/SNI/SNINpHandle.cs | 33 +- .../Data/SqlClient/SNI/SNIPhysicalHandle.cs | 3 +- .../Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 45 ++- .../Data/SqlClient/SNI/SNITcpHandle.cs | 57 +++- .../SqlClient/SqlInternalConnectionTds.cs | 3 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 170 ++++++---- .../Data/SqlClient/TdsParserSafeHandles.cs | 8 +- .../Data/SqlClient/TdsParserStateObject.cs | 33 +- .../SqlClient/TdsParserStateObjectManaged.cs | 28 +- .../SqlClient/TdsParserStateObjectNative.cs | 30 +- .../netfx/ref/Microsoft.Data.SqlClient.cs | 30 +- .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/Interop/SNINativeMethodWrapper.cs | 38 ++- .../SqlClient/SqlInternalConnectionTds.cs | 29 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 291 ++++++++++-------- .../Data/SqlClient/TdsParserSafeHandles.cs | 6 +- .../Data/SqlClient/TdsParserStateObject.cs | 32 +- .../Data/Common/DbConnectionOptions.Common.cs | 5 +- .../Data/Common/DbConnectionStringCommon.cs | 21 +- .../SqlClient/SqlConnectionEncryptOption.cs | 94 ++++++ .../Data/SqlClient/SqlConnectionString.cs | 54 +++- .../SqlClient/SqlConnectionStringBuilder.cs | 45 ++- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 4 + .../AlwaysEncryptedTests/TestFixtures.cs | 2 +- .../SqlConnectionBasicTests.cs | 2 +- .../SqlConnectionStringBuilderTest.cs | 63 +++- .../FunctionalTests/SqlConnectionTest.cs | 47 +-- .../tests/FunctionalTests/TestTdsServer.cs | 2 +- .../SQL/LocalDBTest/LocalDBTest.cs | 2 +- .../ManualTests/TracingTests/TestTdsServer.cs | 2 +- tools/props/Versions.props | 4 +- tools/specs/Microsoft.Data.SqlClient.nuspec | 8 +- 40 files changed, 1020 insertions(+), 347 deletions(-) create mode 100644 doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index c2bf5ac5df..7e007f9180 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -537,9 +537,10 @@ End Module |Current Language

-or-

Language|N/A|Sets the language used for database server warning or error messages.

The language name can be 128 characters or less.| |Data Source

-or-

Server

-or-

Address

-or-

Addr

-or-

Network Address|N/A|The name or network address of the instance of SQL Server to which to connect. The port number can be specified after the server name:

`server=tcp:servername, portnumber`

When specifying a local instance, always use (local). To force a protocol, add one of the following prefixes:

`np:(local), tcp:(local), lpc:(local)`

You can also connect to a LocalDB database as follows:

`server=(localdb)\\myInstance`

For more information about LocalDB, see [SqlClient Support for LocalDB](/sql/connect/ado-net/sql/sqlclient-support-localdb).

**Data Source** must use the TCP format or the Named Pipes format.

TCP format is as follows:

- tcp:\\\
- tcp:\,\

The TCP format must start with the prefix "tcp:" and is followed by the database instance, as specified by a host name and an instance name. This format is not applicable when connecting to Azure SQL Database. TCP is automatically selected for connections to Azure SQL Database when no protocol is specified.

The host name MUST be specified in one of the following ways:

- NetBIOSName
- IPv4Address
- IPv6Address

The instance name is used to resolve to a particular TCP/IP port number on which a database instance is hosted. Alternatively, specifying a TCP/IP port number directly is also allowed. If both instance name and port number are not present, the default database instance is used.

The Named Pipes format is as follows:

- np:\\\\\pipe\\

The Named Pipes format MUST start with the prefix "np:" and is followed by a named pipe name.

The host name MUST be specified in one of the following ways:

- NetBIOSName
- IPv4Address
- IPv6Address

The pipe name is used to identify the database instance to which the .NET application will connect.

If the value of the **Network** key is specified, the prefixes "tcp:" and "np:" should not be specified. **Note:** You can force the use of TCP instead of shared memory, either by prefixing **tcp:** to the server name in the connection string, or by using **localhost**.| |Enclave Attestation Url|N/A|Gets or sets the enclave attestation URL to be used with enclave based Always Encrypted.| -|Encrypt|'false' in v3.1 and older
'true' in v4.0 and newer|When `true`, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `TrustServerCertificate` is false and `Encrypt` is true, the server name (or IP address) in a SQL Server SSL certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| +|Encrypt|'true' in 4.0 and above

'false' in 3.x and below|Recognized values are:
versions 1 - 4: `true`/`yes` and `false`/`no`
versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `Encrypt` is true or strict and `TrustServerCertificate` is false, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| |Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.| |Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.

If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".

The server name can be 128 characters or less.

If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.| +|Host Name In Certificate

-or-

HostNameInCertificate|N/A|Available starting in version 5.0.

The host name to use when validating the server certificate. When not specified, the server name from the Data Source is used for certificate validation.| |Initial Catalog

-or-

Database|N/A|The name of the database.

The database name can be 128 characters or less.| |Integrated Security

-or-

Trusted_Connection|'false'|When `false`, User ID and Password are specified in the connection. When `true`, the current Windows account credentials are used for authentication.

Recognized values are `true`, `false`, `yes`, `no`, and `sspi` (strongly recommended), which is equivalent to `true`.

If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used.

is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`).| |IP Address Preference

-or-

IPAddressPreference|IPv4First|The IP address family preference when establishing TCP connections. If `Transparent Network IP Resolution` (in .NET Framework) or `Multi Subnet Failover` is set to true, this setting has no effect. Supported values include:

`IPAddressPreference=IPv4First`

`IPAddressPreference=IPv6First`

`IPAddressPreference=UsePlatformDefault`| @@ -557,7 +558,7 @@ End Module |Replication|'false'|`true` if replication is supported using the connection.| |Transaction Binding|Implicit Unbind|Controls connection association with an enlisted `System.Transactions` transaction.

Possible values are:

`Transaction Binding=Implicit Unbind;`

`Transaction Binding=Explicit Unbind;`

Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The `System.Transactions.Transaction.Current` property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode.

If the system ends the transaction (in the scope of a using block) before the last command completes, it will throw .

Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit `SqlConnection.TransactionEnlist(null)` is called. Beginning in .NET Framework 4.0, changes to Implicit Unbind make Explicit Unbind obsolete. An `InvalidOperationException` is thrown if `Transaction.Current` is not the enlisted transaction or if the enlisted transaction is not active.| |Transparent Network IP Resolution

-or-

TransparentNetworkIPResolution|See description.|When the value of this key is set to `true`, the application is required to retrieve all IP addresses for a particular DNS entry and attempt to connect with the first one in the list. If the connection is not established within 0.5 seconds, the application will try to connect to all others in parallel. When the first answers, the application will establish the connection with the respondent IP address.

If the `MultiSubnetFailover` key is set to `true`, `TransparentNetworkIPResolution` is ignored.

If the `Failover Partner` key is set, `TransparentNetworkIPResolution` is ignored.

The value of this key must be `true`, `false`, `yes`, or `no`.

A value of `yes` is treated the same as a value of `true`.

A value of `no` is treated the same as a value of `false`.

The default values are as follows:

  • `false` when:

    • Connecting to Azure SQL Database where the data source ends with:

      • .database.chinacloudapi.cn
      • .database.usgovcloudapi.net
      • .database.cloudapi.de
      • .database.windows.net
    • `Authentication` is 'Active Directory Password' or 'Active Directory Integrated'
  • `true` in all other cases.
| -|Trust Server Certificate

-or-

TrustServerCertificate|'false'|When set to `true`, SSL is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to `true` and Encrypt is set to `false`, the channel is not encrypted. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).| +|Trust Server Certificate

-or-

TrustServerCertificate|'false'|When set to `true`, TLS is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to `true` and Encrypt is set to `false`, the channel is not encrypted. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).| |Type System Version|N/A|A string value that indicates the type system the application expects. The functionality available to a client application is dependent on the version of SQL Server and the compatibility level of the database. Explicitly setting the type system version that the client application was written for avoids potential problems that could cause an application to break if a different version of SQL Server is used. **Note:** The type system version cannot be set for common language runtime (CLR) code executing in-process in SQL Server. For more information, see [SQL Server Common Language Runtime Integration](/dotnet/framework/data/adonet/sql/sql-server-common-language-runtime-integration).

Possible values are:

`Type System Version=SQL Server 2012;`

`Type System Version=SQL Server 2008;`

`Type System Version=SQL Server 2005;`

`Type System Version=Latest;`

`Type System Version=SQL Server 2012;` specifies that the application will require version 11.0.0.0 of Microsoft.SqlServer.Types.dll. The other `Type System Version` settings will require version 10.0.0.0 of Microsoft.SqlServer.Types.dll.

`Latest` is obsolete and should not be used. `Latest` is equivalent to `Type System Version=SQL Server 2008;`.| |User ID

-or-

UID

-or-

User|N/A|The SQL Server login account. Not recommended. To maintain a high level of security, we strongly recommend that you use the `Integrated Security` or `Trusted_Connection` keywords instead. is a more secure way to specify credentials for a connection that uses SQL Server Authentication.

The user ID must be 128 characters or less.| |User Instance|'false'|A value that indicates whether to redirect the connection from the default SQL Server Express instance to a runtime-initiated instance running under the account of the caller.| diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml new file mode 100644 index 0000000000..496a8e4103 --- /dev/null +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml @@ -0,0 +1,46 @@ + + + + + + These options are used to control encryption behavior of the communication between the server and the client. + + Implicit conversions have been added to maintain backwards compatibility with boolean behahavior for the property. When converting from a boolean, a value of converts to and a value of converts to . When converting to a boolean, and convert to and converts . + + + + Specifies that TLS encryption is optional when connecting to the server. If the server requires encryption, encryption will be negotiated. + + + Specifies that TLS encryption is required when connecting to the server. If the server doesn't support encryption, the connection will fail. + + + Enables and requires TDS 8.0, TLS encryption to the server. If the server doesn't support TDS 8.0, TLS encryption, the connection will fail. + + + The boolean value to be used for implicit comparison. + + Enables implicit converstion of a boolean to a . A value of converts to . A value of converts to . + + + + The value to be used for implicit comparison. + + Enables implicit converstion of a to a boolean. and convert to . converts to . + + + + Returns the string value of . + + + + Compares the representation of to another . + + + + + Returns the hash code of the value. + + + + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 72cf7aec1e..a73dfdaa9c 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -410,15 +410,15 @@ If the value of the **Network** key is specified, the prefixes "tcp:" and "np:" The enclave attestation URL. - Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. - The value of the property, or if none has been supplied. + Gets or sets a value that indicates whether TLS encryption is required for all data sent between the client and server. + The value of the property. [!NOTE] > Starting from **version 4.0**, the default value of the property `Encrypt` is set to `true`. @@ -898,7 +898,7 @@ Database = AdventureWorks ## Remarks This property corresponds to the "Trust Server Certificate" and "TrustServerCertificate" keys within the connection string. - When `Trust Server Certificate` is set to `true`, the transport layer will use SSL to encrypt the channel and bypass walking the certificate chain to validate trust. If `Trust Server Certificate` is set to `true` and encryption is enforced by target server, the encryption level specified on the server will be used even if `Encrypt` is set to `false`. The connection will fail otherwise. + When `Trust Server Certificate` is set to `true`, the transport layer will use TLS to encrypt the channel and bypass walking the certificate chain to validate trust. If `Trust Server Certificate` is set to `true` and encryption is enforced by target server, the encryption level specified on the server will be used even if `Encrypt` is set to `false`. The connection will fail otherwise. For more information, see [Encryption Hierarchy](/sql/relational-databases/security/encryption/encryption-hierarchy) and [Using Encryption Without Validation](/sql/relational-databases/native-client/features/using-encryption-without-validation). diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 18cc04fbc8..c5b34945a0 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -120,6 +120,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient", ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnection.xml ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionAttestationProtocol.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionAttestationProtocol.xml ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionColumnEncryptionSetting.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionColumnEncryptionSetting.xml + ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionEncryptOption.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionEncryptOption.xml ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionStringBuilder.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionStringBuilder.xml ..\doc\snippets\Microsoft.Data.SqlClient\SqlCredential.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlCredential.xml ..\doc\snippets\Microsoft.Data.SqlClient\SqlDataAdapter.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlDataAdapter.xml diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 1c7d0e0571..ca6ac9d845 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -383,6 +383,7 @@ public void Remove(SqlBulkCopyColumnOrderHint columnOrderHint) { } /// public new void RemoveAt(int index) { } } + /// [System.FlagsAttribute] public enum SqlBulkCopyOptions @@ -502,6 +503,33 @@ public enum SqlConnectionIPAddressPreference /// UsePlatformDefault = 2 } + /// + public sealed class SqlConnectionEncryptOption + { + /// + public static SqlConnectionEncryptOption Optional => throw null; + + /// + public static SqlConnectionEncryptOption Mandatory => throw null; + + /// + public static SqlConnectionEncryptOption Strict => throw null; + + /// + public static implicit operator SqlConnectionEncryptOption(bool value) => throw null; + + /// + public static implicit operator bool(SqlConnectionEncryptOption value) => throw null; + + /// + public override string ToString() { throw null; } + + /// + public override bool Equals(object obj) { throw null; } + + /// + public override int GetHashCode() { throw null; } + } /// public partial class SqlColumnEncryptionCertificateStoreProvider : Microsoft.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider { @@ -996,7 +1024,11 @@ public SqlConnectionStringBuilder(string connectionString) { } /// [System.ComponentModel.DisplayNameAttribute("Encrypt")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] - public bool Encrypt { get { throw null; } set { } } + public SqlConnectionEncryptOption Encrypt { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Host Name In Certificate")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string HostNameInCertificate { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Enlist")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs index 4e84fdc406..dd189d178a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs @@ -19,6 +19,8 @@ internal static partial class SNINativeMethodWrapper [UnmanagedFunctionPointer(CallingConvention.StdCall)] internal delegate void SqlAsyncCallbackDelegate(IntPtr m_ConsKey, IntPtr pPacket, uint dwError); + internal delegate IntPtr SqlClientCertificateDelegate(IntPtr pCallbackContext); + internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer internal static int SniMaxComposedSpnLength @@ -43,6 +45,21 @@ internal struct ConsumerInfo internal IntPtr key; } + [StructLayout(LayoutKind.Sequential)] + internal struct AuthProviderInfo + { + public uint flags; + [MarshalAs(UnmanagedType.Bool)] + public bool tlsFirst; + [MarshalAs(UnmanagedType.LPWStr)] + public string certId; + [MarshalAs(UnmanagedType.Bool)] + public bool certHash; + public object clientCertificateCallbackContext; + public SqlClientCertificateDelegate clientCertificateCallback; + }; + + internal enum ConsumerNumber { SNI_Consumer_SNI, @@ -148,6 +165,8 @@ private unsafe struct SNI_CLIENT_CONSUMER_INFO public Sni_Consumer_Info ConsumerInfo; [MarshalAs(UnmanagedType.LPWStr)] public string wszConnectionString; + [MarshalAs(UnmanagedType.LPWStr)] + public string HostNameInCertificate; public PrefixEnum networkLibrary; public byte* szSPN; public uint cchSPN; @@ -202,6 +221,9 @@ internal struct SNI_Error [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] internal static extern uint SNIAddProvider(SNIHandle pConn, ProviderEnum ProvNum, [In] ref uint pInfo); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] + internal static extern uint SNIAddProvider(SNIHandle pConn, ProviderEnum ProvNum, [In] ref AuthProviderInfo pInfo); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICheckConnectionWrapper")] internal static extern uint SNICheckConnection([In] SNIHandle pConn); @@ -323,12 +345,12 @@ internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) { return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_CONNID, out connId); } - + internal static uint SniGetProviderNumber(SNIHandle pConn, ref ProviderEnum provNum) { return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PROVIDERNUM, out provNum); } - + internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum) { return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PEERPORT, out portNum); @@ -369,9 +391,21 @@ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHan return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo); } - internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, - bool fSync, int timeout, bool fParallel, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) + internal static unsafe uint SNIOpenSyncEx( + ConsumerInfo consumerInfo, + string constring, + ref IntPtr pConn, + byte[] spnBuffer, + byte[] instanceName, + bool fOverrideCache, + bool fSync, + int timeout, + bool fParallel, + SqlConnectionIPAddressPreference ipPreference, + SQLDNSInfo cachedDNSInfo, + string hostNameInCertificate) { + fixed (byte* pin_instanceName = &instanceName[0]) { SNI_CLIENT_CONSUMER_INFO clientConsumerInfo = new SNI_CLIENT_CONSUMER_INFO(); @@ -380,8 +414,8 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons MarshalConsumerInfo(consumerInfo, ref clientConsumerInfo.ConsumerInfo); clientConsumerInfo.wszConnectionString = constring; + clientConsumerInfo.HostNameInCertificate = hostNameInCertificate; clientConsumerInfo.networkLibrary = PrefixEnum.UNKNOWN_PREFIX; - clientConsumerInfo.szInstanceName = pin_instanceName; clientConsumerInfo.cchInstanceName = (uint)instanceName.Length; clientConsumerInfo.fOverrideLastConnectCache = fOverrideCache; 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 7612d1c7de..eed46e0d1c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -328,6 +328,9 @@ Microsoft\Data\SqlClient\SqlCommandSet.cs + + Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs + Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs index 7eec717a8a..1d3d731e0b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs @@ -3,7 +3,12 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Net.Security; using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.Data.SqlClient.SNI { @@ -15,13 +20,38 @@ internal abstract class SNIHandle /// /// Exclude TLS 1.3 (not fully supported). /// - protected readonly SslProtocols SupportedProtocols = LocalAppContextSwitches.UseSystemDefaultSecureProtocols ? SslProtocols.None : SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls + protected static readonly SslProtocols s_supportedProtocols = LocalAppContextSwitches.UseSystemDefaultSecureProtocols ? SslProtocols.None : SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls //protected readonly SslProtocols SupportedProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls #pragma warning disable CS0618 // Type or member is obsolete | SslProtocols.Ssl2 | SslProtocols.Ssl3 #pragma warning restore CS0618 // Type or member is obsolete ; +#if !NETSTANDARD2_0 + protected static readonly List s_tdsProtocols = new List(1) { new(TdsEnums.TDS8_Protocol) }; + + protected static async Task AuthenticateAsClientAsync(SslStream sslStream, string serverNameIndication, X509CertificateCollection certificate, CancellationToken token) + { + SslClientAuthenticationOptions sslClientOptions = new() + { + TargetHost = serverNameIndication, + ApplicationProtocols = s_tdsProtocols, + EnabledSslProtocols = s_supportedProtocols, + ClientCertificates = certificate + }; + await sslStream.AuthenticateAsClientAsync(sslClientOptions, token); + } +#endif + + protected static void AuthenticateAsClient(SslStream sslStream, string serverNameIndication, X509CertificateCollection certificate) + { +#if !NETSTANDARD2_0 + AuthenticateAsClientAsync(sslStream, serverNameIndication, certificate, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); +#else + throw new NotSupportedException(Strings.SQL_TDS8_NotSupported_Netstandard2_0); +#endif + } + /// /// Dispose class /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs index a3ba9e0bba..b48ea36958 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs @@ -23,7 +23,7 @@ internal sealed class SNINpHandle : SNIPhysicalHandle private readonly string _targetServer; private readonly object _sendSync; - + private readonly bool _tlsFirst; private Stream _stream; private NamedPipeClientStream _pipeStream; private SslOverTdsStream _sslOverTdsStream; @@ -37,7 +37,7 @@ internal sealed class SNINpHandle : SNIPhysicalHandle private int _bufferSize = TdsEnums.DEFAULT_LOGIN_PACKET_SIZE; private readonly Guid _connectionId = Guid.NewGuid(); - public SNINpHandle(string serverName, string pipeName, long timerExpire) + public SNINpHandle(string serverName, string pipeName, long timerExpire, bool tlsFirst) { using (TrySNIEventScope.Create(nameof(SNINpHandle))) { @@ -45,7 +45,7 @@ public SNINpHandle(string serverName, string pipeName, long timerExpire) _sendSync = new object(); _targetServer = serverName; - + _tlsFirst = tlsFirst; try { _pipeStream = new NamedPipeClientStream( @@ -90,8 +90,14 @@ public SNINpHandle(string serverName, string pipeName, long timerExpire) return; } - _sslOverTdsStream = new SslOverTdsStream(_pipeStream, _connectionId); - _sslStream = new SNISslStream(_sslOverTdsStream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); + Stream stream = _pipeStream; + + if (!_tlsFirst) + { + _sslOverTdsStream = new SslOverTdsStream(_pipeStream, _connectionId); + stream = _sslOverTdsStream; + } + _sslStream = new SNISslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); _stream = _pipeStream; _status = TdsEnums.SNI_SUCCESS; @@ -311,8 +317,19 @@ public override uint EnableSsl(uint options) _validateCert = (options & TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE) != 0; try { - _sslStream.AuthenticateAsClient(_targetServer, null, SupportedProtocols, false); - _sslOverTdsStream.FinishHandshake(); + if (_tlsFirst) + { + AuthenticateAsClient(_sslStream, _targetServer, null); + } + else + { + // TODO: Resolve whether to send _serverNameIndication or _targetServer. _serverNameIndication currently results in error. Why? + _sslStream.AuthenticateAsClient(_targetServer, null, s_supportedProtocols, false); + } + if (_sslOverTdsStream is not null) + { + _sslOverTdsStream.FinishHandshake(); + } } catch (AuthenticationException aue) { @@ -333,7 +350,7 @@ public override void DisableSsl() { _sslStream.Dispose(); _sslStream = null; - _sslOverTdsStream.Dispose(); + _sslOverTdsStream?.Dispose(); _sslOverTdsStream = null; _stream = _pipeStream; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs index ba08f99bea..94f37d0c6a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.SNI internal abstract class SNIPhysicalHandle : SNIHandle { protected const int DefaultPoolSize = 4; + #if DEBUG private static int s_packetId; #endif @@ -84,7 +85,7 @@ private string GetStackParts() { return string.Join(Environment.NewLine, Environment.StackTrace - .Split(new string[] { Environment.NewLine },StringSplitOptions.None) + .Split(new string[] { Environment.NewLine }, StringSplitOptions.None) .Skip(3) // trims off the common parts at the top of the stack so you can see what the actual caller was .Take(7) // trims off most of the bottom of the stack because when running under xunit there's a lot of spam ); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index f9a3c88fa3..885ea0c806 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -142,9 +142,24 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode) /// IP address preference /// Used for DNS Cache /// Used for DNS Cache + /// + /// /// SNI handle - internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, - bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) + internal static SNIHandle CreateConnectionHandle( + string fullServerName, + bool ignoreSniOpenTimeout, + long timerExpire, + out byte[] instanceName, + ref byte[][] spnBuffer, + bool flushCache, + bool async, + bool parallel, + bool isIntegratedSecurity, + SqlConnectionIPAddressPreference ipPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + bool tlsFirst, + string hostNameInCertificate) { instanceName = new byte[1]; @@ -155,7 +170,6 @@ internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ign { return null; } - // If a localDB Data source is available, we need to use it. fullServerName = localDBDataSource ?? fullServerName; @@ -171,10 +185,11 @@ internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ign case DataSource.Protocol.Admin: case DataSource.Protocol.None: // default to using tcp if no protocol is provided case DataSource.Protocol.TCP: - sniHandle = CreateTcpHandle(details, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo); + sniHandle = CreateTcpHandle(details, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo, + tlsFirst, hostNameInCertificate); break; case DataSource.Protocol.NP: - sniHandle = CreateNpHandle(details, timerExpire, parallel); + sniHandle = CreateNpHandle(details, timerExpire, parallel, tlsFirst); break; default: Debug.Fail($"Unexpected connection protocol: {details._connectionProtocol}"); @@ -264,8 +279,18 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr /// IP address preference /// Key for DNS Cache /// Used for DNS Cache + /// + /// /// SNITCPHandle - private static SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, bool parallel, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) + private static SNITCPHandle CreateTcpHandle( + DataSource details, + long timerExpire, + bool parallel, + SqlConnectionIPAddressPreference ipPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + bool tlsFirst, + string hostNameInCertificate) { // TCP Format: // tcp:\ @@ -303,7 +328,8 @@ private static SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire port = isAdminConnection ? DefaultSqlServerDacPort : DefaultSqlServerPort; } - return new SNITCPHandle(hostName, port, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo); + return new SNITCPHandle(hostName, port, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo, + tlsFirst, hostNameInCertificate); } /// @@ -312,8 +338,9 @@ private static SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire /// Data source /// Timer expiration /// Should MultiSubnetFailover be used. Only returns an error for named pipes. + /// /// SNINpHandle - private static SNINpHandle CreateNpHandle(DataSource details, long timerExpire, bool parallel) + private static SNINpHandle CreateNpHandle(DataSource details, long timerExpire, bool parallel, bool tlsFirst) { if (parallel) { @@ -321,7 +348,7 @@ private static SNINpHandle CreateNpHandle(DataSource details, long timerExpire, SNICommon.ReportSNIError(SNIProviders.NP_PROV, 0, SNICommon.MultiSubnetFailoverWithNonTcpProtocol, Strings.SNI_ERROR_49); return null; } - return new SNINpHandle(details.PipeHostName, details.PipeName, timerExpire); + return new SNINpHandle(details.PipeHostName, details.PipeName, timerExpire, tlsFirst); } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs index b2ec6ccccd..df34f6c31b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs @@ -26,6 +26,8 @@ internal sealed class SNITCPHandle : SNIPhysicalHandle private readonly object _sendSync; private readonly Socket _socket; private NetworkStream _tcpStream; + private readonly string _hostNameInCertificate; + private readonly bool _tlsFirst; private Stream _stream; private SslStream _sslStream; @@ -117,14 +119,27 @@ public override int ProtocolVersion /// Parallel executions /// IP address preference /// Key for DNS Cache - /// Used for DNS Cache - public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) + /// Used for DNS Cache + /// Support TDS8.0 + /// Host Name in Certoficate + public SNITCPHandle( + string serverName, + int port, + long timerExpire, + bool parallel, + SqlConnectionIPAddressPreference ipPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + bool tlsFirst, + string hostNameInCertificate) { using (TrySNIEventScope.Create(nameof(SNITCPHandle))) { SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}", args0: _connectionId, args1: serverName); _targetServer = serverName; + _tlsFirst = tlsFirst; + _hostNameInCertificate = hostNameInCertificate; _sendSync = new object(); SQLDNSInfo cachedDNSInfo; @@ -249,8 +264,13 @@ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel _socket.NoDelay = true; _tcpStream = new SNINetworkStream(_socket, true); - _sslOverTdsStream = new SslOverTdsStream(_tcpStream, _connectionId); - _sslStream = new SNISslStream(_sslOverTdsStream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); + Stream stream = _tcpStream; + if (!_tlsFirst) + { + _sslOverTdsStream = new SslOverTdsStream(_tcpStream, _connectionId); + stream = _sslOverTdsStream; + } + _sslStream = new SNISslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); } catch (SocketException se) { @@ -592,10 +612,22 @@ public override uint EnableSsl(uint options) using (TrySNIEventScope.Create(nameof(SNIHandle))) { _validateCert = (options & TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE) != 0; + try { - _sslStream.AuthenticateAsClient(_targetServer, null, SupportedProtocols, false); - _sslOverTdsStream.FinishHandshake(); + if (_tlsFirst) + { + AuthenticateAsClient(_sslStream, _targetServer, null); + } + else + { + // TODO: Resolve whether to send _serverNameIndication or _targetServer. _serverNameIndication currently results in error. Why? + _sslStream.AuthenticateAsClient(_targetServer, null, s_supportedProtocols, false); + } + if (_sslOverTdsStream is not null) + { + _sslOverTdsStream.FinishHandshake(); + } } catch (AuthenticationException aue) { @@ -621,7 +653,7 @@ public override void DisableSsl() { _sslStream.Dispose(); _sslStream = null; - _sslOverTdsStream.Dispose(); + _sslOverTdsStream?.Dispose(); _sslOverTdsStream = null; _stream = _tcpStream; SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, SSL Disabled. Communication will continue on TCP Stream.", args0: _connectionId); @@ -642,9 +674,18 @@ private bool ValidateServerCertificate(object sender, X509Certificate cert, X509 SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Certificate will not be validated.", args0: _connectionId); return true; } + string serverNameToValidate; + if (!string.IsNullOrEmpty(_hostNameInCertificate)) + { + serverNameToValidate = _hostNameInCertificate; + } + else + { + serverNameToValidate = _targetServer; + } SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Certificate will be validated for Target Server name", args0: _connectionId); - return SNICommon.ValidateSslServerCertificate(_targetServer, cert, policyErrors); + return SNICommon.ValidateSslServerCertificate(serverNameToValidate, cert, policyErrors); } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index ddd7ebdd16..082e24fb96 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1914,7 +1914,8 @@ private void AttemptOneLogin( ConnectionOptions.TrustServerCertificate, ConnectionOptions.IntegratedSecurity, withFailover, - ConnectionOptions.Authentication); + ConnectionOptions.Authentication, + ConnectionOptions.HostNameInCertificate); _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); _timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); 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 4b03949d93..8471782e7c 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 @@ -171,7 +171,7 @@ internal sealed partial class TdsParser internal string EnclaveType { get; set; } internal bool isTcpProtocol { get; set; } - internal string FQDNforDNSCahce { get; set; } + internal string FQDNforDNSCache { get; set; } /// /// Get if data classification is enabled by the server. @@ -359,11 +359,12 @@ internal void Connect( SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, - bool encrypt, + SqlConnectionEncryptOption encrypt, bool trustServerCert, bool integratedSecurity, bool withFailover, - SqlAuthenticationMethod authType) + SqlAuthenticationMethod authType, + string hostNameInCertificate) { if (_state != TdsParserState.Closed) { @@ -393,10 +394,10 @@ internal void Connect( authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString()); } - // Encryption is not supported on SQL Local DB - disable it for current session. - if (connHandler.ConnectionOptions.LocalDBInstance != null && encrypt) + // Encryption is not supported on SQL Local DB - disable it if they have only specified Mandatory + if (connHandler.ConnectionOptions.LocalDBInstance != null && encrypt == SqlConnectionEncryptOption.Mandatory) { - encrypt = false; + encrypt = SqlConnectionEncryptOption.Optional; SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); } @@ -409,6 +410,12 @@ internal void Connect( SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } + // if Strict encryption is chosen trust server certificate should always be false. + if (encrypt == SqlConnectionEncryptOption.Strict) + { + trustServerCert = false; + } + byte[] instanceName = null; Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); @@ -417,19 +424,21 @@ internal void Connect( bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover; - FQDNforDNSCahce = serverInfo.ResolvedServerName; + FQDNforDNSCache = serverInfo.ResolvedServerName; - int commaPos = FQDNforDNSCahce.IndexOf(","); + int commaPos = FQDNforDNSCache.IndexOf(","); if (commaPos != -1) { - FQDNforDNSCahce = FQDNforDNSCahce.Substring(0, commaPos); + FQDNforDNSCache = FQDNforDNSCache.Substring(0, commaPos); } _connHandler.pendingSQLDNSObject = null; // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server - _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, false, true, fParallel, - _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated); + _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, + false, true, fParallel, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, + integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated, encrypt == SqlConnectionEncryptOption.Strict, + hostNameInCertificate); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -472,13 +481,13 @@ internal void Connect( if (null == _connHandler.pendingSQLDNSObject) { // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject); + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); } if (!ClientOSEncryptionSupport) { //If encryption is required, an error will be thrown. - if (encrypt) + if (encrypt != SqlConnectionEncryptOption.Optional) { _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); _physicalStateObj.Dispose(); @@ -488,14 +497,15 @@ internal void Connect( } SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - SendPreLoginHandshake(instanceName, encrypt); + SendPreLoginHandshake(instanceName, encrypt,integratedSecurity); _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); _physicalStateObj.SniContext = SniContext.Snix_PreLogin; SqlClientEventSource.Log.TryTraceEvent(" Consuming prelogin handshake"); - PreLoginHandshakeStatus status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable, out _connHandler._fedAuthRequired); + PreLoginHandshakeStatus status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable, + out _connHandler._fedAuthRequired, encrypt == SqlConnectionEncryptOption.Strict); if (status == PreLoginHandshakeStatus.InstanceFailure) { @@ -506,7 +516,7 @@ internal void Connect( _physicalStateObj.SniContext = SniContext.Snix_Connect; _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, true, true, fParallel, - _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity); + _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, integratedSecurity); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -523,11 +533,12 @@ internal void Connect( if (null == _connHandler.pendingSQLDNSObject) { // for DNS Caching phase 1 - _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject); + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); } - SendPreLoginHandshake(instanceName, encrypt); - status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable, out _connHandler._fedAuthRequired); + SendPreLoginHandshake(instanceName, encrypt, integratedSecurity); + status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable, out _connHandler._fedAuthRequired, + encrypt == SqlConnectionEncryptOption.Strict); // Don't need to check for 7.0 failure, since we've already consumed // one pre-login packet and know we are connecting to 2000. @@ -643,9 +654,23 @@ internal void PutSession(TdsParserStateObject session) } } - - private void SendPreLoginHandshake(byte[] instanceName, bool encrypt) + private void SendPreLoginHandshake( + byte[] instanceName, + SqlConnectionEncryptOption encrypt, + bool integratedSecurity) { + if (encrypt == SqlConnectionEncryptOption.Strict) + { + //Always validate the certificate when in strict encryption mode + uint info = TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE | TdsEnums.SNI_SSL_SEND_ALPN_EXTENSION; + + EnableSsl(info, encrypt, integratedSecurity); + + // Since encryption has already been negotiated, we need to set encryption not supported in + // prelogin so that we don't try to negotiate encryption again during ConsumePreLoginHandshake. + _encryptionOption = EncryptionOptions.NOT_SUP; + } + // PreLoginHandshake buffer consists of: // 1) Standard header, with type = MT_PRELOGIN // 2) Consecutive 5 bytes for each option, (1 byte length, 2 byte offset, 2 byte payload length) @@ -703,7 +728,7 @@ private void SendPreLoginHandshake(byte[] instanceName, bool encrypt) else { // Else, inform server of user request. - if (encrypt) + if (encrypt == SqlConnectionEncryptOption.Mandatory) { payload[payloadLength] = (byte)EncryptionOptions.ON; _encryptionOption = EncryptionOptions.ON; @@ -800,7 +825,56 @@ private void SendPreLoginHandshake(byte[] instanceName, bool encrypt) _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); } - private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trustServerCert, bool integratedSecurity, out bool marsCapable, out bool fedAuthRequired) + private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integratedSecurity) + { + uint error = 0; + + if (encrypt && !integratedSecurity) + { + // optimization: in case of SQL Authentication and encryption in TDS, set SNI_SSL_IGNORE_CHANNEL_BINDINGS + // to let SNI know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. + // This applies to Native SNI + info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; + } + + error = _physicalStateObj.EnableSsl(ref info, encrypt == SqlConnectionEncryptOption.Strict); + + if (error != TdsEnums.SNI_SUCCESS) + { + _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); + ThrowExceptionAndWarning(_physicalStateObj); + } + + int protocolVersion = 0; + WaitForSSLHandShakeToComplete(ref error, ref protocolVersion); + + SslProtocols protocol = (SslProtocols)protocolVersion; + string warningMessage = protocol.GetProtocolWarning(); + if (!string.IsNullOrEmpty(warningMessage)) + { + if (!encrypt && LocalAppContextSwitches.SuppressInsecureTLSWarning) + { + // Skip console warning + SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(EnableSsl), SqlClientLogger.LogLevel.Warning, warningMessage); + } + else + { + // This logs console warning of insecure protocol in use. + _logger.LogWarning(nameof(TdsParser), nameof(EnableSsl), warningMessage); + } + } + + // create a new packet encryption changes the internal packet size + _physicalStateObj.ClearAllWritePackets(); + } + + private PreLoginHandshakeStatus ConsumePreLoginHandshake( + SqlConnectionEncryptOption encrypt, + bool trustServerCert, + bool integratedSecurity, + out bool marsCapable, + out bool fedAuthRequired, + bool tlsFirst) { marsCapable = _fMARS; // Assign default value fedAuthRequired = false; @@ -894,9 +968,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus _physicalStateObj.Dispose(); ThrowExceptionAndWarning(_physicalStateObj); } - break; - case (EncryptionOptions.OFF): if (serverOption == EncryptionOptions.OFF) { @@ -912,7 +984,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus break; case (EncryptionOptions.NOT_SUP): - if (serverOption == EncryptionOptions.REQ) + if (!tlsFirst && serverOption == EncryptionOptions.REQ) { _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); _physicalStateObj.Dispose(); @@ -920,7 +992,6 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus } break; - default: Debug.Fail("Invalid client encryption option detected"); break; @@ -929,50 +1000,13 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus if (_encryptionOption == EncryptionOptions.ON || _encryptionOption == EncryptionOptions.LOGIN) { - uint error = 0; - // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. - bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || (_connHandler._accessTokenInBytes != null && !trustServerCert); + bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || + (_connHandler._accessTokenInBytes != null && !trustServerCert); uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) | (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); - if (encrypt && !integratedSecurity) - { - // optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI - // know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. - // This applies to Native SNI - info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; - } - - error = _physicalStateObj.EnableSsl(ref info); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - int protocolVersion = 0; - WaitForSSLHandShakeToComplete(ref error, ref protocolVersion); - - SslProtocols protocol = (SslProtocols)protocolVersion; - string warningMessage = protocol.GetProtocolWarning(); - if (!string.IsNullOrEmpty(warningMessage)) - { - if (!encrypt && LocalAppContextSwitches.SuppressInsecureTLSWarning) - { - // Skip console warning - SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(ConsumePreLoginHandshake), SqlClientLogger.LogLevel.Warning, warningMessage); - } - else - { - // This logs console warning of insecure protocol in use. - _logger.LogWarning(nameof(TdsParser), nameof(ConsumePreLoginHandshake), warningMessage); - } - } - - // create a new packet encryption changes the internal packet size - _physicalStateObj.ClearAllWritePackets(); + EnableSsl(info, encrypt, integratedSecurity); } break; @@ -3148,7 +3182,7 @@ private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) bool ret = false; if (_connHandler._cleanSQLDNSCaching) { - ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCahce); + ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache); } if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs index 980e0d556c..3db0f07fcf 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs @@ -145,7 +145,9 @@ internal SNIHandle( bool fSync, bool fParallel, SqlConnectionIPAddressPreference ipPreference, - SQLDNSInfo cachedDNSInfo) + SQLDNSInfo cachedDNSInfo, + bool tlsFirst, + string hostNameInCertificate) : base(IntPtr.Zero, true) { try @@ -159,8 +161,8 @@ internal SNIHandle( timeout = Timeout.Infinite; // -1 == native SNIOPEN_TIMEOUT_VALUE / INFINITE } - _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, - spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, ipPreference, cachedDNSInfo); + _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, spnBuffer, instanceName, flushCache, + fSync, timeout, fParallel, ipPreference, cachedDNSInfo, hostNameInCertificate); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 7a095d0933..1fe6a5a9fc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -394,7 +394,7 @@ internal object Owner } else { - _readerState = null; + _readerState = null; } _owner.SetTarget(value); } @@ -786,8 +786,21 @@ private void ResetCancelAndProcessAttention() } } - internal abstract void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel, - SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity = false); + internal abstract void CreatePhysicalSNIHandle( + string serverName, + bool ignoreSniOpenTimeout, + long timerExpire, + out byte[] instanceName, + ref byte[][] spnBuffer, + bool flushCache, + bool async, + bool fParallel, + SqlConnectionIPAddressPreference iPAddressPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + bool isIntegratedSecurity = false, + bool tlsFirst = false, + string hostNameInCertificate = ""); internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo); @@ -799,7 +812,7 @@ internal abstract void CreatePhysicalSNIHandle(string serverName, bool ignoreSni protected abstract void FreeGcHandle(int remaining, bool release); - internal abstract uint EnableSsl(ref uint info); + internal abstract uint EnableSsl(ref uint info, bool tlsFirst); internal abstract uint WaitForSSLHandShakeToComplete(out int protocolVersion); @@ -1011,13 +1024,13 @@ internal Task ExecuteFlush() else { return AsyncHelper.CreateContinuationTaskWithState( - task: writePacketTask, + task: writePacketTask, state: this, - onSuccess: static (object state) => + onSuccess: static (object state) => { TdsParserStateObject stateObject = (TdsParserStateObject)state; stateObject.HasPendingData = true; - stateObject._messageStatus = 0; + stateObject._messageStatus = 0; } ); } @@ -2493,7 +2506,7 @@ internal void ReadSni(TaskCompletionSource completion) Timeout.Infinite, Timeout.Infinite ); - + // -1 == Infinite // 0 == Already timed out (NOTE: To simulate the same behavior as sync we will only timeout on 0 if we receive an IO Pending from SNI) @@ -2901,7 +2914,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) Debug.Assert(CheckPacket(packet, source) && source != null, "AsyncResult null on callback"); if (_parser.MARSOn) - { + { // Only take reset lock on MARS and Async. CheckSetResetConnectionState(error, CallbackType.Read); } @@ -3621,7 +3634,7 @@ internal void SendAttention(bool mustTakeWriteLock = false) } } #if DEBUG - } + } #endif SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds. diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs index e69e8c3fab..e57ffdfb94 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -75,11 +75,25 @@ protected override uint SNIPacketGetData(PacketHandle packet, byte[] inBuff, ref return TdsEnums.SNI_SUCCESS; } - internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel, - SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity) + internal override void CreatePhysicalSNIHandle( + string serverName, + bool ignoreSniOpenTimeout, + long timerExpire, + out byte[] instanceName, + ref byte[][] spnBuffer, + bool flushCache, + bool async, + bool parallel, + SqlConnectionIPAddressPreference iPAddressPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + bool isIntegratedSecurity, + bool tlsFirst, + string hostNameInCertificate) { - SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity, - iPAddressPreference, cachedFQDN, ref pendingDNSInfo); + SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, + flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst, + hostNameInCertificate); if (sessionHandle is not null) { _sessionHandle = sessionHandle; @@ -200,7 +214,7 @@ internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); error = sessionHandle.Receive(out SNIPacket packet, timeoutRemaining); - + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | Info | State Object Id {0}, Session Id {1}", _objectID, sessionHandle.ConnectionId); #if DEBUG SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | TRC | State Object Id {0}, Session Id {1}, Packet {2} received, Packet owner Id {3}, Packet dataLeft {4}", _objectID, sessionHandle.ConnectionId, packet?._id, packet?._owner.ConnectionId, packet?.DataLeft); @@ -278,7 +292,7 @@ internal override uint WritePacket(PacketHandle packetHandle, bool sync) { result = sessionHandle.SendAsync(packet); } - + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.WritePacket | Info | Session Id {0}, SendAsync Result {1}", sessionHandle.ConnectionId, result); return result; } @@ -346,7 +360,7 @@ internal override uint EnableMars(ref uint info) return TdsEnums.SNI_ERROR; } - internal override uint EnableSsl(ref uint info) + internal override uint EnableSsl(ref uint info, bool tlsFirst) { SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); try diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index 89df41b417..7b31d2e5b4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -64,7 +64,7 @@ protected override void CreateSessionHandle(TdsParserStateObject physicalConnect SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); SQLDNSInfo cachedDNSInfo; - bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo); + bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); } @@ -137,8 +137,21 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async) return myInfo; } - internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel, - SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity) + internal override void CreatePhysicalSNIHandle( + string serverName, + bool ignoreSniOpenTimeout, + long timerExpire, + out byte[] instanceName, + ref byte[][] spnBuffer, + bool flushCache, + bool async, + bool fParallel, + SqlConnectionIPAddressPreference ipPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + bool isIntegratedSecurity, + bool tlsFirst, + string hostNameInCertificate) { // We assume that the loadSSPILibrary has been called already. now allocate proper length of buffer spnBuffer = new byte[1][]; @@ -172,7 +185,8 @@ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSni SQLDNSInfo cachedDNSInfo; bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo); - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer[0], ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel, ipPreference, cachedDNSInfo); + _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer[0], ignoreSniOpenTimeout, checked((int)timeout), out instanceName, + flushCache, !async, fParallel, ipPreference, cachedDNSInfo, tlsFirst, hostNameInCertificate); } protected override uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) @@ -376,10 +390,14 @@ internal override uint DisableSsl() internal override uint EnableMars(ref uint info) => SNINativeMethodWrapper.SNIAddProvider(Handle, SNINativeMethodWrapper.ProviderEnum.SMUX_PROV, ref info); - internal override uint EnableSsl(ref uint info) + internal override uint EnableSsl(ref uint info, bool tlsFirst) { + SNINativeMethodWrapper.AuthProviderInfo authInfo = new SNINativeMethodWrapper.AuthProviderInfo(); + authInfo.flags = info; + authInfo.tlsFirst = tlsFirst; + // Add SSL (Encryption) SNI provider. - return SNINativeMethodWrapper.SNIAddProvider(Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, ref info); + return SNINativeMethodWrapper.SNIAddProvider(Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, ref authInfo); } internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 1bf34b20e8..4a203d13bd 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -920,6 +920,34 @@ public enum SqlConnectionIPAddressPreference UsePlatformDefault = 2 } + /// + public sealed class SqlConnectionEncryptOption + { + /// + public static SqlConnectionEncryptOption Optional => throw null; + + /// + public static SqlConnectionEncryptOption Mandatory => throw null; + + /// + public static SqlConnectionEncryptOption Strict => throw null; + + /// + public static implicit operator SqlConnectionEncryptOption(bool value) => throw null; + + /// + public static implicit operator bool(SqlConnectionEncryptOption value) => throw null; + + /// + public override string ToString() { throw null; } + + /// + public override bool Equals(object obj) { throw null; } + + /// + public override int GetHashCode() { throw null; } + } + /// public enum SqlConnectionOverrides { @@ -1008,7 +1036,7 @@ public SqlConnectionStringBuilder(string connectionString) { } /// [System.ComponentModel.DisplayNameAttribute("Encrypt")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] - public bool Encrypt { get { throw null; } set { } } + public SqlConnectionEncryptOption Encrypt { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Enlist")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 4d7e512b40..93b2fe8ca9 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -416,6 +416,9 @@ Microsoft\Data\SqlClient\SqlCommandSet.cs + + Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs + Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs index 39ba5c5259..cf04e9c2ad 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs @@ -170,14 +170,18 @@ internal struct ConsumerInfo internal IntPtr key; } - + [StructLayout(LayoutKind.Sequential)] internal struct AuthProviderInfo { - internal uint flags; - internal string certId; - internal bool certHash; - internal object clientCertificateCallbackContext; - internal SqlClientCertificateDelegate clientCertificateCallback; + public uint flags; + [MarshalAs(UnmanagedType.Bool)] + public bool tlsFirst; + [MarshalAs(UnmanagedType.LPWStr)] + public string certId; + [MarshalAs(UnmanagedType.Bool)] + public bool certHash; + public object clientCertificateCallbackContext; + public SqlClientCertificateDelegate clientCertificateCallback; }; internal struct CTAIPProviderInfo @@ -339,6 +343,8 @@ internal unsafe struct SNI_CLIENT_CONSUMER_INFO public Sni_Consumer_Info ConsumerInfo; [MarshalAs(UnmanagedType.LPWStr)] public string wszConnectionString; + [MarshalAs(UnmanagedType.LPWStr)] + public string HostNameInCertificate; public PrefixEnum networkLibrary; public byte* szSPN; public uint cchSPN; @@ -795,8 +801,22 @@ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHan return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo); } - internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel, - Int32 transparentNetworkResolutionStateNo, Int32 totalTimeout, Boolean isAzureSqlServerEndpoint, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) + internal static unsafe uint SNIOpenSyncEx( + ConsumerInfo consumerInfo, + string constring, + ref IntPtr pConn, + byte[] spnBuffer, + byte[] instanceName, + bool fOverrideCache, + bool fSync, + int timeout, + bool fParallel, + Int32 transparentNetworkResolutionStateNo, + Int32 totalTimeout, + Boolean isAzureSqlServerEndpoint, + SqlConnectionIPAddressPreference ipPreference, + SQLDNSInfo cachedDNSInfo, + string hostNameInCertificate) { fixed (byte* pin_instanceName = &instanceName[0]) { @@ -806,8 +826,8 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons MarshalConsumerInfo(consumerInfo, ref clientConsumerInfo.ConsumerInfo); clientConsumerInfo.wszConnectionString = constring; + clientConsumerInfo.HostNameInCertificate = hostNameInCertificate; clientConsumerInfo.networkLibrary = PrefixEnum.UNKNOWN_PREFIX; - clientConsumerInfo.szInstanceName = pin_instanceName; clientConsumerInfo.cchInstanceName = (uint)instanceName.Length; clientConsumerInfo.fOverrideLastConnectCache = fOverrideCache; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 47f4b29810..a50188950f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -991,7 +991,7 @@ internal override bool IsConnectionAlive(bool throwOnException) tdsReliabilitySection.Start(); #endif //DEBUG - isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException); + isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException); #if DEBUG } @@ -2308,7 +2308,8 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt _serverCallback, _clientCallback, _originalNetworkAddressInfo != null, - disableTnir); + disableTnir, + ConnectionOptions.HostNameInCertificate); timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); @@ -2386,18 +2387,18 @@ internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = { tdsReliabilitySection.Start(); #endif //DEBUG - Task reconnectTask = parent.ValidateAndReconnect(() => - { - ThreadHasParserLockForClose = false; - _parserLock.Release(); - releaseConnectionLock = false; - }, timeout); - if (reconnectTask != null) - { - AsyncHelper.WaitForCompletion(reconnectTask, timeout); - return true; - } - return false; + Task reconnectTask = parent.ValidateAndReconnect(() => + { + ThreadHasParserLockForClose = false; + _parserLock.Release(); + releaseConnectionLock = false; + }, timeout); + if (reconnectTask != null) + { + AsyncHelper.WaitForCompletion(reconnectTask, timeout); + return true; + } + return false; #if DEBUG } finally 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 a181d96276..9a2543d574 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 @@ -296,7 +296,7 @@ internal bool IsColumnEncryptionSupported internal bool isTcpProtocol { get; set; } - internal string FQDNforDNSCahce { get; set; } + internal string FQDNforDNSCache { get; set; } /// /// Get if data classification is enabled by the server. @@ -494,7 +494,7 @@ internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, - bool encrypt, + SqlConnectionEncryptOption encrypt, bool trustServerCert, bool integratedSecurity, bool withFailover, @@ -504,7 +504,8 @@ internal void Connect(ServerInfo serverInfo, ServerCertificateValidationCallback serverCallback, ClientCertificateRetrievalCallback clientCallback, bool useOriginalAddressInfo, - bool disableTnir) + bool disableTnir, + string hostNameInCertificate) { if (_state != TdsParserState.Closed) { @@ -531,10 +532,10 @@ internal void Connect(ServerInfo serverInfo, if (connHandler.ConnectionOptions.LocalDBInstance != null) { LocalDBAPI.CreateLocalDBInstance(connHandler.ConnectionOptions.LocalDBInstance); - if (encrypt) + if (encrypt == SqlConnectionEncryptOption.Mandatory) { // Encryption is not supported on SQL Local DB - disable it for current session. - encrypt = false; + encrypt = SqlConnectionEncryptOption.Optional; SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); } } @@ -585,6 +586,12 @@ internal void Connect(ServerInfo serverInfo, } } + // if Strict encryption is chosen trust server certificate should always be false. + if (encrypt == SqlConnectionEncryptOption.Strict) + { + trustServerCert = false; + } + byte[] instanceName = null; Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); @@ -606,16 +613,17 @@ internal void Connect(ServerInfo serverInfo, int totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout; - FQDNforDNSCahce = serverInfo.ResolvedServerName; + FQDNforDNSCache = serverInfo.ResolvedServerName; - int commaPos = FQDNforDNSCahce.IndexOf(","); + int commaPos = FQDNforDNSCache.IndexOf(","); if (commaPos != -1) { - FQDNforDNSCahce = FQDNforDNSCahce.Substring(0, commaPos); + FQDNforDNSCache = FQDNforDNSCache.Substring(0, commaPos); } - _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, - out instanceName, _sniSpnBuffer, false, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce); + _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, _sniSpnBuffer, + false, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, + encrypt == SqlConnectionEncryptOption.Strict, hostNameInCertificate); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -656,12 +664,12 @@ internal void Connect(ServerInfo serverInfo, Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); // for DNS Caching phase 1 - AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce); + AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache); if (!ClientOSEncryptionSupport) { //If encryption is required, an error will be thrown. - if (encrypt) + if (encrypt == SqlConnectionEncryptOption.Mandatory) { _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); _physicalStateObj.Dispose(); @@ -672,7 +680,8 @@ internal void Connect(ServerInfo serverInfo, // UNDONE - send "" for instance now, need to fix later SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - SendPreLoginHandshake(instanceName, encrypt, !string.IsNullOrEmpty(certificate), useOriginalAddressInfo); + SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, !string.IsNullOrEmpty(certificate), + useOriginalAddressInfo, serverCallback, clientCallback); _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); @@ -680,8 +689,8 @@ internal void Connect(ServerInfo serverInfo, _physicalStateObj.SniContext = SniContext.Snix_PreLogin; SqlClientEventSource.Log.TryTraceEvent(" Consuming prelogin handshake"); - PreLoginHandshakeStatus status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, serverCallback, clientCallback, - out marsCapable, out _connHandler._fedAuthRequired); + PreLoginHandshakeStatus status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, serverCallback, + clientCallback, out marsCapable, out _connHandler._fedAuthRequired, encrypt == SqlConnectionEncryptOption.Strict); if (status == PreLoginHandshakeStatus.InstanceFailure) { @@ -690,8 +699,9 @@ internal void Connect(ServerInfo serverInfo, // On Instance failure re-connect and flush SNI named instance cache. _physicalStateObj.SniContext = SniContext.Snix_Connect; - _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, - out instanceName, _sniSpnBuffer, true, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, serverInfo.ResolvedServerName); + _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, + _sniSpnBuffer, true, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, + serverInfo.ResolvedServerName, encrypt == SqlConnectionEncryptOption.Strict, hostNameInCertificate); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -706,11 +716,12 @@ internal void Connect(ServerInfo serverInfo, SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); // for DNS Caching phase 1 - AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce); + AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache); - SendPreLoginHandshake(instanceName, encrypt, !string.IsNullOrEmpty(certificate), useOriginalAddressInfo); + SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, !string.IsNullOrEmpty(certificate), + useOriginalAddressInfo, serverCallback, clientCallback); status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, serverCallback, clientCallback, - out marsCapable, out _connHandler._fedAuthRequired); + out marsCapable, out _connHandler._fedAuthRequired, encrypt == SqlConnectionEncryptOption.Strict); // Don't need to check for 7.0 failure, since we've already consumed // one pre-login packet and know we are connecting to 2000. @@ -953,8 +964,27 @@ internal void BestEffortCleanup() } } - private void SendPreLoginHandshake(byte[] instanceName, bool encrypt, bool clientCertificate, bool useCtaip) + private void SendPreLoginHandshake( + byte[] instanceName, + SqlConnectionEncryptOption encrypt, + bool integratedSecurity, + bool clientCertificate, + bool useCtaip, + ServerCertificateValidationCallback serverCallback, + ClientCertificateRetrievalCallback clientCallback) { + if (encrypt == SqlConnectionEncryptOption.Strict) + { + //Always validate the certificate when in strict encryption mode + uint info = TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE | TdsEnums.SNI_SSL_SEND_ALPN_EXTENSION; + + EnableSsl(info, encrypt, integratedSecurity, serverCallback, clientCallback); + + // Since encryption has already been negotiated, we need to set encryption not supported in + // prelogin so that we don't try to negotiate encryption again during ConsumePreLoginHandshake. + _encryptionOption = EncryptionOptions.NOT_SUP; + } + // PreLoginHandshake buffer consists of: // 1) Standard header, with type = MT_PRELOGIN // 2) Consecutive 5 bytes for each option, (1 byte length, 2 byte offset, 2 byte payload length) @@ -1015,7 +1045,7 @@ private void SendPreLoginHandshake(byte[] instanceName, bool encrypt, bool clien else { // Else, inform server of user request. - if (encrypt) + if (encrypt == SqlConnectionEncryptOption.Mandatory) { payload[payloadLength] = (byte)EncryptionOptions.ON; _encryptionOption = EncryptionOptions.ON; @@ -1130,10 +1160,118 @@ private void SendPreLoginHandshake(byte[] instanceName, bool encrypt, bool clien _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); } - private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod authType, bool encrypt, bool trustServerCert, bool integratedSecurity, ServerCertificateValidationCallback serverCallback, - ClientCertificateRetrievalCallback clientCallback, out bool marsCapable, out bool fedAuthRequired) + private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integratedSecurity, ServerCertificateValidationCallback serverCallback, ClientCertificateRetrievalCallback clientCallback) { + uint error = 0; + + if (encrypt && !integratedSecurity) + { + // optimization: in case of SQL Authentication and encryption in TDS, set SNI_SSL_IGNORE_CHANNEL_BINDINGS + // to let SNI know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. + // This applies to Native SNI + info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; + } + + // Add SSL (Encryption) SNI provider. + SNINativeMethodWrapper.AuthProviderInfo authInfo = new SNINativeMethodWrapper.AuthProviderInfo(); + authInfo.flags = info; + authInfo.tlsFirst = encrypt == SqlConnectionEncryptOption.Strict; + authInfo.certId = null; + authInfo.certHash = false; + authInfo.clientCertificateCallbackContext = IntPtr.Zero; + authInfo.clientCertificateCallback = null; + + if ((_encryptionOption & EncryptionOptions.CLIENT_CERT) != 0) + { + + string certificate = _connHandler.ConnectionOptions.Certificate; + + if (certificate.StartsWith("subject:", StringComparison.OrdinalIgnoreCase)) + { + authInfo.certId = certificate.Substring(8); + } + else if (certificate.StartsWith("sha1:", StringComparison.OrdinalIgnoreCase)) + { + authInfo.certId = certificate.Substring(5); + authInfo.certHash = true; + } + + if (clientCallback != null) + { + authInfo.clientCertificateCallbackContext = clientCallback; + authInfo.clientCertificateCallback = _clientCertificateCallback; + } + } + + error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, authInfo); + + if (error != TdsEnums.SNI_SUCCESS) + { + _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); + ThrowExceptionAndWarning(_physicalStateObj); + } + + // in the case where an async connection is made, encryption is used and Windows Authentication is used, + // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its + // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete + // before calling SNISecGenClientContext). + error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining(), out uint protocolVersion); + + if (error != TdsEnums.SNI_SUCCESS) + { + _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); + ThrowExceptionAndWarning(_physicalStateObj); + } + + string warningMessage = SslProtocolsHelper.GetProtocolWarning(protocolVersion); + if (!string.IsNullOrEmpty(warningMessage)) + { + if (!encrypt && LocalAppContextSwitches.SuppressInsecureTLSWarning) + { + // Skip console warning + SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(EnableSsl), SqlClientLogger.LogLevel.Warning, warningMessage); + } + else + { + // This logs console warning of insecure protocol in use. + _logger.LogWarning(nameof(TdsParser), nameof(EnableSsl), warningMessage); + } + } + + // Validate server certificate + if (serverCallback != null) + { + X509Certificate2 serverCert = null; + error = SNINativeMethodWrapper.SNISecGetServerCertificate(_physicalStateObj.Handle, ref serverCert); + if (error != TdsEnums.SNI_SUCCESS) + { + _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); + ThrowExceptionAndWarning(_physicalStateObj); + } + + bool valid = serverCallback(serverCert); + if (!valid) + { + throw SQL.InvalidServerCertificate(); + } + } + + // create a new packet encryption changes the internal packet size Bug# 228403 + _physicalStateObj.ClearAllWritePackets(); + } + + private PreLoginHandshakeStatus ConsumePreLoginHandshake( + SqlAuthenticationMethod authType, + SqlConnectionEncryptOption encrypt, + bool trustServerCert, + bool integratedSecurity, + ServerCertificateValidationCallback serverCallback, + ClientCertificateRetrievalCallback clientCallback, + out bool marsCapable, + out bool fedAuthRequired, + bool tlsFirst) + { // Assign default values marsCapable = _fMARS; fedAuthRequired = false; @@ -1239,7 +1377,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod break; case (EncryptionOptions.NOT_SUP): - if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ) + if (!tlsFirst && (serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ) { _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); _physicalStateObj.Dispose(); @@ -1272,112 +1410,13 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod trustServerCert = true; } - UInt32 error = 0; - // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert); UInt32 info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) | (is2005OrLater && (_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0 ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); - if (encrypt && !integratedSecurity) - { - // optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI - // know that it does not need to allocate/retrieve the Channel Bindings from the SSL context. - info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS; - } - - // Add SSL (Encryption) SNI provider. - SNINativeMethodWrapper.AuthProviderInfo authInfo = new SNINativeMethodWrapper.AuthProviderInfo(); - authInfo.flags = info; - authInfo.certId = null; - authInfo.certHash = false; - authInfo.clientCertificateCallbackContext = IntPtr.Zero; - authInfo.clientCertificateCallback = null; - - if ((_encryptionOption & EncryptionOptions.CLIENT_CERT) != 0) - { - - string certificate = _connHandler.ConnectionOptions.Certificate; - - if (certificate.StartsWith("subject:", StringComparison.OrdinalIgnoreCase)) - { - authInfo.certId = certificate.Substring(8); - } - else if (certificate.StartsWith("sha1:", StringComparison.OrdinalIgnoreCase)) - { - authInfo.certId = certificate.Substring(5); - authInfo.certHash = true; - } - - if (clientCallback != null) - { - authInfo.clientCertificateCallbackContext = clientCallback; - authInfo.clientCertificateCallback = _clientCertificateCallback; - } - } - - error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, authInfo); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - // in the case where an async connection is made, encryption is used and Windows Authentication is used, - // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its - // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete - // before calling SNISecGenClientContext). - error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining(), out uint protocolVersion); - - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - string warningMessage = SslProtocolsHelper.GetProtocolWarning(protocolVersion); - if (!string.IsNullOrEmpty(warningMessage)) - { - if (!encrypt && LocalAppContextSwitches.SuppressInsecureTLSWarning) - { - // Skip console warning - SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(ConsumePreLoginHandshake), SqlClientLogger.LogLevel.Warning, warningMessage); - } - else - { - // This logs console warning of insecure protocol in use. - _logger.LogWarning(nameof(TdsParser), nameof(ConsumePreLoginHandshake), warningMessage); - } - } - - // Validate server certificate - if (serverCallback != null) - { - X509Certificate2 serverCert = null; - - error = SNINativeMethodWrapper.SNISecGetServerCertificate(_physicalStateObj.Handle, ref serverCert); - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - bool valid = serverCallback(serverCert); - if (!valid) - { - throw SQL.InvalidServerCertificate(); - } - } - - // create a new packet encryption changes the internal packet size Bug# 228403 - try - { } // EmptyTry/Finally to avoid FXCop violation - finally - { - _physicalStateObj.ClearAllWritePackets(); - } + EnableSsl(info, encrypt == SqlConnectionEncryptOption.Mandatory, integratedSecurity, serverCallback, clientCallback); } break; @@ -3610,7 +3649,7 @@ private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) bool ret = false; if (_connHandler._cleanSQLDNSCaching) { - ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCahce); + ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache); } if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs index 591bd4f0a7..c2472d0c66 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs @@ -150,7 +150,9 @@ internal SNIHandle( TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout, SqlConnectionIPAddressPreference ipPreference, - SQLDNSInfo cachedDNSInfo) + SQLDNSInfo cachedDNSInfo, + bool tlsFirst, + string hostNameInCertificate) : base(IntPtr.Zero, true) { @@ -172,7 +174,7 @@ internal SNIHandle( int transparentNetworkResolutionStateNo = (int)transparentNetworkResolutionState; _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, transparentNetworkResolutionStateNo, totalTimeout, - ADP.IsAzureSqlServerEndpoint(serverName), ipPreference, cachedDNSInfo); + ADP.IsAzureSqlServerEndpoint(serverName), ipPreference, cachedDNSInfo, hostNameInCertificate); } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index e6b42319a2..6e8afce1ea 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -88,7 +88,6 @@ internal int ObjectID internal int _inBytesUsed = 0; // number of bytes used in internal read buffer internal int _inBytesRead = 0; // number of bytes read into internal read buffer internal int _inBytesPacket = 0; // number of bytes left in packet - internal int _spid; // SPID of the current connection // Packet state variables @@ -322,9 +321,8 @@ internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bo SetPacketSize(_parser._physicalStateObj._outBuff.Length); SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); - SQLDNSInfo cachedDNSInfo; - bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo); + bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); _sessionHandle = new SNIHandle(myInfo, physicalConnection, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); if (_sessionHandle.Status != TdsEnums.SNI_SUCCESS) @@ -846,8 +844,21 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async) return myInfo; } - internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, byte[] spnBuffer, bool flushCache, - bool async, bool fParallel, TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN) + internal void CreatePhysicalSNIHandle( + string serverName, + bool ignoreSniOpenTimeout, + long timerExpire, + out byte[] instanceName, + byte[] spnBuffer, + bool flushCache, + bool async, + bool fParallel, + TransparentNetworkResolutionState transparentNetworkResolutionState, + int totalTimeout, + SqlConnectionIPAddressPreference ipPreference, + string cachedFQDN, + bool tlsFirst = false, + string hostNameInCertificate = "") { SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); @@ -872,11 +883,12 @@ internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeo // serverName : serverInfo.ExtendedServerName // may not use this serverName as key - SQLDNSInfo cachedDNSInfo; - bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo); - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), - out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, ipPreference, cachedDNSInfo); + _ = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out SQLDNSInfo cachedDNSInfo); + + _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), + out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, + ipPreference, cachedDNSInfo, tlsFirst, hostNameInCertificate); } internal bool Deactivate() @@ -2625,7 +2637,7 @@ internal void ReadSni(TaskCompletionSource completion) ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); } else if (msecsRemaining == 0) - { + { // Got IO Pending, but we have no time left to wait // disable the timer and set the error state by calling OnTimeoutSync ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs index de5d1cb9e5..65e425590e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs @@ -25,6 +25,7 @@ private static class KEY internal const string Password = DbConnectionStringKeywords.Password; internal const string Persist_Security_Info = DbConnectionStringKeywords.PersistSecurityInfo; internal const string User_ID = DbConnectionStringKeywords.UserID; + internal const string Encrypt = DbConnectionStringKeywords.Encrypt; } // known connection string common synonyms @@ -238,7 +239,7 @@ internal static bool ConvertValueToBooleanInternal(string keyName, string string } } - private static bool CompareInsensitiveInvariant(string strvalue, string strconst) + private static bool CompareInsensitiveInvariant(string strvalue, string strconst) => (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst)); [System.Diagnostics.Conditional("DEBUG")] @@ -725,7 +726,7 @@ internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) StringBuilder builder = new StringBuilder(_usersConnectionString.Length); for (NameValuePair current = _keyChain; null != current; current = current.Next) { - if(!string.Equals(KEY.Password, current.Name, StringComparison.InvariantCultureIgnoreCase) && + if (!string.Equals(KEY.Password, current.Name, StringComparison.InvariantCultureIgnoreCase) && !string.Equals(SYNONYM.Pwd, current.Name, StringComparison.InvariantCultureIgnoreCase)) { builder.Append(_usersConnectionString, copyPosition, current.Length); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 0557ebaa75..98d1fc1d51 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -810,6 +810,20 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st } } + internal static SqlConnectionEncryptOption ConvertToSqlConnectionEncryptOption(string keyword, object value) + { + if (value is null) + { + return DbConnectionStringDefaults.Encrypt; + } + else if (value is string sValue) + { + return SqlConnectionEncryptOption.Parse(sValue); + } + + throw ADP.InvalidConnectionOptionValue(keyword); + } + #endregion #region <> @@ -947,7 +961,8 @@ internal static class DbConnectionStringDefaults #endif internal const string CurrentLanguage = ""; internal const string DataSource = ""; - internal const bool Encrypt = true; + internal static readonly SqlConnectionEncryptOption Encrypt = SqlConnectionEncryptOption.Mandatory; + internal const string HostNameInCertificate = ""; internal const bool Enlist = true; internal const string FailoverPartner = ""; internal const string InitialCatalog = ""; @@ -1010,6 +1025,7 @@ internal static class DbConnectionStringKeywords internal const string ContextConnection = "Context Connection"; internal const string CurrentLanguage = "Current Language"; internal const string Encrypt = "Encrypt"; + internal const string HostNameInCertificate = "Host Name In Certificate"; internal const string FailoverPartner = "Failover Partner"; internal const string InitialCatalog = "Initial Catalog"; internal const string MultipleActiveResultSets = "Multiple Active Result Sets"; @@ -1065,6 +1081,9 @@ internal static class DbConnectionStringSynonyms internal const string EXTENDEDPROPERTIES = "extended properties"; internal const string INITIALFILENAME = "initial file name"; + // internal const string HostNameInCertificate = HOSTNAMEINCERTIFICATE; + internal const string HOSTNAMEINCERTIFICATE = "hostnameincertificate"; + //internal const string ConnectTimeout = CONNECTIONTIMEOUT+","+TIMEOUT; internal const string CONNECTIONTIMEOUT = "connection timeout"; internal const string TIMEOUT = "timeout"; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs new file mode 100644 index 0000000000..5518472434 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + /// + public sealed class SqlConnectionEncryptOption + { + private const string TRUE = "True"; + private const string FALSE = "False"; + private const string STRICT = "Strict"; + private const string TRUE_LOWER = "true"; + private const string YES_LOWER = "yes"; + private const string MANDATORY_LOWER = "mandatory"; + private const string FALSE_LOWER = "false"; + private const string NO_LOWER = "no"; + private const string OPTIONAL_LOWER = "optional"; + private const string STRICT_LOWER = "strict"; + private readonly string _value; + private static readonly SqlConnectionEncryptOption s_optional = new(FALSE); + private static readonly SqlConnectionEncryptOption s_mandatory = new(TRUE); + private static readonly SqlConnectionEncryptOption s_strict = new(STRICT); + + private SqlConnectionEncryptOption(string value) + { + _value = value; + } + + internal static SqlConnectionEncryptOption Parse(string value) + { + switch (value.ToLower()) + { + case TRUE_LOWER: + case YES_LOWER: + case MANDATORY_LOWER: + { + return Mandatory; + } + case FALSE_LOWER: + case NO_LOWER: + case OPTIONAL_LOWER: + { + return Optional; + } + case STRICT_LOWER: + { + return Strict; + } + default: + throw ADP.InvalidConnectionOptionValue(SqlConnectionString.KEY.Encrypt); + } + } + + /// + public static SqlConnectionEncryptOption Optional => s_optional; + + /// + public static SqlConnectionEncryptOption Mandatory => s_mandatory; + + /// + public static SqlConnectionEncryptOption Strict => s_strict; + + /// + public static implicit operator SqlConnectionEncryptOption(bool value) => value ? SqlConnectionEncryptOption.Mandatory : SqlConnectionEncryptOption.Optional; + + /// + public static implicit operator bool(SqlConnectionEncryptOption value) => !Optional.Equals(value); + + /// + public override string ToString() => _value; + + /// + public override bool Equals(object obj) + { + if (obj != null && + obj is SqlConnectionEncryptOption option) + { + return ToString().Equals(option.ToString()); + } + + return false; + } + + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } + +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index aac7c04913..141732ba3f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -32,7 +32,8 @@ internal static class DEFAULT internal const int Connect_Timeout = DbConnectionStringDefaults.ConnectTimeout; internal const string Current_Language = DbConnectionStringDefaults.CurrentLanguage; internal const string Data_Source = DbConnectionStringDefaults.DataSource; - internal const bool Encrypt = DbConnectionStringDefaults.Encrypt; + internal static readonly SqlConnectionEncryptOption Encrypt = DbConnectionStringDefaults.Encrypt; + internal const string HostNameInCertificate = DbConnectionStringDefaults.HostNameInCertificate; internal const bool Enlist = DbConnectionStringDefaults.Enlist; internal const string FailoverPartner = DbConnectionStringDefaults.FailoverPartner; internal const string Initial_Catalog = DbConnectionStringDefaults.InitialCatalog; @@ -89,6 +90,7 @@ internal static class KEY internal const string Current_Language = DbConnectionStringKeywords.CurrentLanguage; internal const string Data_Source = DbConnectionStringKeywords.DataSource; internal const string Encrypt = DbConnectionStringKeywords.Encrypt; + internal const string HostNameInCertificate = DbConnectionStringKeywords.HostNameInCertificate; internal const string Enlist = DbConnectionStringKeywords.Enlist; internal const string FailoverPartner = DbConnectionStringKeywords.FailoverPartner; internal const string Initial_Catalog = DbConnectionStringKeywords.InitialCatalog; @@ -143,6 +145,8 @@ private static class SYNONYM internal const string ADDRESS = DbConnectionStringSynonyms.ADDRESS; internal const string SERVER = DbConnectionStringSynonyms.SERVER; internal const string NETWORK_ADDRESS = DbConnectionStringSynonyms.NETWORKADDRESS; + // host name in certificate + internal const string HOSTNAMEINCERTIFICATE = DbConnectionStringSynonyms.HOSTNAMEINCERTIFICATE; // initial catalog internal const string DATABASE = DbConnectionStringSynonyms.DATABASE; // integrated security @@ -212,9 +216,9 @@ internal static class TRANSACTIONBINDING } #if NETFRAMEWORK - internal const int SynonymCount = 29; + internal const int SynonymCount = 30; #else - internal const int SynonymCount = 26; + internal const int SynonymCount = 27; internal const int DeprecatedSynonymCount = 2; #endif // NETFRAMEWORK @@ -222,7 +226,7 @@ internal static class TRANSACTIONBINDING private readonly bool _integratedSecurity; - private readonly bool _encrypt; + private readonly SqlConnectionEncryptOption _encrypt; private readonly bool _trustServerCertificate; private readonly bool _enlist; private readonly bool _mars; @@ -257,6 +261,7 @@ internal static class TRANSACTIONBINDING private readonly string _initialCatalog; private readonly string _password; private readonly string _userID; + private readonly string _hostNameInCertificate; private readonly string _workstationId; @@ -289,7 +294,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _integratedSecurity = ConvertValueToIntegratedSecurity(); _poolBlockingPeriod = ConvertValueToPoolBlockingPeriod(); - _encrypt = ConvertValueToBoolean(KEY.Encrypt, DEFAULT.Encrypt); + _encrypt = ConvertValueToSqlConnectionEncrypt(); _enlist = ConvertValueToBoolean(KEY.Enlist, DEFAULT.Enlist); _mars = ConvertValueToBoolean(KEY.MARS, DEFAULT.MARS); _persistSecurityInfo = ConvertValueToBoolean(KEY.Persist_Security_Info, DEFAULT.Persist_Security_Info); @@ -322,6 +327,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _enclaveAttestationUrl = ConvertValueToString(KEY.EnclaveAttestationUrl, DEFAULT.EnclaveAttestationUrl); _attestationProtocol = ConvertValueToAttestationProtocol(); _ipAddressPreference = ConvertValueToIPAddressPreference(); + _hostNameInCertificate = ConvertValueToString(KEY.HostNameInCertificate, DEFAULT.HostNameInCertificate); // Temporary string - this value is stored internally as an enum. string typeSystemVersionString = ConvertValueToString(KEY.Type_System_Version, null); @@ -368,7 +374,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G // SQLPT 41700: Ignore ResetConnection=False (still validate the keyword/value) _connectionReset = ConvertValueToBoolean(KEY.Connection_Reset, DEFAULT.Connection_Reset); _contextConnection = ConvertValueToBoolean(KEY.Context_Connection, DEFAULT.Context_Connection); - _encrypt = ConvertValueToEncrypt(); + _encrypt = ConvertValueToSqlConnectionEncrypt(); _enlist = ConvertValueToBoolean(KEY.Enlist, ADP.s_isWindowsNT); _transparentNetworkIPResolution = ConvertValueToBoolean(KEY.TransparentNetworkIPResolution, DEFAULT.TransparentNetworkIPResolution); _networkLibrary = ConvertValueToString(KEY.Network_Library, null); @@ -400,7 +406,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G } } - if (!_encrypt) + if (_encrypt == SqlConnectionEncryptOption.Optional) { // Support legacy registry encryption settings const string folder = "Software\\Microsoft\\MSSQLServer\\Client\\SuperSocketNetLib"; const string value = "Encrypt"; @@ -408,7 +414,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G object obj = ADP.LocalMachineRegistryValue(folder, value); if ((obj is int iObj) && (iObj == 1)) { // If the registry key exists - _encrypt = true; + _encrypt = SqlConnectionEncryptOption.Mandatory; } } @@ -696,7 +702,8 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS // SQLPT 41700: Ignore ResetConnection=False, always reset the connection for security internal bool ConnectionReset => true; // internal bool EnableUdtDownload => _enableUdtDownload;} } - internal bool Encrypt => _encrypt; + internal SqlConnectionEncryptOption Encrypt => _encrypt; + internal string HostNameInCertificate => _hostNameInCertificate; internal bool TrustServerCertificate => _trustServerCertificate; internal bool Enlist => _enlist; internal bool MARS => _mars; @@ -817,6 +824,7 @@ internal static Dictionary GetParseSynonyms() { KEY.Encrypt, KEY.Encrypt }, { KEY.Enlist, KEY.Enlist }, { KEY.FailoverPartner, KEY.FailoverPartner }, + { KEY.HostNameInCertificate, KEY.HostNameInCertificate }, { KEY.Initial_Catalog, KEY.Initial_Catalog }, { KEY.Integrated_Security, KEY.Integrated_Security }, { KEY.Load_Balance_Timeout, KEY.Load_Balance_Timeout }, @@ -847,6 +855,7 @@ internal static Dictionary GetParseSynonyms() { SYNONYM.APP, KEY.Application_Name }, { SYNONYM.APPLICATIONINTENT, KEY.ApplicationIntent }, { SYNONYM.EXTENDED_PROPERTIES, KEY.AttachDBFilename }, + { SYNONYM.HOSTNAMEINCERTIFICATE, KEY.HostNameInCertificate }, { SYNONYM.INITIAL_FILE_NAME, KEY.AttachDBFilename }, { SYNONYM.CONNECTRETRYCOUNT, KEY.Connect_Retry_Count }, { SYNONYM.CONNECTRETRYINTERVAL, KEY.Connect_Retry_Interval }, @@ -1102,6 +1111,27 @@ internal PoolBlockingPeriod ConvertValueToPoolBlockingPeriod() } } + internal SqlConnectionEncryptOption ConvertValueToSqlConnectionEncrypt() + { + if (!TryGetParsetableValue(KEY.Encrypt, out string value)) + { + return DEFAULT.Encrypt; + } + + try + { + return DbConnectionStringBuilderUtil.ConvertToSqlConnectionEncryptOption(KEY.Encrypt, value); + } + catch (FormatException e) + { + throw ADP.InvalidConnectionOptionValue(KEY.Encrypt, e); + } + catch (OverflowException e) + { + throw ADP.InvalidConnectionOptionValue(KEY.Encrypt, e); + } + } + static internal Hashtable NetlibMapping() { const int NetLibCount = 8; @@ -1161,10 +1191,10 @@ protected internal override PermissionSet CreatePermissionSet() return permissionSet; } - internal bool ConvertValueToEncrypt() + internal SqlConnectionEncryptOption ConvertValueToEncrypt() { - bool defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : true; - return ConvertValueToBoolean(KEY.Encrypt, defaultEncryptValue); + SqlConnectionEncryptOption defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : SqlConnectionEncryptOption.Mandatory; + return ConvertValueToSqlConnectionEncrypt(); } private readonly bool _connectionReset; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index b78c2e392b..da7e732f37 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -46,6 +46,7 @@ private enum Keywords Replication, ConnectTimeout, Encrypt, + HostNameInCertificate, TrustServerCertificate, LoadBalanceTimeout, PacketSize, @@ -105,7 +106,8 @@ private enum Keywords private int _packetSize = DbConnectionStringDefaults.PacketSize; private int _connectRetryCount = DbConnectionStringDefaults.ConnectRetryCount; private int _connectRetryInterval = DbConnectionStringDefaults.ConnectRetryInterval; - private bool _encrypt = DbConnectionStringDefaults.Encrypt; + private SqlConnectionEncryptOption _encrypt = DbConnectionStringDefaults.Encrypt; + private string _hostNameInCertificate = DbConnectionStringDefaults.HostNameInCertificate; private bool _trustServerCertificate = DbConnectionStringDefaults.TrustServerCertificate; private bool _enlist = DbConnectionStringDefaults.Enlist; private bool _integratedSecurity = DbConnectionStringDefaults.IntegratedSecurity; @@ -149,6 +151,7 @@ private static string[] CreateValidKeywords() validKeywords[(int)Keywords.CurrentLanguage] = DbConnectionStringKeywords.CurrentLanguage; validKeywords[(int)Keywords.DataSource] = DbConnectionStringKeywords.DataSource; validKeywords[(int)Keywords.Encrypt] = DbConnectionStringKeywords.Encrypt; + validKeywords[(int)Keywords.HostNameInCertificate] = DbConnectionStringKeywords.HostNameInCertificate; validKeywords[(int)Keywords.Enlist] = DbConnectionStringKeywords.Enlist; validKeywords[(int)Keywords.FailoverPartner] = DbConnectionStringKeywords.FailoverPartner; validKeywords[(int)Keywords.InitialCatalog] = DbConnectionStringKeywords.InitialCatalog; @@ -203,6 +206,7 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringKeywords.Encrypt, Keywords.Encrypt }, { DbConnectionStringKeywords.Enlist, Keywords.Enlist }, { DbConnectionStringKeywords.FailoverPartner, Keywords.FailoverPartner }, + { DbConnectionStringKeywords.HostNameInCertificate, Keywords.HostNameInCertificate }, { DbConnectionStringKeywords.InitialCatalog, Keywords.InitialCatalog }, { DbConnectionStringKeywords.IntegratedSecurity, Keywords.IntegratedSecurity }, { DbConnectionStringKeywords.LoadBalanceTimeout, Keywords.LoadBalanceTimeout }, @@ -245,6 +249,7 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringSynonyms.APP, Keywords.ApplicationName }, { DbConnectionStringSynonyms.APPLICATIONINTENT, Keywords.ApplicationIntent }, { DbConnectionStringSynonyms.EXTENDEDPROPERTIES, Keywords.AttachDBFilename }, + { DbConnectionStringSynonyms.HOSTNAMEINCERTIFICATE, Keywords.HostNameInCertificate }, { DbConnectionStringSynonyms.INITIALFILENAME, Keywords.AttachDBFilename }, { DbConnectionStringSynonyms.CONNECTIONTIMEOUT, Keywords.ConnectTimeout }, { DbConnectionStringSynonyms.CONNECTRETRYCOUNT, Keywords.ConnectRetryCount }, @@ -290,6 +295,10 @@ private static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSet private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(string keyword, object value) => DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(keyword, value); + private static SqlConnectionEncryptOption ConvertToSqlConnectionEncryptOption(string keyword, object value) + => DbConnectionStringBuilderUtil.ConvertToSqlConnectionEncryptOption(keyword, value); + + private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) => DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); @@ -318,6 +327,8 @@ private object GetAt(Keywords index) return DataSource; case Keywords.Encrypt: return Encrypt; + case Keywords.HostNameInCertificate: + return HostNameInCertificate; case Keywords.Enlist: return Enlist; case Keywords.FailoverPartner: @@ -440,6 +451,9 @@ private void Reset(Keywords index) case Keywords.Encrypt: _encrypt = DbConnectionStringDefaults.Encrypt; break; + case Keywords.HostNameInCertificate: + _hostNameInCertificate = DbConnectionStringDefaults.HostNameInCertificate; + break; case Keywords.Enlist: _enlist = DbConnectionStringDefaults.Enlist; break; @@ -571,6 +585,11 @@ private void SetAttestationProtocolValue(SqlConnectionAttestationProtocol value) base[DbConnectionStringKeywords.AttestationProtocol] = DbConnectionStringBuilderUtil.AttestationProtocolToString(value); } + private void SetSqlConnectionEncryptionValue(SqlConnectionEncryptOption value) + { + base[DbConnectionStringKeywords.Encrypt] = value.ToString(); + } + private void SetIPAddressPreferenceValue(SqlConnectionIPAddressPreference value) { Debug.Assert(DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value), "Invalid value for SqlConnectionIPAddressPreference"); @@ -978,7 +997,10 @@ public override object this[string keyword] PoolBlockingPeriod = ConvertToPoolBlockingPeriod(keyword, value); break; case Keywords.Encrypt: - Encrypt = ConvertToBoolean(value); + Encrypt = ConvertToSqlConnectionEncryptOption(keyword, value); + break; + case Keywords.HostNameInCertificate: + HostNameInCertificate = ConvertToString(value); break; case Keywords.TrustServerCertificate: TrustServerCertificate = ConvertToBoolean(value); @@ -1170,16 +1192,31 @@ public string DataSource [ResCategory(StringsHelper.ResourceNames.DataCategory_Security)] [ResDescription(StringsHelper.ResourceNames.DbConnectionString_Encrypt)] [RefreshProperties(RefreshProperties.All)] - public bool Encrypt + public SqlConnectionEncryptOption Encrypt { get => _encrypt; set { - SetValue(DbConnectionStringKeywords.Encrypt, value); + SetSqlConnectionEncryptionValue(value); _encrypt = value; } } + /// + [DisplayName(DbConnectionStringKeywords.HostNameInCertificate)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Security)] + [ResDescription(StringsHelper.ResourceNames.DbConnectionString_Encrypt)] + [RefreshProperties(RefreshProperties.All)] + public string HostNameInCertificate + { + get => _hostNameInCertificate; + set + { + SetValue(DbConnectionStringKeywords.HostNameInCertificate, value); + _hostNameInCertificate = value; + } + } + /// [DisplayName(DbConnectionStringKeywords.ColumnEncryptionSetting)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Security)] diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index 0cd0f13a48..956fb183c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; using System.Diagnostics; +using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { @@ -345,6 +347,7 @@ public enum ActiveDirectoryWorkflow : byte public const int SQL2005_MAJOR = 0x72; // the high-byte is sufficient to distinguish later versions public const int SQL2008_MAJOR = 0x73; public const int SQL2012_MAJOR = 0x74; + public const string TDS8_Protocol = "tds/8.0"; //TDS8 // Increments: public const int SQL2000SP1_INCREMENT = 0x00; @@ -621,6 +624,7 @@ public enum ActiveDirectoryWorkflow : byte public const uint SNI_SSL_VALIDATE_CERTIFICATE = 1; // This enables validation of server certificate public const uint SNI_SSL_USE_SCHANNEL_CACHE = 2; // This enables schannel session cache public const uint SNI_SSL_IGNORE_CHANNEL_BINDINGS = 0x10; // Used with SSL Provider, sent to SNIAddProvider in case of SQL Authentication & Encrypt. + public const uint SNI_SSL_SEND_ALPN_EXTENSION = 0x4000; // This flag instructs SSL provider to send the ALPN extension in the ClientHello message public const string DEFAULT_ENGLISH_CODE_PAGE_STRING = "iso_1"; public const short DEFAULT_ENGLISH_CODE_PAGE_VALUE = 1252; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/TestFixtures.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/TestFixtures.cs index 862dc40ec0..b5e920e1b0 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/TestFixtures.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/TestFixtures.cs @@ -34,7 +34,7 @@ public static string DefaultConnectionString(SqlConnectionColumnEncryptionSettin SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(); csb.DataSource = "localhost,12345"; csb.Pooling = false; - csb.Encrypt = false; + csb.Encrypt = SqlConnectionEncryptOption.Optional; csb.ConnectTimeout = 65534; csb.UserID = "prodUser1@FedAuthAzureSqlDb.onmicrosoft.com"; csb.ColumnEncryptionSetting = columnEncryptionSetting; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 0ec7a41973..039bf2f766 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -46,7 +46,7 @@ public void TransientFaultTest(uint errorCode) { DataSource = "localhost," + server.Port, IntegratedSecurity = true, - Encrypt = false + Encrypt = SqlConnectionEncryptOption.Optional }; using SqlConnection connection = new(builder.ConnectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index d80875af80..1962684b43 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Xunit; namespace Microsoft.Data.SqlClient.Tests @@ -52,7 +53,9 @@ public partial class SqlConnectionStringBuilderTest [InlineData("Addr = randomserver.sys.local; User Id = a; Password = b")] [InlineData("Database = master")] [InlineData("Enclave Attestation Url = http://dymmyurl")] - [InlineData("Encrypt = true")] + [InlineData("Encrypt = True")] + [InlineData("Encrypt = False")] + [InlineData("Encrypt = Strict")] [InlineData("Enlist = false")] [InlineData("Initial Catalog = Northwind; Failover Partner = randomserver.sys.local")] [InlineData("Initial Catalog = tempdb")] @@ -82,6 +85,8 @@ public partial class SqlConnectionStringBuilderTest [InlineData("User Instance = true")] [InlineData("Workstation ID = myworkstation")] [InlineData("WSID = myworkstation")] + [InlineData("Host Name In Certificate = tds.test.com")] + [InlineData("HostNameInCertificate = tds.test.com")] public void ConnectionStringTests(string connectionString) { ExecuteConnectionStringTests(connectionString); @@ -307,6 +312,62 @@ public void ConnectionStringBuilder_AttachDbFileName_DataDirectory(string value, Assert.Equal(expected, builder.ConnectionString); } + [Fact] + public void SetEncryptInConnectionStringMapsToString() + { + var data = new List> + { + Tuple.Create("Encrypt=yes", SqlConnectionEncryptOption.Mandatory), + Tuple.Create("Encrypt=no", SqlConnectionEncryptOption.Optional), + Tuple.Create("Encrypt=true", SqlConnectionEncryptOption.Mandatory), + Tuple.Create("Encrypt=false", SqlConnectionEncryptOption.Optional), + Tuple.Create("Encrypt=mandatory", SqlConnectionEncryptOption.Mandatory), + Tuple.Create("Encrypt=optional", SqlConnectionEncryptOption.Optional), + Tuple.Create("Encrypt=strict", SqlConnectionEncryptOption.Strict) + }; + + foreach (var item in data) + { + string connectionString = item.Item1; + SqlConnectionEncryptOption expected = item.Item2; + SqlConnection sqlConnection = new(connectionString); + SqlConnectionStringBuilder scsb = new(sqlConnection.ConnectionString); + Assert.Equal(expected, scsb.Encrypt); + } + } + + [Fact] + public void SetEncryptOnConnectionBuilderMapsToString() + { + var data = new List> + { + Tuple.Create("Encrypt=True", SqlConnectionEncryptOption.Mandatory), + Tuple.Create("Encrypt=False", SqlConnectionEncryptOption.Optional), + Tuple.Create("Encrypt=Strict", SqlConnectionEncryptOption.Strict) + }; + + foreach (Tuple item in data) + { + string expected = item.Item1; + SqlConnectionEncryptOption option = item.Item2; + SqlConnectionStringBuilder scsb = new(); + scsb.Encrypt = option; + Assert.Equal(expected, scsb.ConnectionString); + } + } + + [Fact] + public void ConnectionBuilderEncryptBackwardsCompatibility() + { + SqlConnectionStringBuilder builder = new(); + builder.Encrypt = false; + Assert.Equal("Encrypt=False", builder.ConnectionString); + Assert.False(builder.Encrypt); + builder.Encrypt = true; + Assert.Equal("Encrypt=True", builder.ConnectionString); + Assert.True(builder.Encrypt); + } + internal void ExecuteConnectionStringTests(string connectionString) { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index af8c76878a..ca0ff8ae22 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -873,28 +873,35 @@ public void ConnectionString_UserInstance_Invalid() Assert.Null(ex.ParamName); } - [Fact] - public void ConnectionString_OtherKeywords() + [Theory] + [InlineData("Application Name=test")] + [InlineData("App=test")] + // [InlineData("Connection Reset=true")] // see https://github.com/dotnet/SqlClient/issues/17 + [InlineData("Current Language=test")] + [InlineData("Language=test")] + [InlineData("Encrypt=false")] + [InlineData("Encrypt=true")] + [InlineData("Encrypt=yes")] + [InlineData("Encrypt=no")] + [InlineData("Encrypt=strict")] + [InlineData("Encrypt=mandatory")] + [InlineData("Encrypt=optional")] + [InlineData("Host Name In Certificate=tds.test.com")] + [InlineData("HostNameInCertificate=tds.test.com")] + [InlineData("Enlist=false")] + [InlineData("Enlist=true")] + [InlineData("Integrated Security=true")] + [InlineData("Trusted_connection=true")] + [InlineData("Max Pool Size=10")] + [InlineData("Min Pool Size=10")] + [InlineData("Pooling=true")] + [InlineData("attachdbfilename=dunno")] + [InlineData("extended properties=dunno")] + [InlineData("initial file name=dunno")] + public void ConnectionString_OtherKeywords(string connectionString) { SqlConnection cn = new SqlConnection(); - cn.ConnectionString = "Application Name=test"; - cn.ConnectionString = "App=test"; - // see https://github.com/dotnet/SqlClient/issues/17 - //cn.ConnectionString = "Connection Reset=true"; - cn.ConnectionString = "Current Language=test"; - cn.ConnectionString = "Language=test"; - cn.ConnectionString = "Encrypt=false"; - cn.ConnectionString = "Encrypt=true"; - cn.ConnectionString = "Enlist=false"; - cn.ConnectionString = "Enlist=true"; - cn.ConnectionString = "Integrated Security=true"; - cn.ConnectionString = "Trusted_connection=true"; - cn.ConnectionString = "Max Pool Size=10"; - cn.ConnectionString = "Min Pool Size=10"; - cn.ConnectionString = "Pooling=true"; - cn.ConnectionString = "attachdbfilename=dunno"; - cn.ConnectionString = "extended properties=dunno"; - cn.ConnectionString = "initial file name=dunno"; + cn.ConnectionString = connectionString; } [Fact] diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs index 3df270718f..51e2330bf0 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs @@ -45,7 +45,7 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool server._endpoint.Start(); int port = server._endpoint.ServerEndPoint.Port; - server._connectionStringBuilder = new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = false }; + server._connectionStringBuilder = new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional }; server.ConnectionString = server._connectionStringBuilder.ConnectionString; return server; } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs index ada66a79ff..0c06e6cf47 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -140,7 +140,7 @@ private static void ConnectionWithEncryptionTest(string connectionString) { IntegratedSecurity = true, ConnectTimeout = 2, - Encrypt = true + Encrypt = SqlConnectionEncryptOption.Mandatory }; OpenConnection(builder.ConnectionString); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs index 13f6f7926a..3552204886 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs @@ -43,7 +43,7 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool server._endpoint.Start(); int port = server._endpoint.ServerEndPoint.Port; - server.connectionStringBuilder = new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = 5, Encrypt = false }; + server.connectionStringBuilder = new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = 5, Encrypt = SqlConnectionEncryptOption.Optional }; server.ConnectionString = server.connectionStringBuilder.ConnectionString; return server; } diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 1aa6b58178..f8ee837453 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -20,7 +20,7 @@ - 5.0.0-preview2.22084.1 + 5.0.0-preview3.22165.1 4.3.1 4.3.0 @@ -38,7 +38,7 @@ 5.0.0 - 5.0.0-preview2.22084.1 + 5.0.0-preview3.22165.1 5.0.0 5.0.0 5.0.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index ea863b6f83..b4412f3e75 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -28,7 +28,7 @@ When using NuGet 3.x this package requires at least version 3.4. sqlclient microsoft.data.sqlclient - + @@ -42,7 +42,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -61,7 +61,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -80,7 +80,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + From 748fd36803ce50cdc6a3b029d6c12ebb1a15d5d4 Mon Sep 17 00:00:00 2001 From: Parminder Kaur <88398605+Kaur-Parminder@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:39:01 -0700 Subject: [PATCH 26/35] Update SNI Version 5.0.0-Preview3 (#1644) SNI 5.0.0-preview3.22165.1 From 04db1a40a95726af8567b7596653e20acdd7f19a Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Thu, 16 Jun 2022 13:27:47 -0700 Subject: [PATCH 27/35] Feature | Add support server SPN option (#1607) --- .../SqlConnection.xml | 2 + .../SqlConnectionStringBuilder.xml | 38 ++++++++++++ .../netcore/ref/Microsoft.Data.SqlClient.cs | 8 +++ .../Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 17 ++++-- .../SqlClient/SqlInternalConnectionTds.cs | 26 ++++---- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 18 +++--- .../Data/SqlClient/TdsParserStateObject.cs | 1 + .../SqlClient/TdsParserStateObjectManaged.cs | 4 +- .../SqlClient/TdsParserStateObjectNative.cs | 15 ++++- .../netcore/src/Resources/StringsHelper.cs | 4 +- .../netfx/ref/Microsoft.Data.SqlClient.cs | 8 +++ .../SqlClient/SqlInternalConnectionTds.cs | 29 +++++---- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 31 +++++++--- .../Data/Common/DbConnectionStringCommon.cs | 8 +++ .../Data/SqlClient/SqlConnectionString.cs | 24 +++++++- .../SqlClient/SqlConnectionStringBuilder.cs | 61 ++++++++++++++++++- .../SqlConnectionStringBuilderTest.cs | 4 ++ .../FunctionalTests/SqlConnectionTest.cs | 10 +++ .../ManualTests/DataCommon/DataTestUtility.cs | 19 ++++++ .../IntegratedAuthenticationTest.cs | 20 ++++++ 20 files changed, 291 insertions(+), 56 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index 7e007f9180..304eb234a3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -540,6 +540,7 @@ End Module |Encrypt|'true' in 4.0 and above

'false' in 3.x and below|Recognized values are:
versions 1 - 4: `true`/`yes` and `false`/`no`
versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `Encrypt` is true or strict and `TrustServerCertificate` is false, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| |Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.| |Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.

If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".

The server name can be 128 characters or less.

If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.| +|Failover Partner SPN

-or-

FailoverPartnerSPN|N/A|The SPN for the failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| |Host Name In Certificate

-or-

HostNameInCertificate|N/A|Available starting in version 5.0.

The host name to use when validating the server certificate. When not specified, the server name from the Data Source is used for certificate validation.| |Initial Catalog

-or-

Database|N/A|The name of the database.

The database name can be 128 characters or less.| |Integrated Security

-or-

Trusted_Connection|'false'|When `false`, User ID and Password are specified in the connection. When `true`, the current Windows account credentials are used for authentication.

Recognized values are `true`, `false`, `yes`, `no`, and `sspi` (strongly recommended), which is equivalent to `true`.

If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used.

is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`).| @@ -556,6 +557,7 @@ End Module |Pool Blocking Period

-or-

PoolBlockingPeriod|Auto|Sets the blocking period behavior for a connection pool. See property for details.| |Pooling|'true'|When the value of this key is set to true, any newly created connection will be added to the pool when closed by the application. In a next attempt to open the same connection, that connection will be drawn from the pool.

Connections are considered the same if they have the same connection string. Different connections have different connection strings.

The value of this key can be "true", "false", "yes", or "no".| |Replication|'false'|`true` if replication is supported using the connection.| +|Server SPN

-or-

ServerSPN|N/A|The SPN for the data source. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| |Transaction Binding|Implicit Unbind|Controls connection association with an enlisted `System.Transactions` transaction.

Possible values are:

`Transaction Binding=Implicit Unbind;`

`Transaction Binding=Explicit Unbind;`

Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The `System.Transactions.Transaction.Current` property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode.

If the system ends the transaction (in the scope of a using block) before the last command completes, it will throw .

Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit `SqlConnection.TransactionEnlist(null)` is called. Beginning in .NET Framework 4.0, changes to Implicit Unbind make Explicit Unbind obsolete. An `InvalidOperationException` is thrown if `Transaction.Current` is not the enlisted transaction or if the enlisted transaction is not active.| |Transparent Network IP Resolution

-or-

TransparentNetworkIPResolution|See description.|When the value of this key is set to `true`, the application is required to retrieve all IP addresses for a particular DNS entry and attempt to connect with the first one in the list. If the connection is not established within 0.5 seconds, the application will try to connect to all others in parallel. When the first answers, the application will establish the connection with the respondent IP address.

If the `MultiSubnetFailover` key is set to `true`, `TransparentNetworkIPResolution` is ignored.

If the `Failover Partner` key is set, `TransparentNetworkIPResolution` is ignored.

The value of this key must be `true`, `false`, `yes`, or `no`.

A value of `yes` is treated the same as a value of `true`.

A value of `no` is treated the same as a value of `false`.

The default values are as follows:

  • `false` when:

    • Connecting to Azure SQL Database where the data source ends with:

      • .database.chinacloudapi.cn
      • .database.usgovcloudapi.net
      • .database.cloudapi.de
      • .database.windows.net
    • `Authentication` is 'Active Directory Password' or 'Active Directory Integrated'
  • `true` in all other cases.
| |Trust Server Certificate

-or-

TrustServerCertificate|'false'|When set to `true`, TLS is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to `true` and Encrypt is set to `false`, the channel is not encrypted. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).| diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index a73dfdaa9c..d1c0d6a360 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -459,6 +459,25 @@ If you specify a failover partner and the primary server is not configured for d ]]> + + Gets or sets the service principal name (SPN) of the failover partner for the connection. + + The value of the property, or if none has been supplied. + + + + [!NOTE] +> This property only applies when using Integrated Security mode, otherwise it is ignored. + + ]]> + + + To be added. To be added. @@ -820,6 +839,25 @@ Database = AdventureWorks ]]> + + Gets or sets the service principal name (SPN) of the data source. + + The value of the property, or if none has been supplied. + + + + [!NOTE] +> This property only applies when using Integrated Security mode, otherwise it is ignored. + + ]]> + + + The key to locate in the . Indicates whether the specified key exists in this instance. diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index ca6ac9d845..deb4ad0bf9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -1037,6 +1037,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Failover Partner")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public string FailoverPartner { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string FailoverPartnerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Initial Catalog")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] @@ -1095,6 +1099,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Replication")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public bool Replication { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Server SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string ServerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Transaction Binding")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index 885ea0c806..d94874f908 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -9,7 +9,6 @@ using System.Net.Security; using System.Net.Sockets; using System.Text; -using System.Text.RegularExpressions; namespace Microsoft.Data.SqlClient.SNI { @@ -69,7 +68,7 @@ internal static void GenSspiClientContext(SspiClientContextStatus sspiClientCont string[] serverSPNs = new string[serverName.Length]; for (int i = 0; i < serverName.Length; i++) { - serverSPNs[i] = Encoding.UTF8.GetString(serverName[i]); + serverSPNs[i] = Encoding.Unicode.GetString(serverName[i]); } SecurityStatusPal statusCode = NegotiateStreamPal.InitializeSecurityContext( credentialsHandle, @@ -135,6 +134,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode) /// Timer expiration /// Instance name /// SPN + /// pre-defined SPN /// Flush packet cache /// Asynchronous connection /// Attempt parallel connects @@ -151,6 +151,7 @@ internal static SNIHandle CreateConnectionHandle( long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, + string serverSPN, bool flushCache, bool async, bool parallel, @@ -200,7 +201,7 @@ internal static SNIHandle CreateConnectionHandle( { try { - spnBuffer = GetSqlServerSPNs(details); + spnBuffer = GetSqlServerSPNs(details, serverSPN); } catch (Exception e) { @@ -212,9 +213,13 @@ internal static SNIHandle CreateConnectionHandle( return sniHandle; } - private static byte[][] GetSqlServerSPNs(DataSource dataSource) + private static byte[][] GetSqlServerSPNs(DataSource dataSource, string serverSPN) { Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName)); + if (!string.IsNullOrWhiteSpace(serverSPN)) + { + return new byte[1][] { Encoding.Unicode.GetBytes(serverSPN) }; + } string hostName = dataSource.ServerName; string postfix = null; @@ -262,12 +267,12 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr string serverSpnWithDefaultPort = serverSpn + $":{DefaultSqlServerPort}"; // Set both SPNs with and without Port as Port is optional for default instance SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPNs {0} and {1}", serverSpn, serverSpnWithDefaultPort); - return new byte[][] { Encoding.UTF8.GetBytes(serverSpn), Encoding.UTF8.GetBytes(serverSpnWithDefaultPort) }; + return new byte[][] { Encoding.Unicode.GetBytes(serverSpn), Encoding.Unicode.GetBytes(serverSpnWithDefaultPort) }; } // else Named Pipes do not need to valid port SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPN {0}", serverSpn); - return new byte[][] { Encoding.UTF8.GetBytes(serverSpn) }; + return new byte[][] { Encoding.Unicode.GetBytes(serverSpn) }; } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 082e24fb96..95c098cfc6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1552,7 +1552,7 @@ private void LoginNoFailover(ServerInfo serverInfo, throw SQL.ROR_TimeoutAfterRoutingInfo(this); } - serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName); + serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = serverInfo.UserServerName; @@ -1696,7 +1696,7 @@ TimeoutTimer timeout int sleepInterval = 100; //milliseconds to sleep (back off) between attempts. long timeoutUnitInterval; - ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost); + ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); if (null == ServerProvidedFailOverPartner) @@ -1910,12 +1910,8 @@ private void AttemptOneLogin( this, ignoreSniOpenTimeout, timeout.LegacyTimerExpire, - ConnectionOptions.Encrypt, - ConnectionOptions.TrustServerCertificate, - ConnectionOptions.IntegratedSecurity, - withFailover, - ConnectionOptions.Authentication, - ConnectionOptions.HostNameInCertificate); + ConnectionOptions, + withFailover); _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); _timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); @@ -2797,6 +2793,7 @@ internal sealed class ServerInfo internal string ResolvedServerName { get; private set; } // the resolved servername only internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution internal string UserProtocol { get; private set; } // the user specified protocol + internal string ServerSPN { get; private set; } // the server SPN // The original user-supplied server name from the connection string. // If connection string has no Data Source, the value is set to string.Empty. @@ -2817,10 +2814,16 @@ private set internal readonly string PreRoutingServerName; // Initialize server info from connection options, - internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) { } + internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource, userOptions.ServerSPN) { } + + // Initialize server info from connection options, but override DataSource and ServerSPN with given server name and server SPN + internal ServerInfo(SqlConnectionString userOptions, string serverName, string serverSPN) : this(userOptions, serverName) + { + ServerSPN = serverSPN; + } // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, string serverName) + private ServerInfo(SqlConnectionString userOptions, string serverName) { //----------------- // Preconditions @@ -2839,7 +2842,7 @@ internal ServerInfo(SqlConnectionString userOptions, string serverName) // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) + internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName, string serverSPN) { //----------------- // Preconditions @@ -2860,6 +2863,7 @@ internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string UserProtocol = TdsEnums.TCP; SetDerivedNames(UserProtocol, UserServerName); ResolvedDatabaseName = userOptions.InitialCatalog; + ServerSPN = serverSPN; } internal void SetDerivedNames(string protocol, string serverName) 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 8471782e7c..73c065c27d 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 @@ -359,13 +359,15 @@ internal void Connect( SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, - SqlConnectionEncryptOption encrypt, - bool trustServerCert, - bool integratedSecurity, - bool withFailover, - SqlAuthenticationMethod authType, - string hostNameInCertificate) + SqlConnectionString connectionOptions, + bool withFailover) { + SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; + bool trustServerCert = connectionOptions.TrustServerCertificate; + bool integratedSecurity = connectionOptions.IntegratedSecurity; + SqlAuthenticationMethod authType = connectionOptions.Authentication; + string hostNameInCertificate = connectionOptions.HostNameInCertificate; + if (_state != TdsParserState.Closed) { Debug.Fail("TdsParser.Connect called on non-closed connection!"); @@ -436,7 +438,7 @@ internal void Connect( // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, - false, true, fParallel, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, + false, true, fParallel, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, serverInfo.ServerSPN , integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated, encrypt == SqlConnectionEncryptOption.Strict, hostNameInCertificate); @@ -516,7 +518,7 @@ internal void Connect( _physicalStateObj.SniContext = SniContext.Snix_Connect; _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, true, true, fParallel, - _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, integratedSecurity); + _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, serverInfo.ServerSPN, integratedSecurity); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 1fe6a5a9fc..21f4e9b61b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -798,6 +798,7 @@ internal abstract void CreatePhysicalSNIHandle( SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, + string serverSPN, bool isIntegratedSecurity = false, bool tlsFirst = false, string hostNameInCertificate = ""); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs index e57ffdfb94..f4523f87b4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -87,13 +87,15 @@ internal override void CreatePhysicalSNIHandle( SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, + string serverSPN, bool isIntegratedSecurity, bool tlsFirst, string hostNameInCertificate) { - SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, + SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, serverSPN, flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst, hostNameInCertificate); + if (sessionHandle is not null) { _sessionHandle = sessionHandle; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index 7b31d2e5b4..99bbc9bf53 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Data.Common; using System.Net; +using System.Text; namespace Microsoft.Data.SqlClient { @@ -149,6 +150,7 @@ internal override void CreatePhysicalSNIHandle( SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, + string serverSPN, bool isIntegratedSecurity, bool tlsFirst, string hostNameInCertificate) @@ -158,7 +160,18 @@ internal override void CreatePhysicalSNIHandle( if (isIntegratedSecurity) { // now allocate proper length of buffer - spnBuffer[0] = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + if (!string.IsNullOrEmpty(serverSPN)) + { + // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. + byte[] srvSPN = Encoding.Unicode.GetBytes(serverSPN); + Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "Length of the provided SPN exceeded the buffer size."); + spnBuffer[0] = srvSPN; + SqlClientEventSource.Log.TryTraceEvent("<{0}.{1}|SEC> Server SPN `{2}` from the connection string is used.",nameof(TdsParserStateObjectNative), nameof(CreatePhysicalSNIHandle), serverSPN); + } + else + { + spnBuffer[0] = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + } } SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs index 067426eee3..f4bfdbe67d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs @@ -104,8 +104,9 @@ internal class ResourceNames internal const string DbConnectionString_DataSource = @"Indicates the name of the data source to connect to."; internal const string DbConnectionString_Encrypt = @"When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed."; internal const string DbConnectionString_Enlist = @"Sessions in a Component Services (or MTS, if you are using Microsoft Windows NT) environment should automatically be enlisted in a global transaction where required."; - internal const string DbConnectionString_InitialCatalog = @"The name of the initial catalog or database in the data source."; internal const string DbConnectionString_FailoverPartner = @"The name or network address of the instance of SQL Server that acts as a failover partner."; + internal const string DbConnectionString_FailoverPartnerSPN = @"The service principal name (SPN) of the failover partner."; + internal const string DbConnectionString_InitialCatalog = @"The name of the initial catalog or database in the data source."; internal const string DbConnectionString_IntegratedSecurity = @"Whether the connection is to be a secure connection or not."; internal const string DbConnectionString_LoadBalanceTimeout = @"The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed."; internal const string DbConnectionString_MaxPoolSize = @"The maximum number of connections allowed in the pool."; @@ -117,6 +118,7 @@ internal class ResourceNames internal const string DbConnectionString_PersistSecurityInfo = @"When false, security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state."; internal const string DbConnectionString_Pooling = @"When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool."; internal const string DbConnectionString_Replication = @"Used by SQL Server in Replication."; + internal const string DbConnectionString_ServerSPN = @"The service principal name (SPN) of the server."; internal const string DbConnectionString_TransactionBinding = @"Indicates binding behavior of connection to a System.Transactions Transaction when enlisted."; internal const string DbConnectionString_TrustServerCertificate = @"When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate."; internal const string DbConnectionString_TypeSystemVersion = @"Indicates which server type system the provider will expose through the DataReader."; diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 4a203d13bd..29cd583c94 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -1045,6 +1045,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Failover Partner")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public string FailoverPartner { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string FailoverPartnerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Initial Catalog")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] @@ -1108,6 +1112,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Replication")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public bool Replication { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Server SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string ServerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Transaction Binding")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index a50188950f..12065a2f2c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1879,7 +1879,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt throw SQL.ROR_TimeoutAfterRoutingInfo(this); } - serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName); + serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = serverInfo.UserServerName; @@ -2049,7 +2049,7 @@ TimeoutTimer timeout long timeoutUnitInterval; string protocol = ConnectionOptions.NetworkLibrary; - ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost); + ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); if (null == ServerProvidedFailOverPartner) @@ -2152,7 +2152,7 @@ TimeoutTimer timeout _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); - currentServerInfo = new ServerInfo(ConnectionOptions, _routingInfo, currentServerInfo.ResolvedServerName); + currentServerInfo = new ServerInfo(ConnectionOptions, _routingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN); timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = currentServerInfo.UserServerName; @@ -2298,18 +2298,13 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt this, ignoreSniOpenTimeout, timeout.LegacyTimerExpire, - ConnectionOptions.Encrypt, - ConnectionOptions.TrustServerCertificate, - ConnectionOptions.IntegratedSecurity, + ConnectionOptions, withFailover, isFirstTransparentAttempt, - ConnectionOptions.Authentication, - ConnectionOptions.Certificate, _serverCallback, _clientCallback, _originalNetworkAddressInfo != null, - disableTnir, - ConnectionOptions.HostNameInCertificate); + disableTnir); timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); @@ -3250,6 +3245,7 @@ internal sealed class ServerInfo internal string ResolvedServerName { get; private set; } // the resolved servername only internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution internal string UserProtocol { get; private set; } // the user specified protocol + internal string ServerSPN { get; private set; } // the server SPN // The original user-supplied server name from the connection string. // If connection string has no Data Source, the value is set to string.Empty. @@ -3270,10 +3266,16 @@ private set internal readonly string PreRoutingServerName; // Initialize server info from connection options, - internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) { } + internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource, userOptions.ServerSPN) { } + + // Initialize server info from connection options, but override DataSource and ServerSPN with given server name and server SPN + internal ServerInfo(SqlConnectionString userOptions, string serverName, string serverSPN) : this(userOptions, serverName) + { + ServerSPN = serverSPN; + } // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, string serverName) + private ServerInfo(SqlConnectionString userOptions, string serverName) { //----------------- // Preconditions @@ -3292,7 +3294,7 @@ internal ServerInfo(SqlConnectionString userOptions, string serverName) // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) + internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName, string serverSPN) { //----------------- // Preconditions @@ -3313,6 +3315,7 @@ internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string UserProtocol = TdsEnums.TCP; SetDerivedNames(UserProtocol, UserServerName); ResolvedDatabaseName = userOptions.InitialCatalog; + ServerSPN = serverSPN; } internal void SetDerivedNames(string protocol, string serverName) 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 9a2543d574..664c6e56d5 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 @@ -494,19 +494,21 @@ internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, - SqlConnectionEncryptOption encrypt, - bool trustServerCert, - bool integratedSecurity, + SqlConnectionString connectionOptions, bool withFailover, bool isFirstTransparentAttempt, - SqlAuthenticationMethod authType, - string certificate, ServerCertificateValidationCallback serverCallback, ClientCertificateRetrievalCallback clientCallback, bool useOriginalAddressInfo, - bool disableTnir, - string hostNameInCertificate) + bool disableTnir) { + SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; + bool trustServerCert = connectionOptions.TrustServerCertificate; + bool integratedSecurity = connectionOptions.IntegratedSecurity; + SqlAuthenticationMethod authType = connectionOptions.Authentication; + string certificate = connectionOptions.Certificate; + string hostNameInCertificate = connectionOptions.HostNameInCertificate; + if (_state != TdsParserState.Closed) { Debug.Fail("TdsParser.Connect called on non-closed connection!"); @@ -544,8 +546,19 @@ internal void Connect(ServerInfo serverInfo, if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { LoadSSPILibrary(); - // now allocate proper length of buffer - _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) + { + // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. + byte[] srvSPN = Encoding.Unicode.GetBytes(serverInfo.ServerSPN); + Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provided SPN length exceeded the buffer size."); + _sniSpnBuffer = srvSPN; + SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverInfo.ServerSPN); + } + else + { + // now allocate proper length of buffer + _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + } SqlClientEventSource.Log.TryTraceEvent(" SSPI or Active Directory Authentication Library for SQL Server based integrated authentication"); } else diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 98d1fc1d51..ee6fafa0f0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -991,6 +991,8 @@ internal static class DbConnectionStringDefaults internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified; internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; internal const PoolBlockingPeriod PoolBlockingPeriod = SqlClient.PoolBlockingPeriod.Auto; + internal const string ServerSPN = ""; + internal const string FailoverPartnerSPN = ""; } internal static class DbConnectionStringKeywords @@ -1045,6 +1047,8 @@ internal static class DbConnectionStringKeywords internal const string EnclaveAttestationUrl = "Enclave Attestation Url"; internal const string AttestationProtocol = "Attestation Protocol"; internal const string IPAddressPreference = "IP Address Preference"; + internal const string ServerSPN = "Server SPN"; + internal const string FailoverPartnerSPN = "Failover Partner SPN"; // common keywords (OleDb, OracleClient, SqlClient) internal const string DataSource = "Data Source"; @@ -1141,5 +1145,9 @@ internal static class DbConnectionStringSynonyms //internal const string WorkstationID = WSID; internal const string WSID = "wsid"; + + //internal const string server SPNs + internal const string ServerSPN = "ServerSPN"; + internal const string FailoverPartnerSPN = "FailoverPartnerSPN"; } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index 141732ba3f..dd68546330 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -60,6 +60,8 @@ internal static class DEFAULT internal static readonly SqlAuthenticationMethod Authentication = DbConnectionStringDefaults.Authentication; internal static readonly SqlConnectionAttestationProtocol AttestationProtocol = DbConnectionStringDefaults.AttestationProtocol; internal static readonly SqlConnectionIPAddressPreference IpAddressPreference = DbConnectionStringDefaults.IPAddressPreference; + internal const string ServerSPN = DbConnectionStringDefaults.ServerSPN; + internal const string FailoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN; #if NETFRAMEWORK internal static readonly bool TransparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIPResolution; internal const bool Connection_Reset = DbConnectionStringDefaults.ConnectionReset; @@ -115,6 +117,8 @@ internal static class KEY internal const string Connect_Retry_Count = DbConnectionStringKeywords.ConnectRetryCount; internal const string Connect_Retry_Interval = DbConnectionStringKeywords.ConnectRetryInterval; internal const string Authentication = DbConnectionStringKeywords.Authentication; + internal const string Server_SPN = DbConnectionStringKeywords.ServerSPN; + internal const string Failover_Partner_SPN = DbConnectionStringKeywords.FailoverPartnerSPN; #if NETFRAMEWORK internal const string TransparentNetworkIPResolution = DbConnectionStringKeywords.TransparentNetworkIPResolution; #if ADONET_CERT_AUTH @@ -177,6 +181,9 @@ private static class SYNONYM internal const string User = DbConnectionStringSynonyms.User; // workstation id internal const string WSID = DbConnectionStringSynonyms.WSID; + // server SPNs + internal const string ServerSPN = DbConnectionStringSynonyms.ServerSPN; + internal const string FailoverPartnerSPN = DbConnectionStringSynonyms.FailoverPartnerSPN; #if NETFRAMEWORK internal const string TRANSPARENTNETWORKIPRESOLUTION = DbConnectionStringSynonyms.TRANSPARENTNETWORKIPRESOLUTION; @@ -216,9 +223,9 @@ internal static class TRANSACTIONBINDING } #if NETFRAMEWORK - internal const int SynonymCount = 30; + internal const int SynonymCount = 32; #else - internal const int SynonymCount = 27; + internal const int SynonymCount = 29; internal const int DeprecatedSynonymCount = 2; #endif // NETFRAMEWORK @@ -262,6 +269,8 @@ internal static class TRANSACTIONBINDING private readonly string _password; private readonly string _userID; private readonly string _hostNameInCertificate; + private readonly string _serverSPN; + private readonly string _failoverPartnerSPN; private readonly string _workstationId; @@ -328,6 +337,8 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _attestationProtocol = ConvertValueToAttestationProtocol(); _ipAddressPreference = ConvertValueToIPAddressPreference(); _hostNameInCertificate = ConvertValueToString(KEY.HostNameInCertificate, DEFAULT.HostNameInCertificate); + _serverSPN = ConvertValueToString(KEY.Server_SPN, DEFAULT.ServerSPN); + _failoverPartnerSPN = ConvertValueToString(KEY.Failover_Partner_SPN, DEFAULT.FailoverPartnerSPN); // Temporary string - this value is stored internally as an enum. string typeSystemVersionString = ConvertValueToString(KEY.Type_System_Version, null); @@ -681,6 +692,8 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS _columnEncryptionSetting = connectionOptions._columnEncryptionSetting; _enclaveAttestationUrl = connectionOptions._enclaveAttestationUrl; _attestationProtocol = connectionOptions._attestationProtocol; + _serverSPN = connectionOptions._serverSPN; + _failoverPartnerSPN = connectionOptions._failoverPartnerSPN; #if NETFRAMEWORK _connectionReset = connectionOptions._connectionReset; _contextConnection = connectionOptions._contextConnection; @@ -739,7 +752,8 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS internal string UserID => _userID; internal string WorkstationId => _workstationId; internal PoolBlockingPeriod PoolBlockingPeriod => _poolBlockingPeriod; - + internal string ServerSPN => _serverSPN; + internal string FailoverPartnerSPN => _failoverPartnerSPN; internal TypeSystem TypeSystemVersion => _typeSystemVersion; internal Version TypeSystemAssemblyVersion => _typeSystemAssemblyVersion; @@ -851,6 +865,8 @@ internal static Dictionary GetParseSynonyms() { KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval }, { KEY.Authentication, KEY.Authentication }, { KEY.IPAddressPreference, KEY.IPAddressPreference }, + { KEY.Server_SPN, KEY.Server_SPN }, + { KEY.Failover_Partner_SPN, KEY.Failover_Partner_SPN }, { SYNONYM.APP, KEY.Application_Name }, { SYNONYM.APPLICATIONINTENT, KEY.ApplicationIntent }, @@ -880,6 +896,8 @@ internal static Dictionary GetParseSynonyms() { SYNONYM.UID, KEY.User_ID }, { SYNONYM.User, KEY.User_ID }, { SYNONYM.WSID, KEY.Workstation_Id }, + { SYNONYM.ServerSPN, KEY.Server_SPN }, + { SYNONYM.FailoverPartnerSPN, KEY.Failover_Partner_SPN }, #if NETFRAMEWORK #if ADONET_CERT_AUTH { KEY.Certificate, KEY.Certificate }, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index da7e732f37..28a8fbc6fe 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -66,6 +66,8 @@ private enum Keywords AttestationProtocol, CommandTimeout, IPAddressPreference, + ServerSPN, + FailoverPartnerSPN, #if NETFRAMEWORK ConnectionReset, NetworkLibrary, @@ -124,6 +126,8 @@ private enum Keywords private string _enclaveAttestationUrl = DbConnectionStringDefaults.EnclaveAttestationUrl; private SqlConnectionAttestationProtocol _attestationProtocol = DbConnectionStringDefaults.AttestationProtocol; private SqlConnectionIPAddressPreference _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference; + private string _serverSPN = DbConnectionStringDefaults.ServerSPN; + private string _failoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN; #if NETFRAMEWORK private bool _connectionReset = DbConnectionStringDefaults.ConnectionReset; @@ -179,11 +183,13 @@ private static string[] CreateValidKeywords() validKeywords[(int)Keywords.EnclaveAttestationUrl] = DbConnectionStringKeywords.EnclaveAttestationUrl; validKeywords[(int)Keywords.AttestationProtocol] = DbConnectionStringKeywords.AttestationProtocol; validKeywords[(int)Keywords.IPAddressPreference] = DbConnectionStringKeywords.IPAddressPreference; + validKeywords[(int)Keywords.ServerSPN] = DbConnectionStringKeywords.ServerSPN; + validKeywords[(int)Keywords.FailoverPartnerSPN] = DbConnectionStringKeywords.FailoverPartnerSPN; #if NETFRAMEWORK validKeywords[(int)Keywords.ConnectionReset] = DbConnectionStringKeywords.ConnectionReset; + validKeywords[(int)Keywords.NetworkLibrary] = DbConnectionStringKeywords.NetworkLibrary; validKeywords[(int)Keywords.ContextConnection] = DbConnectionStringKeywords.ContextConnection; validKeywords[(int)Keywords.TransparentNetworkIPResolution] = DbConnectionStringKeywords.TransparentNetworkIPResolution; - validKeywords[(int)Keywords.NetworkLibrary] = DbConnectionStringKeywords.NetworkLibrary; #if ADONET_CERT_AUTH validKeywords[(int)Keywords.Certificate] = DbConnectionStringKeywords.Certificate; #endif @@ -232,6 +238,8 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringKeywords.EnclaveAttestationUrl, Keywords.EnclaveAttestationUrl }, { DbConnectionStringKeywords.AttestationProtocol, Keywords.AttestationProtocol }, { DbConnectionStringKeywords.IPAddressPreference, Keywords.IPAddressPreference }, + { DbConnectionStringKeywords.ServerSPN, Keywords.ServerSPN }, + { DbConnectionStringKeywords.FailoverPartnerSPN, Keywords.FailoverPartnerSPN }, #if NETFRAMEWORK { DbConnectionStringKeywords.ConnectionReset, Keywords.ConnectionReset }, @@ -271,7 +279,9 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringSynonyms.PERSISTSECURITYINFO, Keywords.PersistSecurityInfo }, { DbConnectionStringSynonyms.UID, Keywords.UserID }, { DbConnectionStringSynonyms.User, Keywords.UserID }, - { DbConnectionStringSynonyms.WSID, Keywords.WorkstationID } + { DbConnectionStringSynonyms.WSID, Keywords.WorkstationID }, + { DbConnectionStringSynonyms.ServerSPN, Keywords.ServerSPN }, + { DbConnectionStringSynonyms.FailoverPartnerSPN, Keywords.FailoverPartnerSPN }, }; Debug.Assert((KeywordsCount + SqlConnectionString.SynonymCount) == pairs.Count, "initial expected size is incorrect"); return pairs; @@ -384,7 +394,10 @@ private object GetAt(Keywords index) return AttestationProtocol; case Keywords.IPAddressPreference: return IPAddressPreference; - + case Keywords.ServerSPN: + return ServerSPN; + case Keywords.FailoverPartnerSPN: + return FailoverPartnerSPN; #if NETFRAMEWORK #pragma warning disable 618 // Obsolete properties case Keywords.ConnectionReset: @@ -532,6 +545,12 @@ private void Reset(Keywords index) case Keywords.IPAddressPreference: _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference; break; + case Keywords.ServerSPN: + _serverSPN = DbConnectionStringDefaults.ServerSPN; + break; + case Keywords.FailoverPartnerSPN: + _failoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN; + break; #if NETFRAMEWORK case Keywords.ConnectionReset: _connectionReset = DbConnectionStringDefaults.ConnectionReset; @@ -1032,6 +1051,12 @@ public override object this[string keyword] case Keywords.ConnectRetryInterval: ConnectRetryInterval = ConvertToInt32(value); break; + case Keywords.ServerSPN: + ServerSPN = ConvertToString(value); + break; + case Keywords.FailoverPartnerSPN: + FailoverPartnerSPN = ConvertToString(value); + break; #if NETFRAMEWORK #pragma warning disable 618 // Obsolete properties case Keywords.ConnectionReset: @@ -1187,6 +1212,21 @@ public string DataSource } } + /// + [DisplayName(DbConnectionStringKeywords.ServerSPN)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] + [ResDescription(StringsHelper.ResourceNames.DbConnectionString_ServerSPN)] + [RefreshProperties(RefreshProperties.All)] + public string ServerSPN + { + get => _serverSPN; + set + { + SetValue(DbConnectionStringKeywords.ServerSPN, value); + _serverSPN = value; + } + } + /// [DisplayName(DbConnectionStringKeywords.Encrypt)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Security)] @@ -1340,6 +1380,21 @@ public string FailoverPartner } } + /// + [DisplayName(DbConnectionStringKeywords.FailoverPartnerSPN)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] + [ResDescription(StringsHelper.ResourceNames.DbConnectionString_FailoverPartnerSPN)] + [RefreshProperties(RefreshProperties.All)] + public string FailoverPartnerSPN + { + get => _failoverPartnerSPN; + set + { + SetValue(DbConnectionStringKeywords.FailoverPartnerSPN, value); + _failoverPartnerSPN = value; + } + } + /// [DisplayName(DbConnectionStringKeywords.InitialCatalog)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 1962684b43..65e0a5d32f 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -87,6 +87,10 @@ public partial class SqlConnectionStringBuilderTest [InlineData("WSID = myworkstation")] [InlineData("Host Name In Certificate = tds.test.com")] [InlineData("HostNameInCertificate = tds.test.com")] + [InlineData("Server SPN = server1")] + [InlineData("ServerSPN = server2")] + [InlineData("Failover Partner SPN = server3")] + [InlineData("FailoverPartnerSPN = server4")] public void ConnectionStringTests(string connectionString) { ExecuteConnectionStringTests(connectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index ca0ff8ae22..1ce20f9735 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -1040,6 +1040,16 @@ public void ConnectionString_IPAddressPreference_Invalid(string value) Assert.Null(ex.ParamName); } + [Theory] + [InlineData("Server SPN = server1")] + [InlineData("ServerSPN = server2")] + [InlineData("Failover Partner SPN = server3")] + [InlineData("FailoverPartnerSPN = server4")] + public void ConnectionString_ServerSPN_FailoverPartnerSPN(string value) + { + SqlConnection _ = new(value); + } + [Fact] public void ConnectionRetryForNonAzureEndpoints() { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 9e5b10cdd7..5fed0099a7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -18,6 +18,8 @@ using Microsoft.Data.SqlClient.TestUtilities; using Microsoft.Identity.Client; using Xunit; +using System.Net.NetworkInformation; +using System.Text; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -906,5 +908,22 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) } } } + + /// + /// Resolves the machine's fully qualified domain name if it is applicable. + /// + /// Returns FQDN if the client was domain joined otherwise the machine name. + public static string GetMachineFQDN() + { + IPGlobalProperties machineInfo = IPGlobalProperties.GetIPGlobalProperties(); + StringBuilder fqdn = new(); + fqdn.Append(machineInfo.HostName); + if (!string.IsNullOrEmpty(machineInfo.DomainName)) + { + fqdn.Append("."); + fqdn.Append(machineInfo.DomainName); + } + return fqdn.ToString(); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs index b766502833..6cd19714ae 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs @@ -30,6 +30,26 @@ public static void IntegratedAuthenticationTestWithOutConnectionPooling() TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString); } + [ActiveIssue(21707)] + [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] + public static void IntegratedAuthenticationTest_InvalidServerSPN() + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + builder.IntegratedSecurity = true; + builder.ServerSPN = "InvalidServerSPN"; + SqlException ex = Assert.Throws(() => TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString)); + Assert.Contains("generate SSPI context.", ex.Message); + } + + [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] + public static void IntegratedAuthenticationTest_ServerSPN() + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + builder.IntegratedSecurity = true; + builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN()}"; + TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString); + } + private static void TryOpenConnectionWithIntegratedAuthentication(string connectionString) { using (SqlConnection connection = new SqlConnection(connectionString)) From 25126575ccf1b5383bad6a46d599069a2679385c Mon Sep 17 00:00:00 2001 From: SqlClient DevOps Date: Thu, 16 Jun 2022 22:16:09 +0000 Subject: [PATCH 28/35] [Scheduled Run] Localized resource files from OneLocBuild --- .../netfx/src/Resources/Strings.de.resx | 4 ++-- .../netfx/src/Resources/Strings.es.resx | 4 ++-- .../netfx/src/Resources/Strings.fr.resx | 4 ++-- .../netfx/src/Resources/Strings.ko.resx | 4 ++-- .../netfx/src/Resources/Strings.pt-BR.resx | 4 ++-- .../netfx/src/Resources/Strings.zh-Hans.resx | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index 240957a68f..3ed4714c64 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -4621,9 +4621,9 @@ Timeout bei der Verbindung beim Abrufen eines Zugriffstokens mithilfe der Authentifizierungsmethode "{0}". Letzter Fehler: {1}: {2} - The service principal name (SPN) of the failover partner. + Der Dienstprinzipalname (SPN) des Failoverpartners. - The service principal name (SPN) of the server. + Der Dienstprinzipalname (SPN) des Servers. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index 919748b717..518d8837a9 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -4621,9 +4621,9 @@ Se agotó el tiempo de espera de la conexión al recuperar un token de acceso mediante el método de autenticación "{0}". Último error: {1}: {2} - The service principal name (SPN) of the failover partner. + Nombre de entidad de seguridad de servicio (SPN) del asociado de conmutación por error. - The service principal name (SPN) of the server. + Nombre de entidad de seguridad de servicio (SPN) del servidor. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index c0dbfd4057..33c49e9ede 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -4621,9 +4621,9 @@ La connexion a expiré lors de la récupération d’un jeton d’accès à l’aide de '{0}' méthode d’authentification. Dernière erreur : {1} : {2} - The service principal name (SPN) of the failover partner. + Le nom de principal du service (SPN) du partenaire de basculement. - The service principal name (SPN) of the server. + Le nom de principal du service (SPN) du serveur. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index d9d5c3cf30..6d7d9ab871 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -4621,9 +4621,9 @@ '{0}' 인증 방법을 사용하여 액세스 토큰을 검색하는 동안 연결 시간이 초과되었습니다. 마지막 오류: {1}: {2} - The service principal name (SPN) of the failover partner. + 장애 조치(failover) 파트너의 SPN(서비스 사용자 이름)입니다. - The service principal name (SPN) of the server. + 서버의 SPN(서비스 사용자 이름)입니다. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index b06de583b0..0d181032ba 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -4621,9 +4621,9 @@ A conexão expirou ao recuperar um token de acesso usando o método de autenticação '{0}'. Último erro: {1}: {2} - The service principal name (SPN) of the failover partner. + O nome da entidade de serviço (SPN) do parceiro de failover. - The service principal name (SPN) of the server. + O nome da entidade de serviço (SPN) do servidor. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index 4a6c08d720..6e2c3b212d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -4621,9 +4621,9 @@ 使用“{0}”身份验证方法检索访问令牌时连接超时。最后一个错误: {1}: {2} - The service principal name (SPN) of the failover partner. + 故障转移合作伙伴的服务主体名称(SPN)。 - The service principal name (SPN) of the server. + 服务器的服务主体名称(SPN)。 \ No newline at end of file From 4fb0bca23392b3a206c5780f44a55106661387cb Mon Sep 17 00:00:00 2001 From: Parminder Kaur <88398605+Kaur-Parminder@users.noreply.github.com> Date: Fri, 17 Jun 2022 12:40:08 -0700 Subject: [PATCH 29/35] Release notes v5.0.0-preview3 (#1645) Release Notes for M.D.S 5.0.0-preview3 --- CHANGELOG.md | 29 +++++++ release-notes/5.0/5.0.0-preview3.md | 114 ++++++++++++++++++++++++++++ release-notes/5.0/5.0.md | 1 + release-notes/5.0/README.md | 1 + 4 files changed, 145 insertions(+) create mode 100644 release-notes/5.0/5.0.0-preview3.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dec07b8df..7b20c373bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +## [Preview Release 5.0.0-preview3.22168.1] - 2022-06-16 + +This update brings the below changes over the previous release: + +### Added +- Added support for `TDS 8`. To use TDS 8, users should specify Encrypt=Strict in the connection string. Strict mode disables TrustServerCertificate (always treated as False in Strict mode). HostNameInCertificate has been added to help some Strict mode scenarios. [#1608](https://github.com/dotnet/SqlClient/pull/1608) +- Added support for specifying Server SPN and Failover Server SPN on the connection. [#1607](https://github.com/dotnet/SqlClient/pull/1607) +- Added support for aliases when targeting .NET Core on Windows. [#1588](https://github.com/dotnet/SqlClient/pull/1588) + +### Fixed + +- Fixed naming, order, and formatting for `SqlDiagnosticsListener` on .NET Core and .NET. [#1637] (https://github.com/dotnet/SqlClient/pull/1637) +- Fixed NullReferenceException during Azure Active Directory authentication. [#1625] (https://github.com/dotnet/SqlClient/pull/1625) +- Added CommandText length validation when using stored procedure command types. [#1484](https://github.com/dotnet/SqlClient/pull/1484) +- Fixed `GetSchema("StructuredTypeMembers")` to return correct schema information. [#1500] (https://github.com/dotnet/SqlClient/pull/1500), [#1639](https://github.com/dotnet/SqlClient/pull/1639) +- Fixed NullReferenceException when using `SqlDependency.Start` against an Azure SQL Database.[#1294] (https://github.com/dotnet/SqlClient/pull/1294) +- Send the correct retained transaction descriptor in the MARS TDS Header when there is no current transaction on .NET 5+ and .NET Core. [#1624] (https://github.com/dotnet/SqlClient/pull/1624) +- Parallelize SSRP requests (instance name resolution) on Linux and macOS when MultiSubNetFailover is specified. [#1578] (https://github.com/dotnet/SqlClient/pull/1578) +- Adjust the default ConnectRetryCount against Azure Synapse OnDemand endpoints [#1626] (https://github.com/dotnet/SqlClient/pull/1626) + + +### Changed + +- Dropped the `Microsoft.Data.SqlClient.Server` namespace and replaced it with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) +- Code health improvements [#1353](https://github.com/dotnet/SqlClient/pull/1353) [#1354](https://github.com/dotnet/SqlClient/pull/1354) [#1525](https://github.com/dotnet/SqlClient/pull/1525) [#1186](https://github.com/dotnet/SqlClient/pull/1186) +- Update Azure Identity dependency from 1.5.0 to 1.6.0.[#1611](https://github.com/dotnet/SqlClient/pull/1611) +- Improved Regex for SqlCommandSet [#1548] (https://github.com/dotnet/SqlClient/pull/1548) +- Rework on `TdsParserStateObjectManaged` with nullable annotations. [#1555] (https://github.com/dotnet/SqlClient/pull/1555) + ## [Preview Release 5.0.0-preview2.22096.2] - 2022-04-06 This update brings the below changes over the previous release: diff --git a/release-notes/5.0/5.0.0-preview3.md b/release-notes/5.0/5.0.0-preview3.md new file mode 100644 index 0000000000..48f0a147c9 --- /dev/null +++ b/release-notes/5.0/5.0.0-preview3.md @@ -0,0 +1,114 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.0.0-preview3 released 16 June 2022 + +This update brings the below changes over the previous release: + +### Added + +- Added support for `TDS 8`. To use TDS 8, users should specify Encrypt=Strict in the connection string. [#1608](https://github.com/dotnet/SqlClient/pull/1608) [Read more](#tds-8-enhanced-security) +- Added support for specifying Server SPN and Failover Server SPN on the connection. [#1607](https://github.com/dotnet/SqlClient/pull/1607) [Read more](#server-spn) +- Added support for aliases when targeting .NET Core on Windows. [#1588](https://github.com/dotnet/SqlClient/pull/1588) [Read more](#support-for-aliases) + +### Fixed + +- Fixed naming, order, and formatting for `SqlDiagnosticsListener` on .NET Core and .NET. [#1637] (https://github.com/dotnet/SqlClient/pull/1637) +- Fixed NullReferenceException during Azure Active Directory authentication. [#1625] (https://github.com/dotnet/SqlClient/pull/1625) +- Added CommandText length validation when using stored procedure command types. [#1484](https://github.com/dotnet/SqlClient/pull/1484) +- Fixed `GetSchema("StructuredTypeMembers")` to return correct schema information. [#1500] (https://github.com/dotnet/SqlClient/pull/1500), [#1639](https://github.com/dotnet/SqlClient/pull/1639) +- Fixed NullReferenceException when using `SqlDependency.Start` against an Azure SQL Database.[#1294] (https://github.com/dotnet/SqlClient/pull/1294) +- Send the correct retained transaction descriptor in the MARS TDS Header when there is no current transaction on .NET 5+ and .NET Core. [#1624] (https://github.com/dotnet/SqlClient/pull/1624) +- Parallelize SSRP requests on Linux and macOS when MultiSubNetFailover is specified. [#1578] (https://github.com/dotnet/SqlClient/pull/1578) +- Adjust the default ConnectRetryCount against Azure Synapse OnDemand endpoints [#1626] (https://github.com/dotnet/SqlClient/pull/1626) + +### Changed + +- Dropped the `Microsoft.Data.SqlClient.Server` namespace and replaced it with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) +- Code health improvements [#1353](https://github.com/dotnet/SqlClient/pull/1353) [#1354](https://github.com/dotnet/SqlClient/pull/1354) [#1525](https://github.com/dotnet/SqlClient/pull/1525) [#1186](https://github.com/dotnet/SqlClient/pull/1186) +- Update Azure Identity dependency from 1.5.0 to 1.6.0.[#1611](https://github.com/dotnet/SqlClient/pull/1611) +- Improved Regex for SqlCommandSet [#1548] (https://github.com/dotnet/SqlClient/pull/1548) +- Rework on `TdsParserStateObjectManaged` with nullable annotations. [#1555] (https://github.com/dotnet/SqlClient/pull/1555) + +### TDS 8 Enhanced Security + +To use TDS 8, specify Encrypt=Strict in the connection string. Strict mode disables TrustServerCertificate (always treated as False in Strict mode). HostNameInCertificate has been added to help some Strict mode scenarios. TDS 8 begins and continues all server communication inside a secure, encrypted TLS connection. + +New Encrypt values have been added to clarify connection encryption behavior. Encrypt=Mandatory is equavalent to Encrypt=True and encrypts connections during the TDS connection negotiation. Encrypt=Optional is equivalent to Encrypt=False and only encrypts the connection if the server tells the client that encryption is required during the TDS connection negotiation. + +HostNameInCertificate can be specified in the connection string when using aliases to connect with encryption to a server that has a server certificate with a different name or alternate subject name than the name used by the client to identify the server (DNS aliases, for example). Example usage: HostNameInCertificate=MyDnsAliasName + +### Server SPN + +When connecting in an environment that has unique domain/forest topography, the ServerSPN/Server SPN and FailoverServerSPN/Failover Server SPN connection string settings can be used to override the auto-generated server SPNs used in the library when authenticating with integrated authentication in a domain environment. + +### Support for Aliases + +Users can configure Aliases by using the SQL Server Configuration Manager. These are stored in the Windows registry and are already supported when targeting .NET Framework. This release brings support for aliases when targeting .NET or .NET Core on Windows. + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Core 3.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI.runtime 5.0.0-preview3.22165.1 +- Azure.Identity 1.6.0.0 +- Microsoft.Identity.Client 4.43.2.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0.0 +- System.Buffers 4.0.3.0 +- System.Configuration 4.0.0.0 +- System.Data 4.0.0.0 +- System.EnterpriseServices 4.0.0.0 +- System.IdentityModel.Tokens.Jwt 6.8.0.0 +- System.Runtime.Caching 4.0.0.0 +- System.Runtime.InteropServices.RuntimeInformation 4.0.2.0 +- System.Runtime.Serialization 4.0.0.0 +- System.Transactions 4.0.0.0 +- System.Xml 4.0.0.0 + +#### .NET Core + +- Microsoft.Data.SqlClient.SNI.runtime 5.0.0-preview3.22165.1 +- Azure.Identity 1.6.0 +- Microsoft.Identity.Client 4.43.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 5.0.0 +- System.Diagnostics.DiagnosticSource 5.0.0 +- System.IO 4.3.0 +- System.Runtime.Caching 5.0.0 +- System.Text.Encoding.CodePages 5.0.0 +- System.Text.Encodings.Web 4.7.2 +- System.Resources.ResourceManager 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.0.0-preview3.22165.1 +- Azure.Identity 1.6.0 +- Microsoft.Identity.Client 4.43.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 5.0.0 +- System.IO 4.3.0 +- System.Runtime.Caching 5.0.0 +- System.Text.Encoding.CodePages 5.0.0 +- System.Text.Encodings.Web 4.7.2 +- System.Resources.ResourceManager 4.3.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 +- System.Security.Permissions 5.0.0 +- NetStandard.Library 2.0.3 diff --git a/release-notes/5.0/5.0.md b/release-notes/5.0/5.0.md index 51c28bb3a7..16676aa278 100644 --- a/release-notes/5.0/5.0.md +++ b/release-notes/5.0/5.0.md @@ -4,5 +4,6 @@ The following Microsoft.Data.SqlClient 5.0 preview releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2022/06/16 | 5.0.0-preview3.22168.1 | [release notes](5.0.0-preview3.md) | | 2022/04/06 | 5.0.0-preview2.22096.2 | [release notes](5.0.0-preview2.md) | | 2022/03/09 | 5.0.0-preview1.22069.12 | [release notes](5.0.0-preview1.md) | diff --git a/release-notes/5.0/README.md b/release-notes/5.0/README.md index b0f8887bb5..3f2aa51f5e 100644 --- a/release-notes/5.0/README.md +++ b/release-notes/5.0/README.md @@ -2,5 +2,6 @@ The following Microsoft.Data.SqlClient 5.0 preview releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2022/06/16 | 5.0.0-preview3.22168.1 | [release notes](5.0.0-preview3.md) | | 2022/04/06 | 5.0.0-preview2.22096.2 | [release notes](5.0.0-preview2.md) | | 2022/03/09 | 5.0.0-preview1.22069.1 | [release notes](5.0.0-preview1.md) | \ No newline at end of file From 88b0f243e1bb4482a7b764014e17315fc868f02f Mon Sep 17 00:00:00 2001 From: David Engel Date: Fri, 17 Jun 2022 15:37:39 -0700 Subject: [PATCH 30/35] 5.0 preview 3 release notes adjustments (#1648) * 5.0 preview 3 release notes adjustments * Update CHANGELOG.md Co-authored-by: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Co-authored-by: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> --- CHANGELOG.md | 17 +++++++++++++++-- release-notes/5.0/5.0.0-preview3.md | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b20c373bf..775f0d4be1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) This update brings the below changes over the previous release: +### Breaking changes over preview release v5.0.0-preview2 + +- Dropped classes from the `Microsoft.Data.SqlClient.Server` namespace and replaced them with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) The affected classes and enums are: + - Microsoft.Data.SqlClient.Server.IBinarySerialize -> Microsoft.SqlServer.Server.IBinarySerialize + - Microsoft.Data.SqlClient.Server.InvalidUdtException -> Microsoft.SqlServer.Server.InvalidUdtException + - Microsoft.Data.SqlClient.Server.SqlFacetAttribute -> Microsoft.SqlServer.Server.SqlFacetAttribute + - Microsoft.Data.SqlClient.Server.SqlFunctionAttribute -> Microsoft.SqlServer.Server.SqlFunctionAttribute + - Microsoft.Data.SqlClient.Server.SqlMethodAttribute -> Microsoft.SqlServer.Server.SqlMethodAttribute + - Microsoft.Data.SqlClient.Server.SqlUserDefinedAggregateAttribute -> Microsoft.SqlServer.Server.SqlUserDefinedAggregateAttribute + - Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute -> Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute + - (enum) Microsoft.Data.SqlClient.Server.DataAccessKind -> Microsoft.SqlServer.Server.DataAccessKind + - (enum) Microsoft.Data.SqlClient.Server.Format -> Microsoft.SqlServer.Server.Format + - (enum) Microsoft.Data.SqlClient.Server.SystemDataAccessKind -> Microsoft.SqlServer.Server.SystemDataAccessKind + ### Added + - Added support for `TDS 8`. To use TDS 8, users should specify Encrypt=Strict in the connection string. Strict mode disables TrustServerCertificate (always treated as False in Strict mode). HostNameInCertificate has been added to help some Strict mode scenarios. [#1608](https://github.com/dotnet/SqlClient/pull/1608) - Added support for specifying Server SPN and Failover Server SPN on the connection. [#1607](https://github.com/dotnet/SqlClient/pull/1607) - Added support for aliases when targeting .NET Core on Windows. [#1588](https://github.com/dotnet/SqlClient/pull/1588) @@ -24,10 +39,8 @@ This update brings the below changes over the previous release: - Parallelize SSRP requests (instance name resolution) on Linux and macOS when MultiSubNetFailover is specified. [#1578] (https://github.com/dotnet/SqlClient/pull/1578) - Adjust the default ConnectRetryCount against Azure Synapse OnDemand endpoints [#1626] (https://github.com/dotnet/SqlClient/pull/1626) - ### Changed -- Dropped the `Microsoft.Data.SqlClient.Server` namespace and replaced it with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) - Code health improvements [#1353](https://github.com/dotnet/SqlClient/pull/1353) [#1354](https://github.com/dotnet/SqlClient/pull/1354) [#1525](https://github.com/dotnet/SqlClient/pull/1525) [#1186](https://github.com/dotnet/SqlClient/pull/1186) - Update Azure Identity dependency from 1.5.0 to 1.6.0.[#1611](https://github.com/dotnet/SqlClient/pull/1611) - Improved Regex for SqlCommandSet [#1548] (https://github.com/dotnet/SqlClient/pull/1548) diff --git a/release-notes/5.0/5.0.0-preview3.md b/release-notes/5.0/5.0.0-preview3.md index 48f0a147c9..b97905e00f 100644 --- a/release-notes/5.0/5.0.0-preview3.md +++ b/release-notes/5.0/5.0.0-preview3.md @@ -4,6 +4,20 @@ This update brings the below changes over the previous release: +### Breaking changes over preview release v5.0.0-preview2 + +- Dropped classes from the `Microsoft.Data.SqlClient.Server` namespace and replaced them with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) The affected classes and enums are: + - Microsoft.Data.SqlClient.Server.IBinarySerialize -> Microsoft.SqlServer.Server.IBinarySerialize + - Microsoft.Data.SqlClient.Server.InvalidUdtException -> Microsoft.SqlServer.Server.InvalidUdtException + - Microsoft.Data.SqlClient.Server.SqlFacetAttribute -> Microsoft.SqlServer.Server.SqlFacetAttribute + - Microsoft.Data.SqlClient.Server.SqlFunctionAttribute -> Microsoft.SqlServer.Server.SqlFunctionAttribute + - Microsoft.Data.SqlClient.Server.SqlMethodAttribute -> Microsoft.SqlServer.Server.SqlMethodAttribute + - Microsoft.Data.SqlClient.Server.SqlUserDefinedAggregateAttribute -> Microsoft.SqlServer.Server.SqlUserDefinedAggregateAttribute + - Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute -> Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute + - (enum) Microsoft.Data.SqlClient.Server.DataAccessKind -> Microsoft.SqlServer.Server.DataAccessKind + - (enum) Microsoft.Data.SqlClient.Server.Format -> Microsoft.SqlServer.Server.Format + - (enum) Microsoft.Data.SqlClient.Server.SystemDataAccessKind -> Microsoft.SqlServer.Server.SystemDataAccessKind + ### Added - Added support for `TDS 8`. To use TDS 8, users should specify Encrypt=Strict in the connection string. [#1608](https://github.com/dotnet/SqlClient/pull/1608) [Read more](#tds-8-enhanced-security) @@ -23,7 +37,6 @@ This update brings the below changes over the previous release: ### Changed -- Dropped the `Microsoft.Data.SqlClient.Server` namespace and replaced it with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) - Code health improvements [#1353](https://github.com/dotnet/SqlClient/pull/1353) [#1354](https://github.com/dotnet/SqlClient/pull/1354) [#1525](https://github.com/dotnet/SqlClient/pull/1525) [#1186](https://github.com/dotnet/SqlClient/pull/1186) - Update Azure Identity dependency from 1.5.0 to 1.6.0.[#1611](https://github.com/dotnet/SqlClient/pull/1611) - Improved Regex for SqlCommandSet [#1548] (https://github.com/dotnet/SqlClient/pull/1548) From d8fcedebc2c0112eaec43bd35feab5530fd9f1d0 Mon Sep 17 00:00:00 2001 From: David Engel Date: Fri, 17 Jun 2022 16:05:38 -0700 Subject: [PATCH 31/35] Another 5.0 preview 3 note (#1649) --- release-notes/5.0/5.0.0-preview3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes/5.0/5.0.0-preview3.md b/release-notes/5.0/5.0.0-preview3.md index b97905e00f..19819018bd 100644 --- a/release-notes/5.0/5.0.0-preview3.md +++ b/release-notes/5.0/5.0.0-preview3.md @@ -6,6 +6,7 @@ This update brings the below changes over the previous release: ### Breaking changes over preview release v5.0.0-preview2 +- Added a dependency on the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package. This new dependency may cause namespace conflicts if your application references that namespace and still has package references (direct or indirect) to System.Data.SqlClient from .NET Core. - Dropped classes from the `Microsoft.Data.SqlClient.Server` namespace and replaced them with supported types from the [Microsoft.SqlServer.Server](https://github.com/dotnet/SqlClient/tree/main/src/Microsoft.SqlServer.Server) package.[#1585](https://github.com/dotnet/SqlClient/pull/1585) The affected classes and enums are: - Microsoft.Data.SqlClient.Server.IBinarySerialize -> Microsoft.SqlServer.Server.IBinarySerialize - Microsoft.Data.SqlClient.Server.InvalidUdtException -> Microsoft.SqlServer.Server.InvalidUdtException From f1172a6ca87013323c41abd8775cdb61e4ec52ae Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Wed, 22 Jun 2022 11:04:44 -0700 Subject: [PATCH 32/35] Fix Microsoft.SqlServer.Server netcore project package reference (#1654) --- .../netcore/src/Microsoft.Data.SqlClient.csproj | 6 +++--- tools/props/Versions.props | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) 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 eed46e0d1c..b2c1017fc1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -942,9 +942,6 @@ Microsoft.Data.SqlClient.SqlMetaData.xml - - - @@ -953,6 +950,9 @@ + + + diff --git a/tools/props/Versions.props b/tools/props/Versions.props index f8ee837453..ca8f41ae56 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -40,6 +40,7 @@ 5.0.0 5.0.0-preview3.22165.1 5.0.0 + 1.0.0 5.0.0 5.0.0 4.3.0 From 2cf43354994c838a985d87fae0d2d6027fbe528c Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Mon, 27 Jun 2022 17:57:44 -0700 Subject: [PATCH 33/35] Test | Skip unsupported tests with Azure Synapse (#1658) --- .../ManualTests/DataCommon/DataTestUtility.cs | 16 ++++++++++++++++ .../SQL/DataReaderTest/DataReaderStreamsTest.cs | 10 +++++----- .../ManualTests/SQL/DateTimeTest/DateTimeTest.cs | 2 +- .../tests/ManualTests/SQL/MARSTest/MARSTest.cs | 14 +++++++------- .../SQL/ParameterTest/ParametersTest.cs | 2 +- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 5fed0099a7..9dd199da33 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -437,6 +437,7 @@ public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = public static void DropTable(SqlConnection sqlConnection, string tableName) { + ResurrectConnection(sqlConnection); using (SqlCommand cmd = new SqlCommand(string.Format("IF (OBJECT_ID('{0}') IS NOT NULL) \n DROP TABLE {0}", tableName), sqlConnection)) { cmd.ExecuteNonQuery(); @@ -445,6 +446,7 @@ public static void DropTable(SqlConnection sqlConnection, string tableName) public static void DropUserDefinedType(SqlConnection sqlConnection, string typeName) { + ResurrectConnection(sqlConnection); using (SqlCommand cmd = new SqlCommand(string.Format("IF (TYPE_ID('{0}') IS NOT NULL) \n DROP TYPE {0}", typeName), sqlConnection)) { cmd.ExecuteNonQuery(); @@ -453,12 +455,25 @@ public static void DropUserDefinedType(SqlConnection sqlConnection, string typeN public static void DropStoredProcedure(SqlConnection sqlConnection, string spName) { + ResurrectConnection(sqlConnection); using (SqlCommand cmd = new SqlCommand(string.Format("IF (OBJECT_ID('{0}') IS NOT NULL) \n DROP PROCEDURE {0}", spName), sqlConnection)) { cmd.ExecuteNonQuery(); } } + private static void ResurrectConnection(SqlConnection sqlConnection, int counter = 2) + { + if (sqlConnection.State == ConnectionState.Closed) + { + sqlConnection.Open(); + } + while (counter-- > 0 && sqlConnection.State == ConnectionState.Connecting) + { + Thread.Sleep(80); + } + } + /// /// Drops specified database on provided connection. /// @@ -466,6 +481,7 @@ public static void DropStoredProcedure(SqlConnection sqlConnection, string spNam /// Database name without brackets. public static void DropDatabase(SqlConnection sqlConnection, string dbName) { + ResurrectConnection(sqlConnection); using SqlCommand cmd = new(string.Format("IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{0}')) \nBEGIN \n ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE \n DROP DATABASE [{0}] \nEND", dbName), sqlConnection); cmd.ExecuteNonQuery(); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs index 8c8b2bc1d4..385c0341cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs @@ -51,7 +51,7 @@ public static async Task GetFieldValueAsync_OfStream(CommandBehavior behavior, b Assert.Equal(originalData, outputData); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetCommandBehavioursAndIsAsync))] public static async Task GetFieldValueAsync_OfXmlReader(CommandBehavior behavior, bool isExecuteAsync) { @@ -220,7 +220,7 @@ public static async void GetFieldValue_OfStream(CommandBehavior behavior, bool i Assert.Equal(originalData, outputData); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetCommandBehavioursAndIsAsync))] public static async void GetFieldValue_OfTextReader(CommandBehavior behavior, bool isExecuteAsync) { @@ -290,7 +290,7 @@ public static async void GetStream(CommandBehavior behavior, bool isExecuteAsync Assert.Equal(originalData, outputData); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetCommandBehavioursAndIsAsync))] public static async void GetXmlReader(CommandBehavior behavior, bool isExecuteAsync) { @@ -358,7 +358,7 @@ public static async void GetTextReader(CommandBehavior behavior, bool isExecuteA Assert.Equal(originalText, outputText); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetCommandBehaviourAndAccessorTypes))] public static void NullStreamProperties(CommandBehavior behavior, AccessorType accessorType) { @@ -443,7 +443,7 @@ public static void NullStreamProperties(CommandBehavior behavior, AccessorType a } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetCommandBehaviourAndAccessorTypes))] public static void InvalidCastExceptionStream(CommandBehavior behavior, AccessorType accessorType) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs index d3bc3e54a9..f89017c811 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs @@ -592,7 +592,7 @@ public static void TypeVersionKnobTest() } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [InlineData(true)] [InlineData(false)] public static void BulkCopyTest(bool useReader) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs index b3c874dbcd..f8207f8a6a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs @@ -250,7 +250,7 @@ public static void MARSSyncBusyReaderTest() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async void MARSAsyncExecuteNonQueryTest() { using SqlConnection con = new(_connStr); @@ -297,7 +297,7 @@ public static void MARSSyncExecuteNonQueryTest() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async void MARSAsyncExecuteReaderTest1() { using SqlConnection con = new(_connStr); @@ -411,7 +411,7 @@ public static void MARSSyncExecuteReaderTest1() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async void MARSAsyncExecuteReaderTest2() { using SqlConnection con = new(_connStr); @@ -462,7 +462,7 @@ public static void MARSSyncExecuteReaderTest2() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async void MARSAsyncExecuteReaderTest3() { using SqlConnection con = new(_connStr); @@ -524,7 +524,7 @@ public static void MARSSyncExecuteReaderTest3() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async void MARSAsyncExecuteReaderTest4() { using SqlConnection con = new(_connStr); @@ -616,7 +616,7 @@ public static void MARSMultiDataReaderErrTest() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async Task MarsScenarioClientJoin() { SqlConnectionStringBuilder builder = new(_connStr); @@ -640,7 +640,7 @@ public static async Task MarsScenarioClientJoin() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void MarsConcurrencyTest() { var table = DataTestUtility.GenerateObjectName(); 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 a213a8e148..49cb91792e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -708,7 +708,7 @@ private static void EnableOptimizedParameterBinding_OutputFails() Assert.Contains("OptimizedParameterBinding", exception.Message); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] private static void EnableOptimizedParameterBinding_ReturnSucceeds() { int firstInput = 12; From 1f2902b6b8823ee11fdcc7d054c9b59d7ef4a330 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:30:55 -0700 Subject: [PATCH 34/35] Doc | Fix xml doc (#1661) --- .../Microsoft.Data.SqlClient/SqlConnection.xml | 2 +- .../SqlConnectionEncryptOption.xml | 12 ++++++++---- .../SqlConnectionStringBuilder.xml | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index 304eb234a3..f444d8f2e8 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -537,7 +537,7 @@ End Module |Current Language

-or-

Language|N/A|Sets the language used for database server warning or error messages.

The language name can be 128 characters or less.| |Data Source

-or-

Server

-or-

Address

-or-

Addr

-or-

Network Address|N/A|The name or network address of the instance of SQL Server to which to connect. The port number can be specified after the server name:

`server=tcp:servername, portnumber`

When specifying a local instance, always use (local). To force a protocol, add one of the following prefixes:

`np:(local), tcp:(local), lpc:(local)`

You can also connect to a LocalDB database as follows:

`server=(localdb)\\myInstance`

For more information about LocalDB, see [SqlClient Support for LocalDB](/sql/connect/ado-net/sql/sqlclient-support-localdb).

**Data Source** must use the TCP format or the Named Pipes format.

TCP format is as follows:

- tcp:\\\
- tcp:\,\

The TCP format must start with the prefix "tcp:" and is followed by the database instance, as specified by a host name and an instance name. This format is not applicable when connecting to Azure SQL Database. TCP is automatically selected for connections to Azure SQL Database when no protocol is specified.

The host name MUST be specified in one of the following ways:

- NetBIOSName
- IPv4Address
- IPv6Address

The instance name is used to resolve to a particular TCP/IP port number on which a database instance is hosted. Alternatively, specifying a TCP/IP port number directly is also allowed. If both instance name and port number are not present, the default database instance is used.

The Named Pipes format is as follows:

- np:\\\\\pipe\\

The Named Pipes format MUST start with the prefix "np:" and is followed by a named pipe name.

The host name MUST be specified in one of the following ways:

- NetBIOSName
- IPv4Address
- IPv6Address

The pipe name is used to identify the database instance to which the .NET application will connect.

If the value of the **Network** key is specified, the prefixes "tcp:" and "np:" should not be specified. **Note:** You can force the use of TCP instead of shared memory, either by prefixing **tcp:** to the server name in the connection string, or by using **localhost**.| |Enclave Attestation Url|N/A|Gets or sets the enclave attestation URL to be used with enclave based Always Encrypted.| -|Encrypt|'true' in 4.0 and above

'false' in 3.x and below|Recognized values are:
versions 1 - 4: `true`/`yes` and `false`/`no`
versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `Encrypt` is true or strict and `TrustServerCertificate` is false, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| +|Encrypt|'true' in 4.0 and above

'false' in 3.x and below|Recognized values are:
versions 1 - 4: `true`/`yes` and `false`/`no`
versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `Encrypt` is `mandatory` or `strict` and `TrustServerCertificate` is `false`, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| |Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.| |Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.

If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".

The server name can be 128 characters or less.

If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.| |Failover Partner SPN

-or-

FailoverPartnerSPN|N/A|The SPN for the failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml index 496a8e4103..acf73d2387 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionEncryptOption.xml @@ -2,10 +2,14 @@ - - These options are used to control encryption behavior of the communication between the server and the client. - - Implicit conversions have been added to maintain backwards compatibility with boolean behahavior for the property. When converting from a boolean, a value of converts to and a value of converts to . When converting to a boolean, and convert to and converts . + These options are used to control encryption behavior of the communication between the server and the client. + + property. When converting from a boolean, a value of `true` converts to and a value of `false` converts to . When converting to a boolean, and convert to `true` and converts `false`. + + ]]> diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index d1c0d6a360..a3c12d73de 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -410,15 +410,15 @@ If the value of the **Network** key is specified, the prefixes "tcp:" and "np:" The enclave attestation URL. - Gets or sets a value that indicates whether TLS encryption is required for all data sent between the client and server. - The value of the property. + Gets or sets a value that indicates whether TLS encryption is required for all data sent between the client and server. + The value of the type. or , the server name (or IP address) in a server's TLS certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Enable encrypted connections to the Database Engine](/sql/database-engine/configure-windows/enable-encrypted-connections-to-the-database-engine#certificate-requirements). > [!NOTE] > Starting from **version 4.0**, the default value of the property `Encrypt` is set to `true`. From 4e3aa5eaa913ab4bda1b7b15d00d904b752da331 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Thu, 30 Jun 2022 16:21:54 -0700 Subject: [PATCH 35/35] Doc | Fix typo (#1666) --- .../src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs index 5518472434..6d488ce872 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs @@ -6,7 +6,7 @@ namespace Microsoft.Data.SqlClient { - /// + /// public sealed class SqlConnectionEncryptOption { private const string TRUE = "True";