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 1f836d84d5..83fc516f59 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -108,6 +108,9 @@
Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs
+
+ Microsoft\Data\SqlClient\DataSource.cs
+
Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs
@@ -742,7 +745,7 @@
-
+
@@ -753,8 +756,9 @@
-
-
+
+ Microsoft\Data\SqlClient\LocalDB.Windows.cs
+
@@ -768,7 +772,6 @@
Common\Interop\Windows\kernel32\Interop.GetProcAddress.cs
-
@@ -793,7 +796,7 @@
-
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDB.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDB.Unix.cs
new file mode 100644
index 0000000000..809b0b69d6
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDB.Unix.cs
@@ -0,0 +1,23 @@
+// 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.ProviderBase;
+using Microsoft.Data.Common;
+
+namespace Microsoft.Data.SqlClient
+{
+ internal class LocalDB
+ {
+ internal static string GetLocalDBConnectionString(string localDbInstance)
+ {
+ throw ADP.LocalDBNotSUpportedException();
+ }
+
+ internal static string GetLocalDBDataSource(string fullServerName, TimeoutTimer timeout)
+ {
+ throw ADP.LocalDBNotSUpportedException();
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.uap.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDB.uap.cs
similarity index 59%
rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.uap.cs
rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDB.uap.cs
index e764375802..aac8fb76a7 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.uap.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDB.uap.cs
@@ -2,7 +2,10 @@
// 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
+using System;
+using Microsoft.Data.ProviderBase;
+
+namespace Microsoft.Data.SqlClient
{
internal class LocalDB
{
@@ -10,5 +13,10 @@ internal static string GetLocalDBConnectionString(string localDbInstance)
{
throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // No Registry support on UAP
}
+
+ internal static string GetLocalDBDataSource(string fullServerName, TimeoutTimer timeout)
+ {
+ throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // No Registry support on UAP
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs
deleted file mode 100644
index f30ddf4243..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs
+++ /dev/null
@@ -1,123 +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.Diagnostics;
-using System.Globalization;
-using System.Runtime.InteropServices;
-using System.Text;
-using Microsoft.Data.SqlClient;
-
-namespace Microsoft.Data
-{
- internal static partial class LocalDBAPI
- {
- private static LocalDBFormatMessageDelegate s_localDBFormatMessage = null;
-
- internal static void ReleaseDLLHandles()
- {
- s_userInstanceDLLHandle = IntPtr.Zero;
- s_localDBFormatMessage = null;
- }
-
-
- private static LocalDBFormatMessageDelegate LocalDBFormatMessage
- {
- get
- {
- if (s_localDBFormatMessage == null)
- {
- lock (s_dllLock)
- {
- if (s_localDBFormatMessage == null)
- {
- IntPtr functionAddr = LoadProcAddress();
-
- if (functionAddr == IntPtr.Zero)
- {
- // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error.
- int hResult = Marshal.GetLastWin32Error();
- SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult);
- throw CreateLocalDBException(errorMessage: Strings.LocalDB_MethodNotFound);
- }
- s_localDBFormatMessage = Marshal.GetDelegateForFunctionPointer(functionAddr);
- }
- }
- }
- return s_localDBFormatMessage;
- }
- }
-
- //This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle
- private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero;
-
- private static readonly object s_dllLock = new object();
-
-
- private const uint const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer
- private const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message 1K will be enough for all messages
-
-
- internal static string GetLocalDBMessage(int hrCode)
- {
- Debug.Assert(hrCode < 0, "HRCode does not indicate error");
- try
- {
- StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
- uint len = (uint)buffer.Capacity;
-
-
- // First try for current culture
- int hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (uint)CultureInfo.CurrentCulture.LCID,
- buffer: buffer, buflen: ref len);
- if (hResult >= 0)
- return buffer.ToString();
- else
- {
- // Message is not available for current culture, try default
- buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
- len = (uint)buffer.Capacity;
- hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: 0 /* thread locale with fallback to English */,
- buffer: buffer, buflen: ref len);
- if (hResult >= 0)
- return buffer.ToString();
- else
- return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Strings.LocalDB_UnobtainableMessage, hResult);
- }
- }
- catch (SqlException exc)
- {
- return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message);
- }
- }
-
-
- private static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0)
- {
- Debug.Assert((localDbError == 0) || (sniError == 0), "LocalDB error and SNI error cannot be specified simultaneously");
- Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty");
- SqlErrorCollection collection = new SqlErrorCollection();
-
- int errorCode = (localDbError == 0) ? sniError : localDbError;
-
- if (sniError != 0)
- {
- string sniErrorMessage = SQL.GetSNIErrorMessage(sniError);
- errorMessage = string.Format("{0} (error: {1} - {2})",
- errorMessage, sniError, sniErrorMessage);
- }
-
- collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0));
-
- if (localDbError != 0)
- collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, GetLocalDBMessage(localDbError), null, 0));
-
- SqlException exc = SqlException.CreateException(collection, null);
-
- exc._doNotReconnect = true;
-
- return exc;
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs
deleted file mode 100644
index 1b4679bfe4..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs
+++ /dev/null
@@ -1,41 +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.SqlClient;
-
-namespace Microsoft.Data
-{
- internal static partial class LocalDBAPI
- {
- private static IntPtr LoadProcAddress() => SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage");
-
- private static IntPtr UserInstanceDLLHandle
- {
- get
- {
- if (s_userInstanceDLLHandle == IntPtr.Zero)
- {
- lock (s_dllLock)
- {
- if (s_userInstanceDLLHandle == IntPtr.Zero)
- {
- SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
- if (s_userInstanceDLLHandle != IntPtr.Zero)
- {
- SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained");
- }
- else
- {
- SNINativeMethodWrapper.SNIGetLastError(out SNINativeMethodWrapper.SNI_Error sniError);
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
- }
- }
- }
- }
- return s_userInstanceDLLHandle;
- }
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs
index ba2371c232..af3f9e0fc5 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs
@@ -13,7 +13,6 @@ internal static partial class LocalDBAPI
private const string LocalDbPrefix = @"(localdb)\";
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";
-
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen);
@@ -38,7 +37,6 @@ internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
return input.ToString();
}
-
}
return null;
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Unix.cs
deleted file mode 100644
index a9b235d47c..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Unix.cs
+++ /dev/null
@@ -1,16 +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.SNI
-{
- internal class LocalDB
- {
- internal static string GetLocalDBConnectionString(string localDbInstance)
- {
- throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported.
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs
deleted file mode 100644
index 68eaa25486..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs
+++ /dev/null
@@ -1,260 +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.InteropServices;
-using System.Text;
-using Microsoft.Win32;
-using Microsoft.Win32.SafeHandles;
-
-namespace Microsoft.Data.SqlClient.SNI
-{
- internal sealed class LocalDB
- {
- private static readonly LocalDB Instance = new LocalDB();
-
- //HKEY_LOCAL_MACHINE
- private const string LocalDBInstalledVersionRegistryKey = "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions\\";
-
- private const string InstanceAPIPathValueName = "InstanceAPIPath";
-
- private const string ProcLocalDBStartInstance = "LocalDBStartInstance";
-
- private const int MAX_LOCAL_DB_CONNECTION_STRING_SIZE = 260;
-
- private IntPtr _startInstanceHandle = IntPtr.Zero;
-
- // Local Db api doc https://msdn.microsoft.com/en-us/library/hh217143.aspx
- // HRESULT LocalDBStartInstance( [Input ] PCWSTR pInstanceName, [Input ] DWORD dwFlags,[Output] LPWSTR wszSqlConnection,[Input/Output] LPDWORD lpcchSqlConnection);
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- internal delegate int LocalDBStartInstance(
- [In] [MarshalAs(UnmanagedType.LPWStr)] string localDBInstanceName,
- [In] int flags,
- [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder sqlConnectionDataSource,
- [In, Out]ref int bufferLength);
-
- private LocalDBStartInstance localDBStartInstanceFunc = null;
-
- private volatile SafeLibraryHandle _sqlUserInstanceLibraryHandle;
-
- private LocalDB() { }
-
- internal static string GetLocalDBConnectionString(string localDbInstance) =>
- Instance.LoadUserInstanceDll() ? Instance.GetConnectionString(localDbInstance) : null;
-
- internal static IntPtr GetProcAddress(string functionName) =>
- Instance.LoadUserInstanceDll() ? Interop.Kernel32.GetProcAddress(LocalDB.Instance._sqlUserInstanceLibraryHandle, functionName) : IntPtr.Zero;
-
- private string GetConnectionString(string localDbInstance)
- {
- StringBuilder localDBConnectionString = new StringBuilder(MAX_LOCAL_DB_CONNECTION_STRING_SIZE + 1);
- int sizeOfbuffer = localDBConnectionString.Capacity;
- localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer);
- return localDBConnectionString.ToString();
- }
-
- internal enum LocalDBErrorState
- {
- NO_INSTALLATION, INVALID_CONFIG, NO_SQLUSERINSTANCEDLL_PATH, INVALID_SQLUSERINSTANCEDLL_PATH, NONE
- }
-
- internal static uint MapLocalDBErrorStateToCode(LocalDBErrorState errorState)
- {
- switch (errorState)
- {
- case LocalDBErrorState.NO_INSTALLATION:
- return SNICommon.LocalDBNoInstallation;
- case LocalDBErrorState.INVALID_CONFIG:
- return SNICommon.LocalDBInvalidConfig;
- case LocalDBErrorState.NO_SQLUSERINSTANCEDLL_PATH:
- return SNICommon.LocalDBNoSqlUserInstanceDllPath;
- case LocalDBErrorState.INVALID_SQLUSERINSTANCEDLL_PATH:
- return SNICommon.LocalDBInvalidSqlUserInstanceDllPath;
- case LocalDBErrorState.NONE:
- return 0;
- default:
- return SNICommon.LocalDBInvalidConfig;
- }
- }
-
- internal static string MapLocalDBErrorStateToErrorMessage(LocalDBErrorState errorState)
- {
- switch (errorState)
- {
- case LocalDBErrorState.NO_INSTALLATION:
- return Strings.SNI_ERROR_52;
- case LocalDBErrorState.INVALID_CONFIG:
- return Strings.SNI_ERROR_53;
- case LocalDBErrorState.NO_SQLUSERINSTANCEDLL_PATH:
- return Strings.SNI_ERROR_54;
- case LocalDBErrorState.INVALID_SQLUSERINSTANCEDLL_PATH:
- return Strings.SNI_ERROR_55;
- case LocalDBErrorState.NONE:
- return Strings.SNI_ERROR_50;
- default:
- return Strings.SNI_ERROR_53;
- }
- }
-
- ///
- /// Loads the User Instance dll.
- ///
- private bool LoadUserInstanceDll()
- {
- using (TrySNIEventScope.Create(nameof(LocalDB)))
- {
- // Check in a non thread-safe way if the handle is already set for performance.
- if (_sqlUserInstanceLibraryHandle != null)
- {
- return true;
- }
-
- lock (this)
- {
- if (_sqlUserInstanceLibraryHandle != null)
- {
- return true;
- }
- //Get UserInstance Dll path
- LocalDBErrorState registryQueryErrorState;
-
- // Get the LocalDB instance dll path from the registry
- string dllPath = GetUserInstanceDllPath(out registryQueryErrorState);
-
- // If there was no DLL path found, then there is an error.
- if (dllPath == null)
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, MapLocalDBErrorStateToCode(registryQueryErrorState), MapLocalDBErrorStateToErrorMessage(registryQueryErrorState));
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "User instance DLL path is null.");
- return false;
- }
-
- // In case the registry had an empty path for dll
- if (string.IsNullOrWhiteSpace(dllPath))
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBInvalidSqlUserInstanceDllPath, Strings.SNI_ERROR_55);
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "User instance DLL path is invalid. DLL path = {0}", dllPath);
- return false;
- }
-
- // Load the dll
- SafeLibraryHandle libraryHandle = Interop.Kernel32.LoadLibraryExW(dllPath.Trim(), IntPtr.Zero, 0);
-
- if (libraryHandle.IsInvalid)
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBFailedToLoadDll, Strings.SNI_ERROR_56);
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Library Handle is invalid. Could not load the dll.");
- libraryHandle.Dispose();
- return false;
- }
-
- // Load the procs from the DLLs
- _startInstanceHandle = Interop.Kernel32.GetProcAddress(libraryHandle, ProcLocalDBStartInstance);
-
- if (_startInstanceHandle == IntPtr.Zero)
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBBadRuntime, Strings.SNI_ERROR_57);
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Was not able to load the PROC from DLL. Bad Runtime.");
- libraryHandle.Dispose();
- return false;
- }
-
- // Set the delegate the invoke.
- localDBStartInstanceFunc = (LocalDBStartInstance)Marshal.GetDelegateForFunctionPointer(_startInstanceHandle, typeof(LocalDBStartInstance));
-
- if (localDBStartInstanceFunc == null)
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBBadRuntime, Strings.SNI_ERROR_57);
- libraryHandle.Dispose();
- _startInstanceHandle = IntPtr.Zero;
- return false;
- }
-
- _sqlUserInstanceLibraryHandle = libraryHandle;
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.INFO, "User Instance DLL was loaded successfully.");
- return true;
- }
- }
- }
-
- ///
- /// Retrieves the part of the sqlUserInstance.dll from the registry
- ///
- /// In case the dll path is not found, the error is set here.
- ///
- private string GetUserInstanceDllPath(out LocalDBErrorState errorState)
- {
- using (TrySNIEventScope.Create(nameof(LocalDB)))
- {
- string dllPath = null;
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey(LocalDBInstalledVersionRegistryKey))
- {
- if (key == null)
- {
- errorState = LocalDBErrorState.NO_INSTALLATION;
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "No installation found.");
- return null;
- }
-
- Version zeroVersion = new Version();
-
- Version latestVersion = zeroVersion;
-
- foreach (string subKey in key.GetSubKeyNames())
- {
- Version currentKeyVersion;
-
- if (!Version.TryParse(subKey, out currentKeyVersion))
- {
- errorState = LocalDBErrorState.INVALID_CONFIG;
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Invalid Configuration.");
- return null;
- }
-
- if (latestVersion.CompareTo(currentKeyVersion) < 0)
- {
- latestVersion = currentKeyVersion;
- }
- }
-
- // If no valid versions are found, then error out
- if (latestVersion.Equals(zeroVersion))
- {
- errorState = LocalDBErrorState.INVALID_CONFIG;
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Invalid Configuration.");
- return null;
- }
-
- // Use the latest version to get the DLL path
- using (RegistryKey latestVersionKey = key.OpenSubKey(latestVersion.ToString()))
- {
-
- object instanceAPIPathRegistryObject = latestVersionKey.GetValue(InstanceAPIPathValueName);
-
- if (instanceAPIPathRegistryObject == null)
- {
- errorState = LocalDBErrorState.NO_SQLUSERINSTANCEDLL_PATH;
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "No SQL user instance DLL. Instance API Path Registry Object Error.");
- return null;
- }
-
- RegistryValueKind valueKind = latestVersionKey.GetValueKind(InstanceAPIPathValueName);
-
- if (valueKind != RegistryValueKind.String)
- {
- errorState = LocalDBErrorState.INVALID_SQLUSERINSTANCEDLL_PATH;
- SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Invalid SQL user instance DLL path. Registry value kind mismatch.");
- return null;
- }
-
- dllPath = (string)instanceAPIPathRegistryObject;
-
- errorState = LocalDBErrorState.NONE;
- return dllPath;
- }
- }
- }
- }
- }
-}
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 b92746098f..fc8adb003c 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
@@ -119,14 +119,7 @@ internal class SNICommon
internal const int MultiSubnetFailoverWithInstanceSpecified = 48;
internal const int MultiSubnetFailoverWithNonTcpProtocol = 49;
internal const int MaxErrorValue = 50157;
- internal const int LocalDBNoInstanceName = 51;
- internal const int LocalDBNoInstallation = 52;
- internal const int LocalDBInvalidConfig = 53;
- internal const int LocalDBNoSqlUserInstanceDllPath = 54;
- internal const int LocalDBInvalidSqlUserInstanceDllPath = 55;
- internal const int LocalDBFailedToLoadDll = 56;
- internal const int LocalDBBadRuntime = 57;
-
+
///
/// We only validate Server name in Certificate to match with "targetServerName".
/// Certificate validation and chain trust validations are done by SSLStream class [System.Net.Security.SecureChannel.VerifyRemoteCertificate method]
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 8f101b7bdf..82a2c408ee 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
@@ -144,6 +144,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode)
/// Support TDS8.0
/// Used for the HostName in certificate
/// Used for the path to the Server Certificate
+ /// DataSource deatils
/// SNI handle
internal static SNIHandle CreateConnectionHandle(
string fullServerName,
@@ -160,26 +161,10 @@ internal static SNIHandle CreateConnectionHandle(
ref SQLDNSInfo pendingDNSInfo,
bool tlsFirst,
string hostNameInCertificate,
- string serverCertificateFilename)
+ string serverCertificateFilename,
+ DataSource details)
{
instanceName = new byte[1];
-
- bool errorWithLocalDBProcessing;
- string localDBDataSource = GetLocalDBDataSource(fullServerName, out errorWithLocalDBProcessing);
-
- if (errorWithLocalDBProcessing)
- {
- return null;
- }
- // If a localDB Data source is available, we need to use it.
- fullServerName = localDBDataSource ?? fullServerName;
-
- DataSource details = DataSource.ParseServerName(fullServerName);
- if (details == null)
- {
- return null;
- }
-
SNIHandle sniHandle = null;
switch (details._connectionProtocol)
{
@@ -366,401 +351,5 @@ internal SNIError GetLastError()
{
return SNILoadHandle.SingletonInstance.LastError;
}
-
- ///
- /// Gets the Local db Named pipe data source if the input is a localDB server.
- ///
- /// The data source
- /// Set true when an error occurred while getting LocalDB up
- ///
- private static string GetLocalDBDataSource(string fullServerName, out bool error)
- {
- string localDBConnectionString = null;
- string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out bool isBadLocalDBDataSource);
-
- if (isBadLocalDBDataSource)
- {
- error = true;
- return null;
- }
-
- else if (!string.IsNullOrEmpty(localDBInstance))
- {
- // We have successfully received a localDBInstance which is valid.
- Debug.Assert(!string.IsNullOrWhiteSpace(localDBInstance), "Local DB Instance name cannot be empty.");
- localDBConnectionString = LocalDB.GetLocalDBConnectionString(localDBInstance);
-
- if (fullServerName == null)
- {
- // The Last error is set in LocalDB.GetLocalDBConnectionString. We don't need to set Last here.
- error = true;
- return null;
- }
- }
- error = false;
- return localDBConnectionString;
- }
- }
-
- internal class DataSource
- {
- private const char CommaSeparator = ',';
- private const char SemiColon = ':';
- private const char BackSlashCharacter = '\\';
-
- private const string DefaultHostName = "localhost";
- private const string DefaultSqlServerInstanceName = "mssqlserver";
- private const string PipeBeginning = @"\\";
- private const string Slash = @"/";
- private const string PipeToken = "pipe";
- private const string LocalDbHost = "(localdb)";
- private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#";
- private const string NamedPipeInstanceNameHeader = "mssql$";
- private const string DefaultPipeName = "sql\\query";
-
- internal enum Protocol { TCP, NP, None, Admin };
-
- internal Protocol _connectionProtocol = Protocol.None;
-
- ///
- /// Provides the HostName of the server to connect to for TCP protocol.
- /// This information is also used for finding the SPN of SqlServer
- ///
- internal string ServerName { get; private set; }
-
- ///
- /// Provides the port on which the TCP connection should be made if one was specified in Data Source
- ///
- internal int Port { get; private set; } = -1;
-
- ///
- /// Provides the inferred Instance Name from Server Data Source
- ///
- internal string InstanceName { get; private set; }
-
- ///
- /// Provides the pipe name in case of Named Pipes
- ///
- internal string PipeName { get; private set; }
-
- ///
- /// Provides the HostName to connect to in case of Named pipes Data Source
- ///
- internal string PipeHostName { get; private set; }
-
- private string _workingDataSource;
- private string _dataSourceAfterTrimmingProtocol;
-
- internal bool IsBadDataSource { get; private set; } = false;
-
- internal bool IsSsrpRequired { get; private set; } = false;
-
- private DataSource(string dataSource)
- {
- // Remove all whitespaces from the datasource and all operations will happen on lower case.
- _workingDataSource = dataSource.Trim().ToLowerInvariant();
-
- int firstIndexOfColon = _workingDataSource.IndexOf(SemiColon);
-
- PopulateProtocol();
-
- _dataSourceAfterTrimmingProtocol = (firstIndexOfColon > -1) && _connectionProtocol != Protocol.None
- ? _workingDataSource.Substring(firstIndexOfColon + 1).Trim() : _workingDataSource;
-
- if (_dataSourceAfterTrimmingProtocol.Contains(Slash)) // Pipe paths only allow back slashes
- {
- if (_connectionProtocol == Protocol.None)
- ReportSNIError(SNIProviders.INVALID_PROV);
- else if (_connectionProtocol == Protocol.NP)
- ReportSNIError(SNIProviders.NP_PROV);
- else if (_connectionProtocol == Protocol.TCP)
- ReportSNIError(SNIProviders.TCP_PROV);
- }
- }
-
- private void PopulateProtocol()
- {
- string[] splitByColon = _workingDataSource.Split(SemiColon);
-
- if (splitByColon.Length <= 1)
- {
- _connectionProtocol = Protocol.None;
- }
- else
- {
- // We trim before switching because " tcp : server , 1433 " is a valid data source
- switch (splitByColon[0].Trim())
- {
- case TdsEnums.TCP:
- _connectionProtocol = Protocol.TCP;
- break;
- case TdsEnums.NP:
- _connectionProtocol = Protocol.NP;
- break;
- case TdsEnums.ADMIN:
- _connectionProtocol = Protocol.Admin;
- break;
- default:
- // None of the supported protocols were found. This may be a IPv6 address
- _connectionProtocol = Protocol.None;
- break;
- }
- }
- }
-
- // LocalDbInstance name always starts with (localdb)
- // possible scenarios:
- // (localdb)\
- // or (localdb)\. which goes to default localdb
- // or (localdb)\.\
- internal static string GetLocalDBInstance(string dataSource, out bool error)
- {
- string instanceName = null;
- // ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
- ReadOnlySpan input = dataSource.AsSpan().TrimStart();
- error = false;
- // NetStandard 2.0 does not support passing a string to ReadOnlySpan
- if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
- {
- // When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index
- // Such ad input = input[1..];
- input = input.Slice(LocalDbHost.Length);
- if (!input.IsEmpty && input[0] == BackSlashCharacter)
- {
- input = input.Slice(1);
- }
- if (!input.IsEmpty)
- {
- instanceName = input.Trim().ToString();
- }
- else
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51);
- error = true;
- }
- }
- else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
- {
- instanceName = input.Trim().ToString();
- }
-
- return instanceName;
- }
-
- internal static DataSource ParseServerName(string dataSource)
- {
- DataSource details = new DataSource(dataSource);
-
- if (details.IsBadDataSource)
- {
- return null;
- }
-
- if (details.InferNamedPipesInformation())
- {
- return details;
- }
-
- if (details.IsBadDataSource)
- {
- return null;
- }
-
- if (details.InferConnectionDetails())
- {
- return details;
- }
-
- return null;
- }
-
- private void InferLocalServerName()
- {
- // If Server name is empty or localhost, then use "localhost"
- if (string.IsNullOrEmpty(ServerName) || IsLocalHost(ServerName) ||
- (Environment.MachineName.Equals(ServerName, StringComparison.CurrentCultureIgnoreCase) &&
- _connectionProtocol == Protocol.Admin))
- {
- // For DAC use "localhost" instead of the server name.
- ServerName = DefaultHostName;
- }
- }
-
- private bool InferConnectionDetails()
- {
- string[] tokensByCommaAndSlash = _dataSourceAfterTrimmingProtocol.Split(BackSlashCharacter, CommaSeparator);
- ServerName = tokensByCommaAndSlash[0].Trim();
-
- int commaIndex = _dataSourceAfterTrimmingProtocol.IndexOf(CommaSeparator);
-
- int backSlashIndex = _dataSourceAfterTrimmingProtocol.IndexOf(BackSlashCharacter);
-
- // Check the parameters. The parameters are Comma separated in the Data Source. The parameter we really care about is the port
- // If Comma exists, the try to get the port number
- if (commaIndex > -1)
- {
- string parameter = backSlashIndex > -1
- ? ((commaIndex > backSlashIndex) ? tokensByCommaAndSlash[2].Trim() : tokensByCommaAndSlash[1].Trim())
- : tokensByCommaAndSlash[1].Trim();
-
- // Bad Data Source like "server, "
- if (string.IsNullOrEmpty(parameter))
- {
- ReportSNIError(SNIProviders.INVALID_PROV);
- return false;
- }
-
- // For Tcp and Only Tcp are parameters allowed.
- if (_connectionProtocol == Protocol.None)
- {
- _connectionProtocol = Protocol.TCP;
- }
- else if (_connectionProtocol != Protocol.TCP)
- {
- // Parameter has been specified for non-TCP protocol. This is not allowed.
- ReportSNIError(SNIProviders.INVALID_PROV);
- return false;
- }
-
- int port;
- if (!int.TryParse(parameter, out port))
- {
- ReportSNIError(SNIProviders.TCP_PROV);
- return false;
- }
-
- // If the user explicitly specified a invalid port in the connection string.
- if (port < 1)
- {
- ReportSNIError(SNIProviders.TCP_PROV);
- return false;
- }
-
- Port = port;
- }
- // Instance Name Handling. Only if we found a '\' and we did not find a port in the Data Source
- else if (backSlashIndex > -1)
- {
- // This means that there will not be any part separated by comma.
- InstanceName = tokensByCommaAndSlash[1].Trim();
-
- if (string.IsNullOrWhiteSpace(InstanceName))
- {
- ReportSNIError(SNIProviders.INVALID_PROV);
- return false;
- }
-
- if (DefaultSqlServerInstanceName.Equals(InstanceName))
- {
- ReportSNIError(SNIProviders.INVALID_PROV);
- return false;
- }
-
- IsSsrpRequired = true;
- }
-
- InferLocalServerName();
-
- return true;
- }
-
- private void ReportSNIError(SNIProviders provider)
- {
- SNILoadHandle.SingletonInstance.LastError = new SNIError(provider, 0, SNICommon.InvalidConnStringError, Strings.SNI_ERROR_25);
- IsBadDataSource = true;
- }
-
- private bool InferNamedPipesInformation()
- {
- // If we have a datasource beginning with a pipe or we have already determined that the protocol is Named Pipe
- if (_dataSourceAfterTrimmingProtocol.StartsWith(PipeBeginning, StringComparison.Ordinal) || _connectionProtocol == Protocol.NP)
- {
- // If the data source is "np:servername"
- if (!_dataSourceAfterTrimmingProtocol.Contains(PipeBeginning))
- {
- PipeHostName = ServerName = _dataSourceAfterTrimmingProtocol;
- InferLocalServerName();
- PipeName = SNINpHandle.DefaultPipePath;
- return true;
- }
-
- try
- {
- string[] tokensByBackSlash = _dataSourceAfterTrimmingProtocol.Split(BackSlashCharacter);
-
- // The datasource is of the format \\host\pipe\sql\query [0]\[1]\[2]\[3]\[4]\[5]
- // It would at least have 6 parts.
- // Another valid Sql named pipe for an named instance is \\.\pipe\MSSQL$MYINSTANCE\sql\query
- if (tokensByBackSlash.Length < 6)
- {
- ReportSNIError(SNIProviders.NP_PROV);
- return false;
- }
-
- string host = tokensByBackSlash[2];
-
- if (string.IsNullOrEmpty(host))
- {
- ReportSNIError(SNIProviders.NP_PROV);
- return false;
- }
-
- //Check if the "pipe" keyword is the first part of path
- if (!PipeToken.Equals(tokensByBackSlash[3]))
- {
- ReportSNIError(SNIProviders.NP_PROV);
- return false;
- }
-
- if (tokensByBackSlash[4].StartsWith(NamedPipeInstanceNameHeader, StringComparison.Ordinal))
- {
- InstanceName = tokensByBackSlash[4].Substring(NamedPipeInstanceNameHeader.Length);
- }
-
- StringBuilder pipeNameBuilder = new StringBuilder();
-
- for (int i = 4; i < tokensByBackSlash.Length - 1; i++)
- {
- pipeNameBuilder.Append(tokensByBackSlash[i]);
- pipeNameBuilder.Append(Path.DirectorySeparatorChar);
- }
- // Append the last part without a "/"
- pipeNameBuilder.Append(tokensByBackSlash[tokensByBackSlash.Length - 1]);
- PipeName = pipeNameBuilder.ToString();
-
- if (string.IsNullOrWhiteSpace(InstanceName) && !DefaultPipeName.Equals(PipeName))
- {
- InstanceName = PipeToken + PipeName;
- }
-
- ServerName = IsLocalHost(host) ? Environment.MachineName : host;
- // Pipe hostname is the hostname after leading \\ which should be passed down as is to open Named Pipe.
- // For Named Pipes the ServerName makes sense for SPN creation only.
- PipeHostName = host;
- }
- catch (UriFormatException)
- {
- ReportSNIError(SNIProviders.NP_PROV);
- return false;
- }
-
- // DataSource is something like "\\pipename"
- if (_connectionProtocol == Protocol.None)
- {
- _connectionProtocol = Protocol.NP;
- }
- else if (_connectionProtocol != Protocol.NP)
- {
- // In case the path began with a "\\" and protocol was not Named Pipes
- ReportSNIError(SNIProviders.NP_PROV);
- return false;
- }
- return true;
- }
- return false;
- }
-
- private static bool IsLocalHost(string serverName)
- => ".".Equals(serverName) || "(local)".Equals(serverName) || "localhost".Equals(serverName);
}
}
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 0b745ae01e..4e44b22339 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
@@ -10,6 +10,7 @@
using System.Globalization;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
@@ -1913,14 +1914,40 @@ private void AttemptOneLogin(
{
SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, timout={1}[msec], server={2}", ObjectID, timeout.MillisecondsRemaining, serverInfo.ExtendedServerName);
RoutingInfo = null; // forget routing information
+ string localDBDataSource = null;
+ DataSource details = null;
+ if (ConnectionOptions.LocalDBInstance != null)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ //do not get np result if data source is already np
+ string[] splitByColon = serverInfo.UserServerName.Split(':');
+ if (!splitByColon[0].Trim().Equals("np", StringComparison.CurrentCultureIgnoreCase))
+ {
+ localDBDataSource = LocalDB.GetLocalDBDataSource(serverInfo.UserServerName, timeout);
+ //re-writing serverinfo to make NP as servername
+ ServerInfo original = serverInfo;
+ serverInfo = new ServerInfo(ConnectionOptions, localDBDataSource, original.ServerSPN);
+ serverInfo.SetDerivedNames(original.UserProtocol, localDBDataSource);
+ }
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ }
+ string serverName = localDBDataSource ?? serverInfo.ExtendedServerName;
+ details = DataSource.ParseServerName(serverName);
+
_parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
_parser.Connect(serverInfo,
this,
- timeout.LegacyTimerExpire,
+ timeout,
ConnectionOptions,
- withFailover);
+ withFailover,
+ details);
_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 8c16690149..28562b2a94 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
@@ -16,6 +16,7 @@
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
using Microsoft.Data.Sql;
using Microsoft.Data.SqlClient.DataClassification;
using Microsoft.Data.SqlClient.Server;
@@ -361,9 +362,10 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj)
internal void Connect(
ServerInfo serverInfo,
SqlInternalConnectionTds connHandler,
- long timerExpire,
+ TimeoutTimer timerExpire,
SqlConnectionString connectionOptions,
- bool withFailover)
+ bool withFailover,
+ DataSource details)
{
SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt;
bool isTlsFirst = (encrypt == SqlConnectionEncryptOption.Strict);
@@ -454,6 +456,7 @@ internal void Connect(
FQDNforDNSCache,
ref _connHandler.pendingSQLDNSObject,
serverInfo.ServerSPN,
+ details,
integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated,
isTlsFirst,
hostNameInCertificate,
@@ -487,7 +490,7 @@ internal void Connect(
}
_state = TdsParserState.OpenNotLoggedIn;
_physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite;
- _physicalStateObj.TimeoutTime = timerExpire;
+ _physicalStateObj.TimeoutTime = timerExpire.LegacyTimerExpire;
bool marsCapable = false;
@@ -551,6 +554,7 @@ internal void Connect(
FQDNforDNSCache,
ref _connHandler.pendingSQLDNSObject,
serverInfo.ServerSPN,
+ details,
integratedSecurity,
isTlsFirst,
hostNameInCertificate,
@@ -1530,12 +1534,6 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
//
errorMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber);
- // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
- if (details.sniErrorNumber == (int)SNINativeMethodWrapper.SniSpecialErrors.LocalDBErrorCode)
- {
- errorMessage += LocalDBAPI.GetLocalDBMessage((int)details.nativeError);
- win32ErrorCode = 0;
- }
}
}
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
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 1f15345167..2fc9bd2e50 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
@@ -9,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
namespace Microsoft.Data.SqlClient
{
@@ -198,7 +199,7 @@ private void ResetCancelAndProcessAttention()
internal abstract void CreatePhysicalSNIHandle(
string serverName,
- long timerExpire,
+ TimeoutTimer timerExpire,
out byte[] instanceName,
ref byte[][] spnBuffer,
bool flushCache,
@@ -208,6 +209,7 @@ internal abstract void CreatePhysicalSNIHandle(
string cachedFQDN,
ref SQLDNSInfo pendingDNSInfo,
string serverSPN,
+ DataSource dataSourceDetails,
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 9665d8f188..6f3576ac4f 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
@@ -13,6 +13,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
namespace Microsoft.Data.SqlClient.SNI
{
@@ -82,7 +83,7 @@ protected override uint SNIPacketGetData(PacketHandle packet, byte[] inBuff, ref
internal override void CreatePhysicalSNIHandle(
string serverName,
- long timerExpire,
+ TimeoutTimer timerExpire,
out byte[] instanceName,
ref byte[][] spnBuffer,
bool flushCache,
@@ -92,15 +93,17 @@ internal override void CreatePhysicalSNIHandle(
string cachedFQDN,
ref SQLDNSInfo pendingDNSInfo,
string serverSPN,
+ DataSource details,
bool isIntegratedSecurity,
bool tlsFirst,
string hostNameInCertificate,
string serverCertificateFilename)
{
- SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, timerExpire, out instanceName, ref spnBuffer, serverSPN,
- flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst,
- hostNameInCertificate, serverCertificateFilename);
-
+ SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, timerExpire.LegacyTimerExpire, out instanceName, ref spnBuffer, serverSPN,
+ flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst,
+ hostNameInCertificate, serverCertificateFilename, details);
+
+
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 bf8337cacb..50c32741d2 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
@@ -5,12 +5,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Net;
using System.Runtime.InteropServices;
using System.Security.Authentication;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.Data.Common;
-using System.Net;
-using System.Text;
+using Microsoft.Data.ProviderBase;
namespace Microsoft.Data.SqlClient
{
@@ -140,7 +141,7 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async)
internal override void CreatePhysicalSNIHandle(
string serverName,
- long timerExpire,
+ TimeoutTimer timerExpire,
out byte[] instanceName,
ref byte[][] spnBuffer,
bool flushCache,
@@ -150,6 +151,7 @@ internal override void CreatePhysicalSNIHandle(
string cachedFQDN,
ref SQLDNSInfo pendingDNSInfo,
string serverSPN,
+ DataSource dataSource,
bool isIntegratedSecurity,
bool tlsFirst,
string hostNameInCertificate,
@@ -178,13 +180,13 @@ internal override void CreatePhysicalSNIHandle(
// Translate to SNI timeout values (Int32 milliseconds)
long timeout;
- if (long.MaxValue == timerExpire)
+ if (long.MaxValue == timerExpire.LegacyTimerExpire)
{
timeout = int.MaxValue;
}
else
{
- timeout = ADP.TimerRemainingMilliseconds(timerExpire);
+ timeout = ADP.TimerRemainingMilliseconds(timerExpire.LegacyTimerExpire);
if (timeout > int.MaxValue)
{
timeout = int.MaxValue;
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 cf900c4553..30a9384826 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs
@@ -1158,6 +1158,15 @@ internal static string LocalDB_MethodNotFound {
}
}
+ ///
+ /// Looks up a localized string similar to SQLUserInstanceDLL is not supported on current plaform. Install SqlLocalDB as an alternate option to connect to LocalDB..
+ ///
+ internal static string LocalDB_SQLUserInstanceDLL {
+ get {
+ return ResourceManager.GetString("LocalDB_SQLUserInstanceDLL", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cannot obtain Local Database Runtime error message.
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
index 7f52b2556f..1cf66028fd 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
@@ -1950,4 +1950,7 @@
Cannot set the AccessTokenCallback property if 'Authentication=Active Directory Default' has been specified in the connection string.
+
+ SQLUserInstanceDLL is not supported on current plaform. Install SqlLocalDB as an alternate option to connect to LocalDB.
+
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 675dd57fe6..c40c02e087 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -185,6 +185,9 @@
Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs
+
+ Microsoft\Data\SqlClient\DataSource.cs
+
Microsoft\Data\SqlClient\DisposableTemporaryOnStack.cs
@@ -206,6 +209,9 @@
Microsoft\Data\SqlClient\LocalAppContextSwitches.cs
+
+ Microsoft\Data\SqlClient\LocalDB.Windows.cs
+
Microsoft\Data\SqlClient\NoneAttestationEnclaveProvider.cs
@@ -599,6 +605,21 @@
Resources\ResDescriptionAttribute.cs
+
+ Common\Interop\Windows\Interop.Libraries.cs
+
+
+ Common\Interop\Windows\kernel32\Interop.LoadLibraryEx.cs
+
+
+ Common\CoreLib\Microsoft\Win32\SafeHandles\SafeLibraryHandle.cs
+
+
+ Common\Interop\Windows\kernel32\Interop.FreeLibrary.cs
+
+
+ Common\Interop\Windows\kernel32\Interop.GetProcAddress.cs
+
@@ -625,7 +646,6 @@
-
@@ -737,4 +757,4 @@
-
\ No newline at end of file
+
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 b9af5849c4..c516df7c54 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
@@ -38,18 +38,10 @@ internal static class SNINativeMethodWrapper
internal const int InternalExceptionError = 35;
internal const int ConnOpenFailedError = 40;
internal const int ErrorSpnLookup = 44;
- internal const int LocalDBErrorCode = 50;
internal const int MultiSubnetFailoverWithMoreThan64IPs = 47;
internal const int MultiSubnetFailoverWithInstanceSpecified = 48;
internal const int MultiSubnetFailoverWithNonTcpProtocol = 49;
internal const int MaxErrorValue = 50157;
- internal const int LocalDBNoInstanceName = 51;
- internal const int LocalDBNoInstallation = 52;
- internal const int LocalDBInvalidConfig = 53;
- internal const int LocalDBNoSqlUserInstanceDllPath = 54;
- internal const int LocalDBInvalidSqlUserInstanceDllPath = 55;
- internal const int LocalDBFailedToLoadDll = 56;
- internal const int LocalDBBadRuntime = 57;
internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer
internal static int SniMaxComposedSpnLength
@@ -148,8 +140,6 @@ internal static bool NativeSetData(void* passedData, int passedSize)
internal enum SniSpecialErrors : uint
{
- LocalDBErrorCode = SNINativeMethodWrapper.LocalDBErrorCode,
-
// multi-subnet-failover specific error codes
MultiSubnetFailoverWithMoreThan64IPs = SNINativeMethodWrapper.MultiSubnetFailoverWithMoreThan64IPs,
MultiSubnetFailoverWithInstanceSpecified = SNINativeMethodWrapper.MultiSubnetFailoverWithInstanceSpecified,
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs
index e3b4ea2ef4..7ac16542f7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs
@@ -3,16 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Diagnostics;
-using System.Globalization;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using System.Security;
-using System.Text;
-using System.Threading;
-using Microsoft.Data.SqlClient;
namespace Microsoft.Data
{
@@ -26,7 +17,6 @@ internal static class LocalDBAPI
static bool _partialTrustFlagChecked = false;
static bool _partialTrustAllowed = false;
-
// check if name is in format (localdb)\ and return instance name if it is
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
@@ -47,208 +37,10 @@ internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
return input.ToString();
}
-
}
return null;
}
- internal static void ReleaseDLLHandles()
- {
- s_userInstanceDLLHandle = IntPtr.Zero;
- s_localDBFormatMessage = null;
- s_localDBCreateInstance = null;
- }
-
-
-
- //This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle
- static IntPtr s_userInstanceDLLHandle = IntPtr.Zero;
-
- static object s_dllLock = new object();
-
- static IntPtr UserInstanceDLLHandle
- {
- get
- {
- if (s_userInstanceDLLHandle == IntPtr.Zero)
- {
- bool lockTaken = false;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- Monitor.Enter(s_dllLock, ref lockTaken);
- if (s_userInstanceDLLHandle == IntPtr.Zero)
- {
- SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
- if (s_userInstanceDLLHandle != IntPtr.Zero)
- {
- SqlClientEventSource.Log.TryTraceEvent(" LocalDB - handle obtained");
- }
- else
- {
- SNINativeMethodWrapper.SNI_Error sniError = new SNINativeMethodWrapper.SNI_Error();
- SNINativeMethodWrapper.SNIGetLastError(out sniError);
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
- }
- }
- }
- finally
- {
- if (lockTaken)
- Monitor.Exit(s_dllLock);
- }
- }
- return s_userInstanceDLLHandle;
- }
- }
-
- [SuppressUnmanagedCodeSecurity]
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags);
-
- static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null;
-
- static LocalDBCreateInstanceDelegate LocalDBCreateInstance
- {
- get
- {
- if (s_localDBCreateInstance == null)
- {
- bool lockTaken = false;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- Monitor.Enter(s_dllLock, ref lockTaken);
- if (s_localDBCreateInstance == null)
- {
- IntPtr functionAddr = SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBCreateInstance");
-
- if (functionAddr == IntPtr.Zero)
- {
- int hResult = Marshal.GetLastWin32Error();
- SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBCreateInstance error 0x{0}", hResult);
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound"));
- }
- s_localDBCreateInstance = (LocalDBCreateInstanceDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBCreateInstanceDelegate));
- }
- }
- finally
- {
- if (lockTaken)
- Monitor.Exit(s_dllLock);
- }
- }
- return s_localDBCreateInstance;
- }
- }
-
-
- [SuppressUnmanagedCodeSecurity]
- [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
- private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, UInt32 dwFlags, UInt32 dwLanguageId, StringBuilder buffer, ref UInt32 buflen);
-
- static LocalDBFormatMessageDelegate s_localDBFormatMessage = null;
-
- static LocalDBFormatMessageDelegate LocalDBFormatMessage
- {
- get
- {
- if (s_localDBFormatMessage == null)
- {
- bool lockTaken = false;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- Monitor.Enter(s_dllLock, ref lockTaken);
- if (s_localDBFormatMessage == null)
- {
- IntPtr functionAddr = SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage");
-
- if (functionAddr == IntPtr.Zero)
- {
- // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error.
- int hResult = Marshal.GetLastWin32Error();
- SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult);
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound"));
- }
- s_localDBFormatMessage = (LocalDBFormatMessageDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBFormatMessageDelegate));
- }
- }
- finally
- {
- if (lockTaken)
- Monitor.Exit(s_dllLock);
- }
- }
- return s_localDBFormatMessage;
- }
- }
-
- const UInt32 const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer
- const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message, according to Serverless team, 1K will be enough for all messages
-
-
- internal static string GetLocalDBMessage(int hrCode)
- {
- Debug.Assert(hrCode < 0, "HRCode does not indicate error");
- try
- {
- StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
- UInt32 len = (UInt32)buffer.Capacity;
-
-
- // First try for current culture
- int hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (UInt32)CultureInfo.CurrentCulture.LCID,
- buffer: buffer, buflen: ref len);
- if (hResult >= 0)
- return buffer.ToString();
- else
- {
- // Message is not available for current culture, try default
- buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
- len = (UInt32)buffer.Capacity;
- hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: 0 /* thread locale with fallback to English */,
- buffer: buffer, buflen: ref len);
- if (hResult >= 0)
- return buffer.ToString();
- else
- return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), hResult);
- }
- }
- catch (SqlException exc)
- {
- return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), exc.Message);
- }
- }
-
-
- static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0)
- {
- Debug.Assert((localDbError == 0) || (sniError == 0), "LocalDB error and SNI error cannot be specified simultaneously");
- Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty");
- SqlErrorCollection collection = new SqlErrorCollection();
-
- int errorCode = (localDbError == 0) ? sniError : localDbError;
-
- if (sniError != 0)
- {
- string sniErrorMessage = SQL.GetSNIErrorMessage(sniError);
- errorMessage = String.Format((IFormatProvider)null, "{0} (error: {1} - {2})",
- errorMessage, sniError, sniErrorMessage);
- }
-
- collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0));
-
- if (localDbError != 0)
- collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, GetLocalDBMessage(localDbError), null, 0));
-
- SqlException exc = SqlException.CreateException(collection, null);
-
- exc._doNotReconnect = true;
-
- return exc;
- }
-
private class InstanceInfo
{
internal InstanceInfo(string version)
@@ -261,9 +53,6 @@ internal InstanceInfo(string version)
internal bool created;
}
- static object s_configLock = new object();
- static Dictionary s_configurableInstances = null;
-
internal static void DemandLocalDBPermissions()
{
if (!_partialTrustAllowed)
@@ -293,73 +82,5 @@ internal static void AssertLocalDBPermissions()
{
_partialTrustAllowed = true;
}
-
-
- internal static void CreateLocalDBInstance(string instance)
- {
- DemandLocalDBPermissions();
- if (s_configurableInstances == null)
- {
- // load list of instances from configuration, mark them as not created
- bool lockTaken = false;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- Monitor.Enter(s_configLock, ref lockTaken);
- if (s_configurableInstances == null)
- {
- Dictionary tempConfigurableInstances = new Dictionary(StringComparer.OrdinalIgnoreCase);
- object section = ConfigurationManager.GetSection("system.data.localdb");
- if (section != null) // if no section just skip creation
- {
- // validate section type
- LocalDBConfigurationSection configSection = section as LocalDBConfigurationSection;
- if (configSection == null)
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_BadConfigSectionType"));
- foreach (LocalDBInstanceElement confElement in configSection.LocalDbInstances)
- {
- Debug.Assert(confElement.Name != null && confElement.Version != null, "Both name and version should not be null");
- tempConfigurableInstances.Add(confElement.Name.Trim(), new InstanceInfo(confElement.Version.Trim()));
- }
- }
- else
- {
- SqlClientEventSource.Log.TryTraceEvent(" No system.data.localdb section found in configuration");
- }
- s_configurableInstances = tempConfigurableInstances;
- }
- }
- finally
- {
- if (lockTaken)
- Monitor.Exit(s_configLock);
- }
- }
-
- InstanceInfo instanceInfo = null;
-
- if (!s_configurableInstances.TryGetValue(instance, out instanceInfo))
- return; // instance name was not in the config
-
- if (instanceInfo.created)
- return; // instance has already been created
-
- Debug.Assert(!instance.Contains("\0"), "Instance name should contain embedded nulls");
-
- if (instanceInfo.version.Contains("\0"))
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_InvalidVersion"), instance: instance);
-
- // LocalDBCreateInstance is thread- and cross-process safe method, it is OK to call from two threads simultaneously
- int hr = LocalDBCreateInstance(instanceInfo.version, instance, flags: 0);
- SqlClientEventSource.Log.TryTraceEvent(" Starting creation of instance {0} version {1}", instance, instanceInfo.version);
-
- if (hr < 0)
- {
- throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr);
- }
-
- SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance);
- instanceInfo.created = true; // mark instance as created
- } // CreateLocalDbInstance
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBConfig.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBConfig.cs
deleted file mode 100644
index 4c830520d9..0000000000
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBConfig.cs
+++ /dev/null
@@ -1,81 +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
-{
- using System;
- using System.Collections;
- using System.Configuration;
-
- internal sealed class LocalDBInstanceElement : ConfigurationElement
- {
- [ConfigurationProperty("name", IsRequired = true)]
- public string Name
- {
- get
- {
- return this["name"] as string;
- }
- }
-
- [ConfigurationProperty("version", IsRequired = true)]
- public string Version
- {
- get
- {
- return this["version"] as string;
- }
- }
- }
-
- internal sealed class LocalDBInstancesCollection : ConfigurationElementCollection
- {
-
- private class TrimOrdinalIgnoreCaseStringComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- string xStr = x as string;
- if (xStr != null)
- x = xStr.Trim();
-
- string yStr = y as string;
- if (yStr != null)
- y = yStr.Trim();
-
- return StringComparer.OrdinalIgnoreCase.Compare(x, y);
- }
- }
-
- static readonly TrimOrdinalIgnoreCaseStringComparer s_comparer = new TrimOrdinalIgnoreCaseStringComparer();
-
- internal LocalDBInstancesCollection()
- : base(s_comparer)
- {
- }
-
- protected override ConfigurationElement CreateNewElement()
- {
- return new LocalDBInstanceElement();
- }
-
- protected override object GetElementKey(ConfigurationElement element)
- {
- return ((LocalDBInstanceElement)element).Name;
- }
-
- }
-
- internal sealed class LocalDBConfigurationSection : ConfigurationSection
- {
- [ConfigurationProperty("localdbinstances", IsRequired = true)]
- public LocalDBInstancesCollection LocalDbInstances
- {
- get
- {
- return (LocalDBInstancesCollection)this["localdbinstances"] ?? new LocalDBInstancesCollection();
- }
- }
- }
-}
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 aa6a670021..068b37dc71 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
@@ -2300,7 +2300,7 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt
_parser.Connect(serverInfo,
this,
- timeout.LegacyTimerExpire,
+ timeout,
ConnectionOptions,
withFailover,
isFirstTransparentAttempt,
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 21abbab757..e6759bf7f2 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
@@ -10,6 +10,7 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
@@ -17,8 +18,8 @@
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using System.Net;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
using Microsoft.Data.Sql;
using Microsoft.Data.SqlClient.DataClassification;
using Microsoft.Data.SqlClient.Server;
@@ -494,7 +495,7 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj)
internal void Connect(ServerInfo serverInfo,
SqlInternalConnectionTds connHandler,
- long timerExpire,
+ TimeoutTimer timerExpire,
SqlConnectionString connectionOptions,
bool withFailover,
bool isFirstTransparentAttempt,
@@ -536,7 +537,17 @@ internal void Connect(ServerInfo serverInfo,
//Create LocalDB instance if necessary
if (connHandler.ConnectionOptions.LocalDBInstance != null)
{
- LocalDBAPI.CreateLocalDBInstance(connHandler.ConnectionOptions.LocalDBInstance);
+ //do not get np result if data source is already np
+ string[] splitByColon = serverInfo.UserServerName.Split(':');
+ if (!splitByColon[0].Trim().Equals("np", StringComparison.CurrentCultureIgnoreCase))
+ {
+ string localDBDataSource = LocalDB.GetLocalDBDataSource(serverInfo.UserServerName, timerExpire);
+ string serverName = localDBDataSource ?? serverInfo.ExtendedServerName;
+ //re-writing serverinfo
+ ServerInfo original = serverInfo;
+ serverInfo = new ServerInfo(connectionOptions, serverName, original.ServerSPN);
+ serverInfo.SetDerivedNames(original.UserProtocol, serverName);
+ }
if (encrypt == SqlConnectionEncryptOption.Mandatory)
{
// Encryption is not supported on SQL Local DB - disable it for current session.
@@ -679,7 +690,7 @@ internal void Connect(ServerInfo serverInfo,
}
_state = TdsParserState.OpenNotLoggedIn;
_physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite; // SQL BU DT 376766
- _physicalStateObj.TimeoutTime = timerExpire;
+ _physicalStateObj.TimeoutTime = timerExpire.LegacyTimerExpire;
bool marsCapable = false;
@@ -1934,13 +1945,6 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
// SNI error. Replace the entire message
//
errorMessage = SQL.GetSNIErrorMessage((int)sniError.sniError);
-
- // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
- if (sniError.sniError == (int)SNINativeMethodWrapper.SniSpecialErrors.LocalDBErrorCode)
- {
- errorMessage += LocalDBAPI.GetLocalDBMessage((int)sniError.nativeError);
- win32ErrorCode = 0;
- }
}
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
sqlContextInfo, providerName, (int)sniError.sniError, errorMessage);
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 5d5ade91f4..4967ad62a8 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
@@ -12,6 +12,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
namespace Microsoft.Data.SqlClient
{
@@ -279,7 +280,7 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async)
internal void CreatePhysicalSNIHandle(
string serverName,
- long timerExpire,
+ TimeoutTimer timerExpire,
out byte[] instanceName,
byte[] spnBuffer,
bool flushCache,
@@ -295,13 +296,13 @@ internal void CreatePhysicalSNIHandle(
// Translate to SNI timeout values (Int32 milliseconds)
long timeout;
- if (long.MaxValue == timerExpire)
+ if (long.MaxValue == timerExpire.LegacyTimerExpire)
{
timeout = int.MaxValue;
}
else
{
- timeout = ADP.TimerRemainingMilliseconds(timerExpire);
+ timeout = ADP.TimerRemainingMilliseconds(timerExpire.LegacyTimerExpire);
if (timeout > int.MaxValue)
{
timeout = int.MaxValue;
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 5fd4a54382..4579a24131 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -6972,6 +6972,15 @@ internal static string LocalDB_MethodNotFound {
}
}
+ ///
+ /// Looks up a localized string similar to SQLUserInstanceDLL is not supported on current plaform. Install SqlLocalDB as an alternate option to connect to LocalDB..
+ ///
+ internal static string LocalDB_SQLUserInstanceDLL {
+ get {
+ return ResourceManager.GetString("LocalDB_SQLUserInstanceDLL", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cannot obtain Local Database Runtime error message.
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index 2b2374e490..08096e95c5 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4644,4 +4644,7 @@
Cannot set the AccessTokenCallback property if 'Authentication=Active Directory Default' has been specified in the connection string.
+
+ SQLUserInstanceDLL is not supported on current plaform. Install SqlLocalDB as an alternate option to connect to LocalDB.
+
\ No newline at end of file
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
index 8b84feecfc..43e5ff0aed 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs
@@ -2,6 +2,8 @@
// 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.Common
{
///
@@ -20,5 +22,8 @@ internal static object LocalMachineRegistryValue(string subkey, string queryvalu
// No registry in non-Windows environments
return null;
}
+
+ internal static PlatformNotSupportedException LocalDBNotSUpportedException() =>
+ new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported.
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs
index 9948b223d1..c8db6f97e1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs
@@ -2,9 +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 Microsoft.Data.Common;
using System;
using System.Diagnostics;
+using Microsoft.Data.Common;
namespace Microsoft.Data.ProviderBase
{
@@ -180,6 +180,42 @@ internal long MillisecondsRemaining
return milliseconds;
}
}
+
+ // Returns milliseconds remaining trimmed to zero for none remaining
+ internal int MillisecondsRemainingInt
+ {
+ get
+ {
+ //-------------------
+ // Method Body
+ int milliseconds;
+ if (_isInfiniteTimeout)
+ {
+ milliseconds = int.MaxValue;
+ }
+ else
+ {
+ long longMilliseconds = ADP.TimerRemainingMilliseconds(_timerExpire);
+ if (0 > longMilliseconds)
+ {
+ milliseconds = 0;
+ }
+ else if (longMilliseconds > int.MaxValue)
+ {
+ milliseconds = int.MaxValue;
+ }
+ else
+ {
+ milliseconds = (int)longMilliseconds;
+ }
+ }
+
+ //--------------------
+ // Postconditions
+ Debug.Assert(0 <= milliseconds);
+
+ return milliseconds;
+ }
+ }
}
}
-
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/DataSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/DataSource.cs
new file mode 100644
index 0000000000..4bcd67c348
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/DataSource.cs
@@ -0,0 +1,411 @@
+// 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.IO;
+using System.Text;
+
+namespace Microsoft.Data.SqlClient
+{
+ internal class DataSource
+ {
+ private const char CommaSeparator = ',';
+ private const char SemiColon = ':';
+ private const char BackSlashCharacter = '\\';
+
+ private const string DefaultHostName = "localhost";
+ private const string DefaultSqlServerInstanceName = "mssqlserver";
+ private const string PipeBeginning = @"\\";
+ private const string Slash = @"/";
+ private const string PipeToken = "pipe";
+ private const string LocalDbHost = "(localdb)";
+ private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#";
+ private const string NamedPipeInstanceNameHeader = "mssql$";
+ private const string DefaultPipeName = "sql\\query";
+
+ internal const int LocalDBNoInstanceName = 51;
+ internal const int LocalDBNoInstallation = 52;
+ internal const int LocalDBInvalidConfig = 53;
+ internal const int LocalDBNoSqlUserInstanceDllPath = 54;
+ internal const int LocalDBInvalidSqlUserInstanceDllPath = 55;
+ internal const int LocalDBFailedToLoadDll = 56;
+ internal const int LocalDBBadRuntime = 57;
+ internal const int InvalidConnStringError = 25;
+
+ private enum ProviderEnum
+ {
+ HTTP_PROV,
+ NP_PROV,
+ SESSION_PROV,
+ SIGN_PROV,
+ SM_PROV,
+ SMUX_PROV,
+ SSL_PROV,
+ TCP_PROV,
+ VIA_PROV,
+ CTAIP_PROV,
+ MAX_PROVS,
+ INVALID_PROV,
+ }
+ internal enum Protocol { TCP, NP, None, Admin };
+
+ internal Protocol _connectionProtocol = Protocol.None;
+
+ ///
+ /// Provides the HostName of the server to connect to for TCP protocol.
+ /// This information is also used for finding the SPN of SqlServer
+ ///
+ internal string ServerName { get; private set; }
+
+ ///
+ /// Provides the port on which the TCP connection should be made if one was specified in Data Source
+ ///
+ internal int Port { get; private set; } = -1;
+
+ ///
+ /// Provides the inferred Instance Name from Server Data Source
+ ///
+ internal string InstanceName { get; private set; }
+
+ ///
+ /// Provides the pipe name in case of Named Pipes
+ ///
+ internal string PipeName { get; private set; }
+
+ ///
+ /// Provides the HostName to connect to in case of Named pipes Data Source
+ ///
+ internal string PipeHostName { get; private set; }
+
+ private readonly string _workingDataSource;
+ private readonly string _dataSourceAfterTrimmingProtocol;
+
+ internal bool IsBadDataSource { get; private set; } = false;
+
+ internal bool IsSsrpRequired { get; private set; } = false;
+
+ private DataSource(string dataSource)
+ {
+ // Remove all whitespaces from the datasource and all operations will happen on lower case.
+ _workingDataSource = dataSource.Trim().ToLowerInvariant();
+
+ int firstIndexOfColon = _workingDataSource.IndexOf(SemiColon);
+
+ PopulateProtocol();
+
+ _dataSourceAfterTrimmingProtocol = (firstIndexOfColon > -1) && _connectionProtocol != Protocol.None
+ ? _workingDataSource.Substring(firstIndexOfColon + 1).Trim() : _workingDataSource;
+
+ if (_dataSourceAfterTrimmingProtocol.Contains(Slash)) // Pipe paths only allow back slashes
+ {
+ if (_connectionProtocol == Protocol.None)
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.INVALID_PROV, ServerName), "");
+ }
+ else if (_connectionProtocol == Protocol.NP)
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.NP_PROV, ServerName), "");
+ }
+ else if (_connectionProtocol == Protocol.TCP)
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.TCP_PROV, ServerName), "");
+ }
+ }
+ }
+
+ private void PopulateProtocol()
+ {
+ string[] splitByColon = _workingDataSource.Split(SemiColon);
+
+ if (splitByColon.Length <= 1)
+ {
+ _connectionProtocol = Protocol.None;
+ }
+ else
+ {
+ // We trim before switching because " tcp : server , 1433 " is a valid data source
+ switch (splitByColon[0].Trim())
+ {
+ case TdsEnums.TCP:
+ _connectionProtocol = Protocol.TCP;
+ break;
+ case TdsEnums.NP:
+ _connectionProtocol = Protocol.NP;
+ break;
+ case TdsEnums.ADMIN:
+ _connectionProtocol = Protocol.Admin;
+ break;
+ default:
+ // None of the supported protocols were found. This may be a IPv6 address
+ _connectionProtocol = Protocol.None;
+ break;
+ }
+ }
+ }
+
+ // LocalDbInstance name always starts with (localdb)
+ // possible scenarios:
+ // (localdb)\
+ // or (localdb)\. which goes to default localdb
+ // or (localdb)\.\
+ internal static string GetLocalDBInstance(string dataSource, out bool error)
+ {
+ string instanceName = null;
+ // ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
+ ReadOnlySpan input = dataSource.AsSpan().TrimStart();
+ error = false;
+ // NetStandard 2.0 does not support passing a string to ReadOnlySpan
+ if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
+ {
+ // When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index
+ // Such ad input = input[1..];
+ input = input.Slice(LocalDbHost.Length);
+ if (!input.IsEmpty && input[0] == BackSlashCharacter)
+ {
+ input = input.Slice(1);
+ }
+ if (!input.IsEmpty)
+ {
+ instanceName = input.Trim().ToString();
+ }
+ else
+ {
+ error = true;
+ SqlErrorCollection errors = new SqlErrorCollection
+ {
+ new SqlError(InvalidConnStringError, 0, TdsEnums.FATAL_ERROR_CLASS, dataSource, string.Format("(provider: {0}, error: {1} - {2})",
+ ProviderEnum.INVALID_PROV, LocalDBNoInstanceName, Strings.SNI_ERROR_51), null, 0)
+ };
+ throw SqlException.CreateException(errors, "");
+ }
+ }
+ else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
+ {
+ instanceName = input.Trim().ToString();
+ }
+
+ return instanceName;
+ }
+ private static SqlErrorCollection LocalDBErrorFormat(ProviderEnum providerName, string ServerName)
+ {
+ return new SqlErrorCollection
+ {
+ new SqlError(InvalidConnStringError, 0, TdsEnums.MIN_ERROR_CLASS, ServerName, string.Format("(provider: {0}, error: {1} - {2})",
+ providerName, InvalidConnStringError, Strings.SNI_ERROR_25), null, 0)
+ };
+ }
+
+ internal static DataSource ParseServerName(string dataSource)
+ {
+ DataSource details = new DataSource(dataSource);
+
+ if (details.IsBadDataSource)
+ {
+ return null;
+ }
+
+ if (details.InferNamedPipesInformation())
+ {
+ return details;
+ }
+
+ if (details.IsBadDataSource)
+ {
+ return null;
+ }
+
+ if (details.InferConnectionDetails())
+ {
+ return details;
+ }
+
+ return null;
+ }
+
+ private void InferLocalServerName()
+ {
+ // If Server name is empty or localhost, then use "localhost"
+ if (string.IsNullOrEmpty(ServerName) || IsLocalHost(ServerName) ||
+ (Environment.MachineName.Equals(ServerName, StringComparison.CurrentCultureIgnoreCase) &&
+ _connectionProtocol == Protocol.Admin))
+ {
+ // For DAC use "localhost" instead of the server name.
+ ServerName = DefaultHostName;
+ }
+ }
+
+ private bool InferConnectionDetails()
+ {
+ string[] tokensByCommaAndSlash = _dataSourceAfterTrimmingProtocol.Split(BackSlashCharacter, CommaSeparator);
+ ServerName = tokensByCommaAndSlash[0].Trim();
+
+ int commaIndex = _dataSourceAfterTrimmingProtocol.IndexOf(CommaSeparator);
+
+ int backSlashIndex = _dataSourceAfterTrimmingProtocol.IndexOf(BackSlashCharacter);
+
+ // Check the parameters. The parameters are Comma separated in the Data Source. The parameter we really care about is the port
+ // If Comma exists, the try to get the port number
+ if (commaIndex > -1)
+ {
+ string parameter = backSlashIndex > -1
+ ? ((commaIndex > backSlashIndex) ? tokensByCommaAndSlash[2].Trim() : tokensByCommaAndSlash[1].Trim())
+ : tokensByCommaAndSlash[1].Trim();
+
+ // Bad Data Source like "server, "
+ if (string.IsNullOrEmpty(parameter))
+ {
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.INVALID_PROV, ServerName), "");
+ }
+
+ // For Tcp and Only Tcp are parameters allowed.
+ if (_connectionProtocol == Protocol.None)
+ {
+ _connectionProtocol = Protocol.TCP;
+ }
+ else if (_connectionProtocol != Protocol.TCP)
+ {
+ IsBadDataSource = true;
+ // Parameter has been specified for non-TCP protocol. This is not allowed.
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.INVALID_PROV, ServerName), "");
+ }
+
+ int port;
+ if (!int.TryParse(parameter, out port))
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.TCP_PROV, ServerName), "");
+ }
+
+ // If the user explicitly specified a invalid port in the connection string.
+ if (port < 1)
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.TCP_PROV, ServerName), "");
+ }
+
+ Port = port;
+ }
+ // Instance Name Handling. Only if we found a '\' and we did not find a port in the Data Source
+ else if (backSlashIndex > -1)
+ {
+ // This means that there will not be any part separated by comma.
+ InstanceName = tokensByCommaAndSlash[1].Trim();
+
+ if (string.IsNullOrWhiteSpace(InstanceName))
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.INVALID_PROV, ServerName), "");
+ }
+
+ if (DefaultSqlServerInstanceName.Equals(InstanceName))
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.INVALID_PROV, ServerName), "");
+ }
+
+ IsSsrpRequired = true;
+ }
+
+ InferLocalServerName();
+
+ return true;
+ }
+
+ private bool InferNamedPipesInformation()
+ {
+ // If we have a datasource beginning with a pipe or we have already determined that the protocol is Named Pipe
+ if (_dataSourceAfterTrimmingProtocol.StartsWith(PipeBeginning) || _connectionProtocol == Protocol.NP)
+ {
+ // If the data source is "np:servername"
+ if (!_dataSourceAfterTrimmingProtocol.Contains(PipeBeginning))
+ {
+ PipeHostName = ServerName = _dataSourceAfterTrimmingProtocol;
+ InferLocalServerName();
+ PipeName = DefaultPipeName;
+ return true;
+ }
+
+ try
+ {
+ string[] tokensByBackSlash = _dataSourceAfterTrimmingProtocol.Split(BackSlashCharacter);
+
+ // The datasource is of the format \\host\pipe\sql\query [0]\[1]\[2]\[3]\[4]\[5]
+ // It would at least have 6 parts.
+ // Another valid Sql named pipe for an named instance is \\.\pipe\MSSQL$MYINSTANCE\sql\query
+ if (tokensByBackSlash.Length < 6)
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.NP_PROV, ServerName), "");
+ }
+
+ string host = tokensByBackSlash[2];
+
+ if (string.IsNullOrEmpty(host))
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.NP_PROV, ServerName), "");
+ }
+
+ //Check if the "pipe" keyword is the first part of path
+ if (!PipeToken.Equals(tokensByBackSlash[3]))
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.NP_PROV, ServerName), "");
+ }
+
+ if (tokensByBackSlash[4].StartsWith(NamedPipeInstanceNameHeader))
+ {
+ InstanceName = tokensByBackSlash[4].Substring(NamedPipeInstanceNameHeader.Length);
+ }
+
+ StringBuilder pipeNameBuilder = new StringBuilder();
+
+ for (int i = 4; i < tokensByBackSlash.Length - 1; i++)
+ {
+ pipeNameBuilder.Append(tokensByBackSlash[i]);
+ pipeNameBuilder.Append(Path.DirectorySeparatorChar);
+ }
+ // Append the last part without a "/"
+ pipeNameBuilder.Append(tokensByBackSlash[tokensByBackSlash.Length - 1]);
+ PipeName = pipeNameBuilder.ToString();
+
+ if (string.IsNullOrWhiteSpace(InstanceName) && !DefaultPipeName.Equals(PipeName))
+ {
+ InstanceName = PipeToken + PipeName;
+ }
+
+ ServerName = IsLocalHost(host) ? Environment.MachineName : host;
+ // Pipe hostname is the hostname after leading \\ which should be passed down as is to open Named Pipe.
+ // For Named Pipes the ServerName makes sense for SPN creation only.
+ PipeHostName = host;
+ }
+ catch (UriFormatException)
+ {
+ IsBadDataSource = true;
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.NP_PROV, ServerName), "");
+ }
+
+ // DataSource is something like "\\pipename"
+ if (_connectionProtocol == Protocol.None)
+ {
+ _connectionProtocol = Protocol.NP;
+ }
+ else if (_connectionProtocol != Protocol.NP)
+ {
+ IsBadDataSource = true;
+ // In case the path began with a "\\" and protocol was not Named Pipes
+ throw SqlException.CreateException(LocalDBErrorFormat(ProviderEnum.NP_PROV, ServerName), "");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private static bool IsLocalHost(string serverName)
+ => ".".Equals(serverName) || "(local)".Equals(serverName) || "localhost".Equals(serverName);
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDB.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDB.Windows.cs
new file mode 100644
index 0000000000..b1437f4343
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDB.Windows.cs
@@ -0,0 +1,404 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
+using Microsoft.Data.ProviderBase;
+using Microsoft.Win32;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.Data.SqlClient
+{
+ internal sealed class LocalDB
+ {
+ private static readonly LocalDB Instance = new LocalDB();
+ private const string ClassName = nameof(LocalDB);
+ //HKEY_LOCAL_MACHINE
+ private const string LocalDBInstalledVersionRegistryKey = "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions\\";
+
+ // Set a non-infinite timeout on the regex to ensure we never hang. 10 seconds should
+ // be more than enough as a fail-safe for the small amount of text that is going to be processed. Since
+ // we can't set the timeout on each Match() call, this prioritizes perf (RegexOptions.Compiled) over
+ // the small possiblity that we might go a little over a connect timeout during extremely high load.
+ private static readonly Regex regex = new Regex("(np:.+)\r", RegexOptions.Compiled | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(10));
+
+ private const string InstanceAPIPathValueName = "InstanceAPIPath";
+ private const string ProcLocalDBStartInstance = "LocalDBStartInstance";
+ private const int MAX_LOCAL_DB_CONNECTION_STRING_SIZE = 260;
+ private IntPtr _startInstanceHandle = IntPtr.Zero;
+ private static Lazy s_sqlLocalDBExe = new Lazy(() => GetPathToSqlLocalDB());
+
+ // Local Db api doc https://msdn.microsoft.com/en-us/library/hh217143.aspx
+ // HRESULT LocalDBStartInstance( [Input ] PCWSTR pInstanceName, [Input ] DWORD dwFlags,[Output] LPWSTR wszSqlConnection,[Input/Output] LPDWORD lpcchSqlConnection);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int LocalDBStartInstance(
+ [In] [MarshalAs(UnmanagedType.LPWStr)] string localDBInstanceName,
+ [In] int flags,
+ [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder sqlConnectionDataSource,
+ [In, Out]ref int bufferLength);
+
+ private LocalDBStartInstance localDBStartInstanceFunc = null;
+
+ private volatile SafeLibraryHandle _sqlUserInstanceLibraryHandle;
+
+ private LocalDB() { }
+
+ internal static string GetLocalDBConnectionString(string localDbInstance, TimeoutTimer timeout)
+ {
+ try
+ {
+ return Instance.LoadUserInstanceDll() ? Instance.GetConnectionString(localDbInstance) : null;
+ }
+ catch(Exception ex)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(ClassName, EventType.ERR,ex?.Message);
+ SqlClientEventSource.Log.TryTraceEvent(ClassName, EventType.INFO, "Falling back to use SqlLocalDB.exe.");
+
+ // The old logic to load the SqlUserInstance.dll did not quite work possibly because
+ // of an archiecture mismatch (e.g. we are running in an ARM64 process and SqlLocalDB.exe
+ // installed on the machine is an AMD64 process).
+ // We try to figure out what we need by asking SqlLocalDB.exe (out of proc).
+
+ if (!TryGetLocalDBConnectionStringUsingSqlLocalDBExe(localDbInstance, timeout, out string connString, out string error))
+ {
+ SqlClientEventSource.Log.TryTraceEvent(ClassName, EventType.ERR, "Unable to to use SqlLocalDB.exe to get the ConnectionString.");
+ //errors from localdb.exe
+ if (error != null)
+ throw new Exception(error, ex);
+ else
+ throw new Exception(StringsHelper.LocalDB_SQLUserInstanceDLL, ex);
+ }
+ return connString;
+ }
+ }
+
+ private static string GetPathToSqlLocalDB()
+ {
+ RegistryKey mssqlRegKey = null;
+
+ try
+ {
+ mssqlRegKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server");
+ if (mssqlRegKey != null)
+ {
+ string[] alphaNumnericKeys = mssqlRegKey.GetSubKeyNames();
+ List intKeys =new List();
+ for(int i =0;i=0;item--)
+ {
+ using (RegistryKey sk = mssqlRegKey.OpenSubKey($@"{intKeys[item]}\Tools\\ClientSetup", writable: false))
+ {
+ if (sk.GetValue("Path") is string value)
+ {
+ string path = Path.Combine(value, "SqlLocalDB.exe");
+ if (File.Exists(path))
+ {
+ return path;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch
+ {
+ SqlClientEventSource.Log.TryTraceEvent(ClassName, EventType.ERR, "No File path exist for SqlLocalDB.exe under Registry key.");
+ }
+ finally
+ {
+ mssqlRegKey?.Close();
+ }
+
+ return null;
+ }
+
+ private string GetConnectionString(string localDbInstance)
+ {
+ StringBuilder localDBConnectionString = new StringBuilder(MAX_LOCAL_DB_CONNECTION_STRING_SIZE + 1);
+ int sizeOfbuffer = localDBConnectionString.Capacity;
+ localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer);
+ return localDBConnectionString.ToString();
+ }
+ private static bool TryGetLocalDBConnectionStringUsingSqlLocalDBExe(string localDbInstance, TimeoutTimer timeout, out string connString, out string error)
+ {
+ connString = null;
+ error = null;
+ try
+ {
+ // Make sure the instance is running first. If it is, this call won't do any harm, aside from
+ // wasting some time.
+ var psi = new ProcessStartInfo(s_sqlLocalDBExe.Value, $"s \"{localDbInstance}\"")
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden
+ };
+
+ var proc = Process.Start(psi);
+
+ if (proc.WaitForExit(milliseconds: timeout.MillisecondsRemainingInt))
+ {
+ psi = new ProcessStartInfo(s_sqlLocalDBExe.Value, $"i \"{localDbInstance}\"")
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden
+ };
+
+ proc = Process.Start(psi);
+
+ if (proc.WaitForExit(milliseconds: timeout.MillisecondsRemainingInt))
+ {
+ var alllines = proc.StandardOutput.ReadToEnd();
+
+ SqlClientEventSource.Log.TryTraceEvent(ClassName, EventType.INFO, $"Called: {s_sqlLocalDBExe.Value} \"{localDbInstance}\"");
+
+ Match match = regex.Match(alllines);
+ if (match.Success)
+ {
+ connString = match.Value.Trim();
+ return true;
+ }
+ else
+ {
+ error = alllines;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.INFO, "No match found for named pipe SqlLocalDB.exe process stdout.");
+ }
+ }
+ }
+ }
+ catch(RegexMatchTimeoutException ex)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Unable to retrieve named pipe SqlLocalDB.exe process stdout, it took longer than the maximum time allowed."+ex?.Message);
+ }
+ catch
+ {
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Unable to start LocalDB.exe process.");
+ }
+
+ return false;
+ }
+
+ internal enum LocalDBErrorState
+ {
+ NO_INSTALLATION, INVALID_CONFIG, NO_SQLUSERINSTANCEDLL_PATH, INVALID_SQLUSERINSTANCEDLL_PATH, NONE
+ }
+
+ ///
+ /// Loads the User Instance dll.
+ ///
+ private bool LoadUserInstanceDll()
+ {
+ // Check in a non thread-safe way if the handle is already set for performance.
+ if (_sqlUserInstanceLibraryHandle != null)
+ {
+ return true;
+ }
+
+ lock (this)
+ {
+ if (_sqlUserInstanceLibraryHandle != null)
+ {
+ return true;
+ }
+ //Get UserInstance Dll path
+ LocalDBErrorState registryQueryErrorState;
+
+ // Get the LocalDB instance dll path from the registry
+ string dllPath = GetUserInstanceDllPath(out registryQueryErrorState);
+
+ // If there was no DLL path found, then there is an error.
+ if (dllPath == null)
+ {
+ SqlClientEventSource.Log.TraceEvent(nameof(LocalDB), EventType.ERR, "User instance DLL path is null.");
+ throw new Exception(MapLocalDBErrorStateToErrorMessage(registryQueryErrorState));
+ }
+
+ // In case the registry had an empty path for dll
+ if (string.IsNullOrWhiteSpace(dllPath))
+ {
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "User instance DLL path is invalid. DLL path = {0}", dllPath);
+ throw new Exception(Strings.SNI_ERROR_55);
+ }
+
+ // Load the dll
+ SafeLibraryHandle libraryHandle = Interop.Kernel32.LoadLibraryExW(dllPath.Trim(), IntPtr.Zero, 0);
+
+ if (libraryHandle.IsInvalid)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Library Handle is invalid. Could not load the dll.");
+ libraryHandle.Dispose();
+ throw new Exception(Strings.SNI_ERROR_56);
+ }
+
+ // Load the procs from the DLLs
+ _startInstanceHandle = Interop.Kernel32.GetProcAddress(libraryHandle, ProcLocalDBStartInstance);
+
+ if (_startInstanceHandle == IntPtr.Zero)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Was not able to load the PROC from DLL. Bad Runtime.");
+ libraryHandle.Dispose();
+ throw new Exception(Strings.SNI_ERROR_57);
+ }
+
+ // Set the delegate the invoke.
+ localDBStartInstanceFunc = (LocalDBStartInstance)Marshal.GetDelegateForFunctionPointer(_startInstanceHandle, typeof(LocalDBStartInstance));
+
+ if (localDBStartInstanceFunc == null)
+ {
+ libraryHandle.Dispose();
+ _startInstanceHandle = IntPtr.Zero;
+ throw new Exception(Strings.SNI_ERROR_57);
+ }
+
+ _sqlUserInstanceLibraryHandle = libraryHandle;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.INFO, "User Instance DLL was loaded successfully.");
+ return true;
+ }
+ }
+
+ ///
+ /// Gets the Local db Named pipe data source if the input is a localDB server.
+ ///
+ /// The data source
+ ///
+ ///
+ internal static string GetLocalDBDataSource(string fullServerName, TimeoutTimer timeout)
+ {
+ string localDBConnectionString = null;
+ string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out bool isBadLocalDBDataSource);
+
+ if (isBadLocalDBDataSource)
+ {
+ return null;
+ }
+
+ else if (!string.IsNullOrEmpty(localDBInstance))
+ {
+ // We have successfully received a localDBInstance which is valid.
+ Debug.Assert(!string.IsNullOrWhiteSpace(localDBInstance), "Local DB Instance name cannot be empty.");
+ localDBConnectionString = LocalDB.GetLocalDBConnectionString(localDBInstance, timeout);
+
+ if (fullServerName == null)
+ {
+ // The Last error is set in LocalDB.GetLocalDBConnectionString. We don't need to set Last here.
+ return null;
+ }
+ }
+ return localDBConnectionString;
+ }
+
+ ///
+ /// Retrieves the part of the sqlUserInstance.dll from the registry
+ ///
+ /// In case the dll path is not found, the error is set here.
+ ///
+ private string GetUserInstanceDllPath(out LocalDBErrorState errorState)
+ {
+ using (TrySNIEventScope.Create(nameof(LocalDB)))
+ {
+ string dllPath = null;
+ using (RegistryKey key = Registry.LocalMachine.OpenSubKey(LocalDBInstalledVersionRegistryKey))
+ {
+ if (key == null)
+ {
+ errorState = LocalDBErrorState.NO_INSTALLATION;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "No installation found.");
+ return null;
+ }
+
+ Version zeroVersion = new Version();
+
+ Version latestVersion = zeroVersion;
+
+ foreach (string subKey in key.GetSubKeyNames())
+ {
+ Version currentKeyVersion;
+
+ if (!Version.TryParse(subKey, out currentKeyVersion))
+ {
+ errorState = LocalDBErrorState.INVALID_CONFIG;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Invalid Configuration.");
+ return null;
+ }
+
+ if (latestVersion.CompareTo(currentKeyVersion) < 0)
+ {
+ latestVersion = currentKeyVersion;
+ }
+ }
+
+ // If no valid versions are found, then error out
+ if (latestVersion.Equals(zeroVersion))
+ {
+ errorState = LocalDBErrorState.INVALID_CONFIG;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Invalid Configuration.");
+ return null;
+ }
+
+ // Use the latest version to get the DLL path
+ using (RegistryKey latestVersionKey = key.OpenSubKey(latestVersion.ToString()))
+ {
+
+ object instanceAPIPathRegistryObject = latestVersionKey.GetValue(InstanceAPIPathValueName);
+
+ if (instanceAPIPathRegistryObject == null)
+ {
+ errorState = LocalDBErrorState.NO_SQLUSERINSTANCEDLL_PATH;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "No SQL user instance DLL. Instance API Path Registry Object Error.");
+ return null;
+ }
+
+ RegistryValueKind valueKind = latestVersionKey.GetValueKind(InstanceAPIPathValueName);
+
+ if (valueKind != RegistryValueKind.String)
+ {
+ errorState = LocalDBErrorState.INVALID_SQLUSERINSTANCEDLL_PATH;
+ SqlClientEventSource.Log.TryTraceEvent(nameof(LocalDB), EventType.ERR, "Invalid SQL user instance DLL path. Registry value kind mismatch.");
+ return null;
+ }
+
+ dllPath = (string)instanceAPIPathRegistryObject;
+
+ errorState = LocalDBErrorState.NONE;
+ return dllPath;
+ }
+ }
+ }
+ }
+
+ internal static string MapLocalDBErrorStateToErrorMessage(LocalDBErrorState errorState)
+ {
+ switch (errorState)
+ {
+ case LocalDBErrorState.NO_INSTALLATION:
+ return Strings.SNI_ERROR_52;
+ case LocalDBErrorState.INVALID_CONFIG:
+ return Strings.SNI_ERROR_53;
+ case LocalDBErrorState.NO_SQLUSERINSTANCEDLL_PATH:
+ return Strings.SNI_ERROR_54;
+ case LocalDBErrorState.INVALID_SQLUSERINSTANCEDLL_PATH:
+ return Strings.SNI_ERROR_55;
+ case LocalDBErrorState.NONE:
+ return Strings.SNI_ERROR_50;
+ default:
+ return Strings.SNI_ERROR_53;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs
index 8d276cd285..9844a96da3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs
@@ -76,7 +76,6 @@ override protected bool ReleaseHandle()
{
if (TdsEnums.SNI_SUCCESS == _sniStatus)
{
- LocalDBAPI.ReleaseDLLHandles();
SNINativeMethodWrapper.SNITerminate();
}
base.handle = IntPtr.Zero;
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 0c06e6cf47..3987073af8 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs
@@ -23,8 +23,7 @@ private enum InfoType
private static readonly string s_commandPrompt = "cmd.exe";
private static readonly string s_sqlLocalDbInfo = @$"/c SqlLocalDb info {DataTestUtility.LocalDbAppName}";
private static readonly string s_startLocalDbCommand = @$"/c SqlLocalDb start {DataTestUtility.LocalDbAppName}";
- private static readonly string s_localDbNamedPipeConnectionString = @$"server={GetLocalDbNamedPipe()}";
-
+
#region LocalDbTests
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
@@ -100,7 +99,7 @@ public static void SqlLocalDbSharedInstanceConnectionTest()
[ActiveIssue(20245)] //pending pipeline configuration
public static void SqlLocalDbNamedPipeConnectionTest()
{
- ConnectionTest(s_localDbNamedPipeConnectionString);
+ ConnectionTest(@$"server={GetLocalDbNamedPipe()}");
}
[Fact]
@@ -110,7 +109,7 @@ public static void LocalDBNamedPipeEncryptionNotSupportedTest()
{
// Encryption is not supported by SQL Local DB.
// But connection should succeed as encryption is disabled by driver.
- ConnectionWithEncryptionTest(s_localDbNamedPipeConnectionString);
+ ConnectionWithEncryptionTest(@$"server={GetLocalDbNamedPipe()}");
}
[Fact]
@@ -118,11 +117,22 @@ public static void LocalDBNamedPipeEncryptionNotSupportedTest()
[ActiveIssue(20245)] //pending pipeline configuration
public static void LocalDBNamepipeMarsTest()
{
- ConnectionWithMarsTest(s_localDbNamedPipeConnectionString);
+ ConnectionWithMarsTest(@$"server={GetLocalDbNamedPipe()}");
}
#endregion
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ [Fact]
+ public static void SqlLocalDbNotSupportedException()
+ {
+ Assert.Throws(() =>
+ {
+ using SqlConnection connection = new(s_localDbConnectionString);
+ connection.Open();
+ });
+ }
+
private static void ConnectionWithMarsTest(string connectionString)
{
SqlConnectionStringBuilder builder = new(connectionString)