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 index 6ec654e3bf..1b4679bfe4 100644 --- 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 @@ -22,14 +22,13 @@ private static IntPtr UserInstanceDLLHandle if (s_userInstanceDLLHandle == IntPtr.Zero) { SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); - if(s_userInstanceDLLHandle != IntPtr.Zero) + if (s_userInstanceDLLHandle != IntPtr.Zero) { SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained"); } else { - SNINativeMethodWrapper.SNI_Error sniError; - SNINativeMethodWrapper.SNIGetLastError(out sniError); + SNINativeMethodWrapper.SNIGetLastError(out SNINativeMethodWrapper.SNI_Error sniError); throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError); } } 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 4f3fd13dce..ba2371c232 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 @@ -10,25 +10,37 @@ namespace Microsoft.Data { internal static partial class LocalDBAPI { - private const string const_localDbPrefix = @"(localdb)\"; + 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); // check if name is in format (localdb)\ and return instance name if it is + // localDB can also have a format of np:\\.\pipe\LOCALDB#\tsql\query internal static string GetLocalDbInstanceNameFromServerName(string serverName) { - if (serverName == null) - return null; - serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes - if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase)) - return null; - string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim(); - if (instanceName.Length == 0) - return null; - else - return instanceName; + if (serverName is not null) + { + // it can start with spaces if specified in quotes + // Memory allocation is reduced by using ReadOnlySpan + ReadOnlySpan input = serverName.AsSpan().Trim(); + if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + input = input.Slice(LocalDbPrefix.Length); + if (!input.IsEmpty) + { + return input.ToString(); + } + } + else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return input.ToString(); + } + + } + return null; } } } 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 0af8441333..501a68e401 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 @@ -342,8 +342,7 @@ internal SNIError GetLastError() private static string GetLocalDBDataSource(string fullServerName, out bool error) { string localDBConnectionString = null; - bool isBadLocalDBDataSource; - string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out isBadLocalDBDataSource); + string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out bool isBadLocalDBDataSource); if (isBadLocalDBDataSource) { @@ -381,6 +380,7 @@ internal class DataSource 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"; @@ -482,11 +482,9 @@ private void PopulateProtocol() 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)) { @@ -507,6 +505,11 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) error = true; } } + else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) + { + instanceName = input.Trim().ToString(); + } + return instanceName; } 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 f1f4c955af..e3b4ea2ef4 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 @@ -16,11 +16,11 @@ namespace Microsoft.Data { - internal static class LocalDBAPI { - const string const_localDbPrefix = @"(localdb)\"; - const string const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; + private const string LocalDbPrefix = @"(localdb)\"; + private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; + const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; static PermissionSet _fullTrust = null; static bool _partialTrustFlagChecked = false; @@ -30,18 +30,27 @@ internal static class LocalDBAPI // check if name is in format (localdb)\ and return instance name if it is internal static string GetLocalDbInstanceNameFromServerName(string serverName) { - if (serverName == null) - return null; - serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes - if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase)) - return null; - string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim(); - if (instanceName.Length == 0) - return null; - else - return instanceName; - } + if (serverName is not null) + { + // it can start with spaces if specified in quotes + // Memory allocation is reduced by using ReadOnlySpan + ReadOnlySpan input = serverName.AsSpan().Trim(); + if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + input = input.Slice(LocalDbPrefix.Length); + if (!input.IsEmpty) + { + return input.ToString(); + } + } + else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return input.ToString(); + } + } + return null; + } internal static void ReleaseDLLHandles() { @@ -261,7 +270,7 @@ internal static void DemandLocalDBPermissions() { if (!_partialTrustFlagChecked) { - object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(const_partialTrustFlagKey); + object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey); if (partialTrustFlagValue != null && partialTrustFlagValue is bool) { _partialTrustAllowed = (bool)partialTrustFlagValue; 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 ecbdb98a48..63edb9f926 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -1,7 +1,9 @@ // 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.Collections.Generic; +using System; +using System.Diagnostics; +using System.Threading; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -13,14 +15,18 @@ public static class LocalDBTest private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}"; private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." }; private static readonly string s_badConnectionString = $@"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;"; + 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()}"; - static string LocalDbName = DataTestUtility.LocalDbAppName; #region LocalDbTests [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP [ConditionalFact(nameof(IsLocalDBEnvironmentSet))] public static void SqlLocalDbConnectionTest() { ConnectionTest(s_localDbConnectionString); + ConnectionTest(s_localDbNamedPipeConnectionString); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP @@ -30,6 +36,7 @@ public static void LocalDBEncryptionNotSupportedTest() // Encryption is not supported by SQL Local DB. // But connection should succeed as encryption is disabled by driver. ConnectionWithEncryptionTest(s_localDbConnectionString); + ConnectionWithEncryptionTest(s_localDbNamedPipeConnectionString); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP @@ -37,6 +44,7 @@ public static void LocalDBEncryptionNotSupportedTest() public static void LocalDBMarsTest() { ConnectionWithMarsTest(s_localDbConnectionString); + ConnectionWithMarsTest(s_localDbNamedPipeConnectionString); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP @@ -123,5 +131,39 @@ private static void OpenConnection(string connString) var result = command.ExecuteScalar(); Assert.NotNull(result); } + + private static string GetLocalDbNamedPipe() + { + string state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "state"); + while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase)) + { + state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_startLocalDbCommand, "state"); + Thread.Sleep(2000); + } + return ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "pipeName"); + } + + private static string ExecuteLocalDBCommandProcess(string filename, string arguments, string infoType) + { + ProcessStartInfo sInfo = new() + { + FileName = filename, + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + string[] lines = Process.Start(sInfo).StandardOutput.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + if (infoType.Equals("state")) + { + return lines[5].Split(':')[1].Trim(); + } + else if (infoType.Equals("pipeName")) + { + return lines[7].Split(new string[] { "Instance pipe name:" }, StringSplitOptions.None)[1].Trim(); + } + return null; + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json index 043b73c4c9..b49cf6a9ec 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json @@ -16,6 +16,7 @@ "AzureKeyVaultClientSecret": "", "SupportsIntegratedSecurity": true, "LocalDbAppName": "", + "LocalDbSharedInstanceName":"", "SupportsFileStream": false, "FileStreamDirectory": "", "UseManagedSNIOnWindows": false,