From 659ba229c7b61e92b35a12d1326c319ae7a573fb Mon Sep 17 00:00:00 2001 From: JRahnama Date: Mon, 6 Dec 2021 13:40:03 -0800 Subject: [PATCH 1/2] address issue with localdb named pipe --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 5 +-- .../Microsoft/Data/SqlClient/LocalDBAPI.cs | 29 +++++++----- .../Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 19 ++++++-- .../Microsoft/Data/SqlClient/LocalDBAPI.cs | 31 +++++++------ .../SQL/LocalDBTest/LocalDBTest.cs | 45 ++++++++++++++++++- 5 files changed, 97 insertions(+), 32 deletions(-) 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..6506b55871 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 @@ -5,30 +5,37 @@ using System; using System.Runtime.InteropServices; using System.Text; +using System.Text.RegularExpressions; namespace Microsoft.Data { internal static partial class LocalDBAPI { - private const string const_localDbPrefix = @"(localdb)\"; + private const string LocalDbPrefix = @"(localdb)\"; + private const string LocalDbPrefix_namedInstance = @"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; + string instanceName = null; + bool isLocalDb = serverName.StartsWith(LocalDbPrefix) || serverName.StartsWith(LocalDbPrefix_namedInstance); + if (isLocalDb) + { + if (serverName.StartsWith(LocalDbPrefix)) + { + instanceName = serverName.Substring(LocalDbPrefix.Length).Trim(); + } + else + { + instanceName = serverName; + } + } + return instanceName; } } } 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..6cfbd94370 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_NamedInstance = @"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,19 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) error = true; } } + if (input.StartsWith(LocalDbHost_NamedInstance.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) + { + 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; + } + } + 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..e9c3ee5685 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 @@ -19,8 +19,9 @@ 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_namedInstance = @"np:\\.\pipe\LOCALDB#"; + const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; static PermissionSet _fullTrust = null; static bool _partialTrustFlagChecked = false; @@ -30,16 +31,20 @@ 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; + string instanceName = null; + bool isLocalDb = serverName.StartsWith(LocalDbPrefix) || serverName.StartsWith(LocalDbPrefix_namedInstance); + if (isLocalDb) + { + if (serverName.StartsWith(LocalDbPrefix)) + { + instanceName = serverName.Substring(LocalDbPrefix.Length).Trim(); + } + else + { + instanceName = serverName; + } + } + return instanceName; } @@ -261,7 +266,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..9173cba49c 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,6 +15,10 @@ 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 @@ -21,6 +27,7 @@ public static class LocalDBTest public static void SqlLocalDbConnectionTest() { ConnectionTest(s_localDbConnectionString); + ConnectionTest(s_localDbNamedPipeConnectionString); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP @@ -30,6 +37,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 +45,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 +132,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; + } } } From 6af0b8e219f1d2f1b41e2d810425309ed165b59c Mon Sep 17 00:00:00 2001 From: JRahnama Date: Fri, 7 Jan 2022 09:46:27 -0800 Subject: [PATCH 2/2] review comments --- .../src/Microsoft/Data/SqlClient/LocalDBAPI.cs | 10 +++++----- .../src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 14 +++----------- .../src/Microsoft/Data/SqlClient/LocalDBAPI.cs | 9 +++++---- .../ManualTests/SQL/LocalDBTest/LocalDBTest.cs | 1 - .../config.default.json | 1 + 5 files changed, 14 insertions(+), 21 deletions(-) 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 6506b55871..0cd5e31aac 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 @@ -5,14 +5,13 @@ using System; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; namespace Microsoft.Data { internal static partial class LocalDBAPI { private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_namedInstance = @"np:\\.\pipe\LOCALDB#"; + private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] @@ -23,16 +22,17 @@ internal static partial class LocalDBAPI internal static string GetLocalDbInstanceNameFromServerName(string serverName) { string instanceName = null; - bool isLocalDb = serverName.StartsWith(LocalDbPrefix) || serverName.StartsWith(LocalDbPrefix_namedInstance); + serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes + bool isLocalDb = serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase) || serverName.StartsWith(LocalDbPrefix_NP, StringComparison.OrdinalIgnoreCase); if (isLocalDb) { - if (serverName.StartsWith(LocalDbPrefix)) + if (serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase)) { instanceName = serverName.Substring(LocalDbPrefix.Length).Trim(); } else { - instanceName = serverName; + instanceName = serverName.TrimEnd(); } } return instanceName; 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 6cfbd94370..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 @@ -380,7 +380,7 @@ internal class DataSource private const string Slash = @"/"; private const string PipeToken = "pipe"; private const string LocalDbHost = "(localdb)"; - private const string LocalDbHost_NamedInstance = @"np:\\.\pipe\LOCALDB#"; + private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#"; private const string NamedPipeInstanceNameHeader = "mssql$"; private const string DefaultPipeName = "sql\\query"; @@ -505,17 +505,9 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) error = true; } } - if (input.StartsWith(LocalDbHost_NamedInstance.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) + else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) { - 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; - } + 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 e9c3ee5685..32f84e9131 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 @@ -20,7 +20,7 @@ namespace Microsoft.Data internal static class LocalDBAPI { private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_namedInstance = @"np:\\.\pipe\LOCALDB#"; + private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; static PermissionSet _fullTrust = null; @@ -32,16 +32,17 @@ internal static class LocalDBAPI internal static string GetLocalDbInstanceNameFromServerName(string serverName) { string instanceName = null; - bool isLocalDb = serverName.StartsWith(LocalDbPrefix) || serverName.StartsWith(LocalDbPrefix_namedInstance); + serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes + bool isLocalDb = serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase) || serverName.StartsWith(LocalDbPrefix_NP, StringComparison.OrdinalIgnoreCase); if (isLocalDb) { - if (serverName.StartsWith(LocalDbPrefix)) + if (serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase)) { instanceName = serverName.Substring(LocalDbPrefix.Length).Trim(); } else { - instanceName = serverName; + instanceName = serverName.TrimEnd(); } } return instanceName; 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 9173cba49c..63edb9f926 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -20,7 +20,6 @@ public static class LocalDBTest 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))] 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,