diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md
index 1943ef0bbc..95595da81f 100644
--- a/BUILDGUIDE.md
+++ b/BUILDGUIDE.md
@@ -118,6 +118,7 @@ Manual Tests require the below setup to run:
|AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ |
|AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ |
|LocalDbAppName | (Optional) If Local Db Testing is supported, this property configures the name of Local DB App instance available in client environment. Empty string value disables Local Db testing. | Name of Local Db App to connect to.|
+ |LocalDbSharedInstanceName | (Optional) If LocalDB testing is supported and the instance is shared, this property configures the name of the shared instance of LocalDB to connect to. | Name of shared instance of LocalDB. |
|SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`|
|FileStreamDirectory | (Optional) If File Stream is enabled on SQL Server, pass local directory path to be used for setting up File Stream enabled database. | `D:\\escaped\\absolute\\path\\to\\directory\\` |
|UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`|
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 24c8609876..0af8441333 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
@@ -9,6 +9,7 @@
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
+using System.Text.RegularExpressions;
namespace Microsoft.Data.SqlClient.SNI
{
@@ -142,7 +143,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode)
/// Used for DNS Cache
/// Used for DNS Cache
/// SNI handle
- internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer,
+ internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer,
bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
{
instanceName = new byte[1];
@@ -415,6 +416,7 @@ internal enum Protocol { TCP, NP, None, Admin };
private string _workingDataSource;
private string _dataSourceAfterTrimmingProtocol;
+
internal bool IsBadDataSource { get; private set; } = false;
internal bool IsSsrpRequired { get; private set; } = false;
@@ -472,31 +474,39 @@ private void PopulateProtocol()
}
}
+ // 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;
- string workingDataSource = dataSource.ToLowerInvariant();
-
- string[] tokensByBackSlash = workingDataSource.Split(BackSlashCharacter);
-
+ // ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
+ ReadOnlySpan input = dataSource.AsSpan().TrimStart();
error = false;
- // All LocalDb endpoints are of the format host\instancename where host is always (LocalDb) (case-insensitive)
- if (tokensByBackSlash.Length == 2 && LocalDbHost.Equals(tokensByBackSlash[0].TrimStart()))
+ // NetStandard 2.0 does not support passing a string to ReadOnlySpan
+ if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
- if (!string.IsNullOrWhiteSpace(tokensByBackSlash[1]))
+ // 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)
{
- instanceName = tokensByBackSlash[1].Trim();
+ 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;
- return null;
}
}
-
return instanceName;
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index b60b10e5d8..c3aa7b0486 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
@@ -39,6 +39,7 @@ public static class DataTestUtility
public static readonly string AKVClientId = null;
public static readonly string AKVClientSecret = null;
public static readonly string LocalDbAppName = null;
+ public static readonly string LocalDbSharedInstanceName = null;
public static List AEConnStrings = new List();
public static List AEConnStringsSetup = new List();
public static bool EnclaveEnabled { get; private set; } = false;
@@ -84,6 +85,7 @@ static DataTestUtility()
AADServicePrincipalId = c.AADServicePrincipalId;
AADServicePrincipalSecret = c.AADServicePrincipalSecret;
LocalDbAppName = c.LocalDbAppName;
+ LocalDbSharedInstanceName = c.LocalDbSharedInstanceName;
SupportsIntegratedSecurity = c.SupportsIntegratedSecurity;
FileStreamDirectory = c.FileStreamDirectory;
EnclaveEnabled = c.EnclaveEnabled;
@@ -441,7 +443,8 @@ public static void DropDatabase(SqlConnection sqlConnection, string dbName)
cmd.ExecuteNonQuery();
}
- public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim());
+ public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim()) && IsIntegratedSecuritySetup();
+ public static bool IsLocalDbSharedInstanceSetup() => !string.IsNullOrEmpty(LocalDbSharedInstanceName?.Trim()) && IsIntegratedSecuritySetup();
public static bool IsIntegratedSecuritySetup() => SupportsIntegratedSecurity;
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 06defb1125..c2512259c4 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,7 @@
// 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 Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -9,49 +9,87 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
public static class LocalDBTest
{
private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled();
+ private static bool IsLocalDbSharedInstanceSet() => DataTestUtility.IsLocalDbSharedInstanceSetup();
+ 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;";
+ static string LocalDbName = DataTestUtility.LocalDbAppName;
+ #region LocalDbTests
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
- public static void LocalDBConnectionTest()
+ public static void SqlLocalDbConnectionTest()
{
- SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}");
- builder.IntegratedSecurity = true;
- builder.ConnectTimeout = 2;
- OpenConnection(builder.ConnectionString);
+ ConnectionTest(s_localDbConnectionString);
}
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBMarsTest()
{
- SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}");
- builder.IntegratedSecurity = true;
- builder.MultipleActiveResultSets = true;
- builder.ConnectTimeout = 2;
- OpenConnection(builder.ConnectionString);
+ ConnectionWithMarsTest(s_localDbConnectionString);
}
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
- public static void InvalidDBTest()
+ public static void InvalidLocalDBTest()
{
- using (var connection = new SqlConnection(@$"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;"))
+ using var connection = new SqlConnection(s_badConnectionString);
+ DataTestUtility.AssertThrowsWrapper(() => connection.Open());
+ }
+ #endregion
+
+ #region SharedLocalDb tests
+ [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
+ [ConditionalFact(nameof(IsLocalDbSharedInstanceSet))]
+ public static void SharedLocalDbMarsTest()
+ {
+ foreach (string connectionString in s_sharedLocalDbInstances)
{
- DataTestUtility.AssertThrowsWrapper(() => connection.Open());
+ ConnectionWithMarsTest(connectionString);
}
}
- private static void OpenConnection(string connString)
+ [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
+ [ConditionalFact(nameof(IsLocalDbSharedInstanceSet))]
+ public static void SqlLocalDbSharedInstanceConnectionTest()
{
- using (SqlConnection connection = new SqlConnection(connString))
+ foreach (string connectionString in s_sharedLocalDbInstances)
{
- connection.Open();
- using (SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection))
- {
- var result = command.ExecuteScalar();
- Assert.NotNull(result);
- }
+ ConnectionTest(connectionString);
}
}
+ #endregion
+
+ private static void ConnectionWithMarsTest(string connectionString)
+ {
+ SqlConnectionStringBuilder builder = new(connectionString)
+ {
+ IntegratedSecurity = true,
+ MultipleActiveResultSets = true,
+ ConnectTimeout = 2,
+ Encrypt = false
+ };
+ OpenConnection(builder.ConnectionString);
+ }
+ private static void ConnectionTest(string connectionString)
+ {
+ SqlConnectionStringBuilder builder = new(connectionString)
+ {
+ IntegratedSecurity = true,
+ ConnectTimeout = 2,
+ Encrypt = false
+ };
+ OpenConnection(builder.ConnectionString);
+ }
+
+ private static void OpenConnection(string connString)
+ {
+ using SqlConnection connection = new(connString);
+ connection.Open();
+ using SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection);
+ var result = command.ExecuteScalar();
+ Assert.NotNull(result);
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs
index ce1aaaca86..d3413622b2 100644
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs
+++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs
@@ -24,6 +24,7 @@ public class Config
public string AzureKeyVaultClientId = null;
public string AzureKeyVaultClientSecret = null;
public string LocalDbAppName = null;
+ public string LocalDbSharedInstanceName = null;
public bool EnclaveEnabled = false;
public bool TracingEnabled = false;
public bool SupportsIntegratedSecurity = false;
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 6b4e45ef8f..69bacdc3f9 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": "",
+ "SupportsFileStream": false,
"FileStreamDirectory": "",
"UseManagedSNIOnWindows": false,
"DNSCachingConnString": "",