diff --git a/ElasticDatabaseTools.sln b/ElasticDatabaseTools.sln
index 8b388cd..b6a9025 100644
--- a/ElasticDatabaseTools.sln
+++ b/ElasticDatabaseTools.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29709.97
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.SqlDatabase.ElasticScale.Client", "Src\ElasticScale.Client\Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj", "{4C3B3EC4-5702-469E-800E-313FB27A0A2B}"
EndProject
@@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.SqlDatabase
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests", "Test\ElasticScale.Query.UnitTests\Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj", "{74CEE77F-D2C7-4B8B-9411-8F97F4E803FA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElasticScaleStarterKit", "Samples\ElasticScaleStarterKit\ElasticScaleStarterKit.csproj", "{115A0283-AC42-4D37-97F2-106D168E04D2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElasticScaleStarterKit", "Samples\ElasticScaleStarterKit\ElasticScaleStarterKit.csproj", "{115A0283-AC42-4D37-97F2-106D168E04D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMultiTenant", "Samples\EFMultiTenant\EntityFrameworkMultiTenant.csproj", "{BC17F3EF-FEB4-4186-9342-E2D18F1E56AB}"
EndProject
@@ -21,6 +21,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElasticDapper", "Samples\Da
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShardSqlCmd", "Samples\ShardSqlCmd\ShardSqlCmd.csproj", "{B210D6E5-7171-4117-9C77-3F2CB59D04D8}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{F7A2F005-3B53-495D-B0EC-CE41FAB4DC75}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9C78C56E-7738-4D3B-9722-67A4CB028A75}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{A0CBF252-A093-4448-BC10-1A1EF10DB64E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Cover|Any CPU = Cover|Any CPU
@@ -143,6 +149,17 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {4C3B3EC4-5702-469E-800E-313FB27A0A2B} = {A0CBF252-A093-4448-BC10-1A1EF10DB64E}
+ {9336E9E7-19BF-49AC-92E3-19FA6B98921E} = {9C78C56E-7738-4D3B-9722-67A4CB028A75}
+ {BEA6F911-BA98-462C-99AF-3B0595DE2307} = {9C78C56E-7738-4D3B-9722-67A4CB028A75}
+ {74CEE77F-D2C7-4B8B-9411-8F97F4E803FA} = {9C78C56E-7738-4D3B-9722-67A4CB028A75}
+ {115A0283-AC42-4D37-97F2-106D168E04D2} = {F7A2F005-3B53-495D-B0EC-CE41FAB4DC75}
+ {BC17F3EF-FEB4-4186-9342-E2D18F1E56AB} = {F7A2F005-3B53-495D-B0EC-CE41FAB4DC75}
+ {8EB66613-D5A2-4683-B91A-6AF904FD6B70} = {F7A2F005-3B53-495D-B0EC-CE41FAB4DC75}
+ {AC76C04B-881E-4CB9-B491-4D19B68459F1} = {F7A2F005-3B53-495D-B0EC-CE41FAB4DC75}
+ {B210D6E5-7171-4117-9C77-3F2CB59D04D8} = {F7A2F005-3B53-495D-B0EC-CE41FAB4DC75}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {24237160-521B-4CD0-87AB-8994A5E50BE2}
EndGlobalSection
diff --git a/Samples/Dapper/DataClasses.cs b/Samples/Dapper/Blog.cs
similarity index 86%
rename from Samples/Dapper/DataClasses.cs
rename to Samples/Dapper/Blog.cs
index 6abf03e..1c3cae1 100644
--- a/Samples/Dapper/DataClasses.cs
+++ b/Samples/Dapper/Blog.cs
@@ -1,15 +1,15 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace ElasticDapper
-{
- // Let's use the standard blogging class
- public class Blog
- {
- public int BlogId { get; set; }
-
- public string Name { get; set; }
-
- public string Url { get; set; }
- }
-}
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace ElasticDapper
+{
+ // Let's use the standard blogging class
+ public class Blog
+ {
+ public long BlogId { get; set; }
+
+ public string Name { get; set; }
+
+ public string Url { get; set; }
+ }
+}
diff --git a/Samples/Dapper/ElasticDapper.csproj b/Samples/Dapper/ElasticDapper.csproj
index d04f160..0ee4f8a 100644
--- a/Samples/Dapper/ElasticDapper.csproj
+++ b/Samples/Dapper/ElasticDapper.csproj
@@ -5,14 +5,13 @@
-
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Dapper/LICENSE b/Samples/Dapper/LICENSE
index b8b569d..06f7e30 100644
--- a/Samples/Dapper/LICENSE
+++ b/Samples/Dapper/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Microsoft
+Copyright (c) 2024 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Samples/Dapper/Program.cs b/Samples/Dapper/Program.cs
index dafc787..051f663 100644
--- a/Samples/Dapper/Program.cs
+++ b/Samples/Dapper/Program.cs
@@ -3,10 +3,10 @@
using System;
using System.Collections.Generic;
-using System.Data.SqlClient;
using Dapper;
using DapperExtensions;
using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement;
+using Microsoft.Data.SqlClient;
////////////////////////////////////////////////////////////////////////////////////////
// This sample illustrates the adjustments that need to be made to use Dapper
@@ -28,9 +28,8 @@ internal class Program
private static string s_shardmapmgrdb = "[YourShardMapManagerDatabaseName]";
private static string s_shard1 = "[YourShard01DatabaseName]";
private static string s_shard2 = "[YourShard02DatabaseName]";
- private static string s_userName = "YourUserName";
- private static string s_password = "YourPassword";
private static string s_applicationName = "ESC_Dapv1.0";
+ private static SqlAuthenticationMethod s_authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryDefault;
// Just two tenants for now.
// Those we will allocate to shards.
@@ -39,19 +38,14 @@ internal class Program
public static void Main()
{
- SqlConnectionStringBuilder connStrBldr = new SqlConnectionStringBuilder
- {
- UserID = s_userName,
- Password = s_password,
- ApplicationName = s_applicationName
- };
+ SqlConnectionStringBuilder connStrBldr = GetConnectionStringBuilder();
// Bootstrap the shard map manager, register shards, and store mappings of tenants to shards
// Note that you can keep working with existing shard maps. There is no need to
// re-create and populate the shard map from scratch every time.
Sharding shardingLayer = new Sharding(s_server, s_shardmapmgrdb, connStrBldr.ConnectionString);
- shardingLayer.RegisterNewShard(s_server, s_shard1, connStrBldr.ConnectionString, s_tenantId1);
- shardingLayer.RegisterNewShard(s_server, s_shard2, connStrBldr.ConnectionString, s_tenantId2);
+ shardingLayer.RegisterNewShard(s_server, s_shard1, s_tenantId1);
+ shardingLayer.RegisterNewShard(s_server, s_shard2, s_tenantId2);
// Create schema on each shard.
foreach (string shard in new[] {s_shard1, s_shard2})
@@ -67,89 +61,74 @@ public static void Main()
Console.Write("Enter a name for a new Blog: ");
var name = Console.ReadLine();
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+ using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
+ key: s_tenantId1,
+ connectionString: connStrBldr.ConnectionString,
+ options: ConnectionOptions.Validate))
{
- using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
- key: s_tenantId1,
- connectionString: connStrBldr.ConnectionString,
- options: ConnectionOptions.Validate))
- {
- var blog = new Blog { Name = name };
- sqlconn.Insert(blog);
- }
- });
+ var blog = new Blog { Name = name };
+ sqlconn.Insert(blog);
+ }
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+ using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
+ key: s_tenantId1,
+ connectionString: connStrBldr.ConnectionString,
+ options: ConnectionOptions.Validate))
{
- using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
- key: s_tenantId1,
- connectionString: connStrBldr.ConnectionString,
- options: ConnectionOptions.Validate))
+ // Display all Blogs for tenant 1
+ IEnumerable result = sqlconn.Query(@"
+ SELECT *
+ FROM Blog
+ ORDER BY Name");
+
+ Console.WriteLine("All blogs for tenant id {0}:", s_tenantId1);
+ foreach (var item in result)
{
- // Display all Blogs for tenant 1
- IEnumerable result = sqlconn.Query(@"
- SELECT *
- FROM Blog
- ORDER BY Name");
-
- Console.WriteLine("All blogs for tenant id {0}:", s_tenantId1);
- foreach (var item in result)
- {
- Console.WriteLine(item.Name);
- }
+ Console.WriteLine(item.Name);
}
- });
+ }
// Do work for tenant 2 :-)
// Here I am going to illustrate how to integrate
// with DapperExtensions which saves us the T-SQL
//
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+ using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
+ key: s_tenantId2,
+ connectionString: connStrBldr.ConnectionString,
+ options: ConnectionOptions.Validate))
{
- using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
- key: s_tenantId2,
- connectionString: connStrBldr.ConnectionString,
- options: ConnectionOptions.Validate))
+ // Display all Blogs for tenant 2
+ IEnumerable result = sqlconn.GetList();
+ Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2);
+ foreach (var item in result)
{
- // Display all Blogs for tenant 2
- IEnumerable result = sqlconn.GetList();
- Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2);
- foreach (var item in result)
- {
- Console.WriteLine(item.Name);
- }
+ Console.WriteLine(item.Name);
}
- });
+ }
// Create and save a new Blog
Console.Write("Enter a name for a new Blog: ");
var name2 = Console.ReadLine();
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+ using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
+ key: s_tenantId2,
+ connectionString: connStrBldr.ConnectionString,
+ options: ConnectionOptions.Validate))
{
- using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(
- key: s_tenantId2,
- connectionString: connStrBldr.ConnectionString,
- options: ConnectionOptions.Validate))
- {
- var blog = new Blog { Name = name2 };
- sqlconn.Insert(blog);
- }
- });
+ var blog = new Blog { Name = name2 };
+ sqlconn.Insert(blog);
+ }
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+ using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(s_tenantId2, connStrBldr.ConnectionString, ConnectionOptions.Validate))
{
- using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(s_tenantId2, connStrBldr.ConnectionString, ConnectionOptions.Validate))
+ // Display all Blogs for tenant 2
+ IEnumerable result = sqlconn.GetList();
+ Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2);
+ foreach (var item in result)
{
- // Display all Blogs for tenant 2
- IEnumerable result = sqlconn.GetList();
- Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2);
- foreach (var item in result)
- {
- Console.WriteLine(item.Name);
- }
+ Console.WriteLine(item.Name);
}
- });
+ }
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
@@ -157,14 +136,10 @@ FROM Blog
private static void CreateSchema(string shardName)
{
- SqlConnectionStringBuilder connStrBldr = new SqlConnectionStringBuilder
- {
- UserID = s_userName,
- Password = s_password,
- ApplicationName = s_applicationName,
- DataSource = s_server,
- InitialCatalog = shardName
- };
+ SqlConnectionStringBuilder connStrBldr = GetConnectionStringBuilder();
+
+ connStrBldr.DataSource = s_server;
+ connStrBldr.InitialCatalog = shardName;
using (SqlConnection conn = new SqlConnection(connStrBldr.ToString()))
{
@@ -178,5 +153,19 @@ [Url] [nvarchar](max) NULL,
)");
}
}
+
+ private static SqlConnectionStringBuilder GetConnectionStringBuilder()
+ {
+ var connBuilder = new SqlConnectionStringBuilder
+ {
+ Authentication = s_authenticationMethod,
+ ApplicationName = s_applicationName,
+ CommandTimeout = 60,
+ ConnectTimeout = 60,
+ TrustServerCertificate = true
+ };
+
+ return connBuilder;
+ }
}
}
diff --git a/Samples/Dapper/Sharding.cs b/Samples/Dapper/Sharding.cs
index a7625cf..4e751ff 100644
--- a/Samples/Dapper/Sharding.cs
+++ b/Samples/Dapper/Sharding.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System.Data.SqlClient;
using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement;
+using Microsoft.Data.SqlClient;
namespace ElasticDapper
{
@@ -25,41 +25,41 @@ public Sharding(string smmserver, string smmdatabase, string smmconnstr)
ShardMapManager smm;
if (!ShardMapManagerFactory.TryGetSqlShardMapManager(connStrBldr.ConnectionString, ShardMapManagerLoadPolicy.Lazy, out smm))
{
- this.ShardMapManager = ShardMapManagerFactory.CreateSqlShardMapManager(connStrBldr.ConnectionString);
+ ShardMapManager = ShardMapManagerFactory.CreateSqlShardMapManager(connStrBldr.ConnectionString);
}
else
{
- this.ShardMapManager = smm;
+ ShardMapManager = smm;
}
ListShardMap sm;
if (!ShardMapManager.TryGetListShardMap("ElasticScaleWithDapper", out sm))
{
- this.ShardMap = ShardMapManager.CreateListShardMap("ElasticScaleWithDapper");
+ ShardMap = ShardMapManager.CreateListShardMap("ElasticScaleWithDapper");
}
else
{
- this.ShardMap = sm;
+ ShardMap = sm;
}
}
// Enter a new shard - i.e. an empty database - to the shard map, allocate a first tenant to it
- public void RegisterNewShard(string server, string database, string connstr, int key)
+ public void RegisterNewShard(string server, string database, int key)
{
Shard shard;
ShardLocation shardLocation = new ShardLocation(server, database);
- if (!this.ShardMap.TryGetShard(shardLocation, out shard))
+ if (!ShardMap.TryGetShard(shardLocation, out shard))
{
- shard = this.ShardMap.CreateShard(shardLocation);
+ shard = ShardMap.CreateShard(shardLocation);
}
// Register the mapping of the tenant to the shard in the shard map.
// After this step, DDR on the shard map can be used
PointMapping mapping;
- if (!this.ShardMap.TryGetMappingForKey(key, out mapping))
+ if (!ShardMap.TryGetMappingForKey(key, out mapping))
{
- this.ShardMap.CreatePointMapping(key, shard);
+ ShardMap.CreatePointMapping(key, shard);
}
}
}
diff --git a/Samples/Dapper/SqlDatabaseUtils.cs b/Samples/Dapper/SqlDatabaseUtils.cs
deleted file mode 100644
index 66de772..0000000
--- a/Samples/Dapper/SqlDatabaseUtils.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
-
-namespace ElasticDapper
-{
- ///
- /// Helper methods for interacting with SQL Databases.
- ///
- internal static class SqlDatabaseUtils
- {
- ///
- /// Gets the retry policy to use for connections to SQL Server.
- ///
- public static RetryPolicy SqlRetryPolicy
- {
- get { return new RetryPolicy(10, TimeSpan.FromSeconds(5)); }
- }
- }
-}
diff --git a/Samples/ElasticScaleStarterKit/App.config b/Samples/ElasticScaleStarterKit/App.config
new file mode 100644
index 0000000..a6b214c
--- /dev/null
+++ b/Samples/ElasticScaleStarterKit/App.config
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/ElasticScaleStarterKit/Configuration.cs b/Samples/ElasticScaleStarterKit/Configuration.cs
index 3d1fd33..0a46df9 100644
--- a/Samples/ElasticScaleStarterKit/Configuration.cs
+++ b/Samples/ElasticScaleStarterKit/Configuration.cs
@@ -1,8 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System;
using System.Configuration;
-using System.Data.SqlClient;
+using Microsoft.Data.SqlClient;
namespace ElasticScaleStarterKit
{
@@ -76,17 +77,21 @@ public static string GetCredentialsConnectionString()
string userId = ConfigurationManager.AppSettings["UserName"] ?? string.Empty;
string password = ConfigurationManager.AppSettings["Password"] ?? string.Empty;
- // Get Integrated Security from the app.config file.
- // If it exists, then parse it (throw exception on failure), otherwise default to false.
- string integratedSecurityString = ConfigurationManager.AppSettings["IntegratedSecurity"];
- bool integratedSecurity = integratedSecurityString != null && bool.Parse(integratedSecurityString);
+ string trustServerCertificateString = ConfigurationManager.AppSettings["TrustServerCertificate"] ?? string.Empty;
+
+ var trustServerCertificate = trustServerCertificateString != null && bool.Parse(trustServerCertificateString);
+
+ // Get Sql Auth method from the app.config file.
+ SqlAuthenticationMethod authMethod;
+ var enumString = ConfigurationManager.AppSettings["SqlAuthenticationMethod"];
+ if (!Enum.TryParse(enumString, out authMethod))
+ {
+ throw new ArgumentException("Invalid SqlAuthenticationMethod in app.config");
+ }
SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder
{
- // DDR and MSQ require credentials to be set
- UserID = userId,
- Password = password,
- IntegratedSecurity = integratedSecurity,
+ Authentication = authMethod,
// DataSource and InitialCatalog cannot be set for DDR and MSQ APIs, because these APIs will
// determine the DataSource and InitialCatalog for you.
@@ -97,10 +102,52 @@ public static string GetCredentialsConnectionString()
//
// Other SqlClient ConnectionString keywords are supported.
+ TrustServerCertificate = trustServerCertificate,
+
ApplicationName = "ESC_SKv1.0",
- ConnectTimeout = 30
+
+ // Set to 120 if ActiveDirectoryDeviceCodeFlow
+ // not even the fastest cut and pasters can get the device code
+ // into the browser and click through in 30 seconds.
+ ConnectTimeout = authMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow ? 120 : 30,
};
+
+ // DEVNOTE: NotSpecified behaves the same as SqlPassword (i.e. Sql Auth)
+ if (authMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity ||
+ authMethod == SqlAuthenticationMethod.ActiveDirectoryMSI ||
+ authMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal ||
+ authMethod == SqlAuthenticationMethod.ActiveDirectoryPassword ||
+ authMethod == SqlAuthenticationMethod.SqlPassword ||
+ authMethod == SqlAuthenticationMethod.NotSpecified)
+ {
+ // DDR and MSQ require credentials to be set
+
+ // ActiveDirectoryManagedIdentity / ActiveDirectoryMSI when using a System Managed System Identify does not use a UserID
+ if (authMethod != SqlAuthenticationMethod.ActiveDirectoryManagedIdentity &&
+ authMethod != SqlAuthenticationMethod.ActiveDirectoryMSI)
+ {
+ if (userId == string.Empty)
+ {
+ throw new ArgumentException("UserName must be specified in app.config");
+ }
+ }
+
+ connStr.UserID = userId;
+
+ // ActiveDirectoryManagedIdentity/ActiveDirectoryMSI does not use a Password.
+ if (authMethod != SqlAuthenticationMethod.ActiveDirectoryManagedIdentity &&
+ authMethod != SqlAuthenticationMethod.ActiveDirectoryMSI)
+ {
+ if (password == string.Empty)
+ {
+ throw new ArgumentException("Password must be specified in app.config");
+ }
+
+ connStr.Password = password;
+ }
+ }
+
return connStr.ToString();
}
}
-}
+}
\ No newline at end of file
diff --git a/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs b/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs
index 780b0f7..a34c397 100644
--- a/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs
+++ b/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
-using System.Data.SqlClient;
using System.Linq;
using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement;
+using Microsoft.Data.SqlClient;
namespace ElasticScaleStarterKit
{
@@ -64,21 +64,23 @@ private static void AddCustomer(
// Open and execute the command with retry for transient faults. Note that if the command fails, the connection is closed, so
// the entire block is wrapped in a retry. This means that only one command should be executed per block, since if we had multiple
// commands then the first command may be executed multiple times if later commands fail.
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+
+ // Looks up the key in the shard map and opens a connection to the shard
+ using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString))
{
- // Looks up the key in the shard map and opens a connection to the shard
- using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString))
+ // Create a simple command that will insert or update the customer information
+ using (SqlCommand cmd = conn.CreateCommand())
{
- // Create a simple command that will insert or update the customer information
- SqlCommand cmd = conn.CreateCommand();
+ cmd.RetryLogicProvider = SqlDatabaseUtils.SqlRetryProvider;
+
cmd.CommandText = @"
- IF EXISTS (SELECT 1 FROM Customers WHERE CustomerId = @customerId)
- UPDATE Customers
- SET Name = @name, RegionId = @regionId
- WHERE CustomerId = @customerId
- ELSE
- INSERT INTO Customers (CustomerId, Name, RegionId)
- VALUES (@customerId, @name, @regionId)";
+ IF EXISTS (SELECT 1 FROM Customers WHERE CustomerId = @customerId)
+ UPDATE Customers
+ SET Name = @name, RegionId = @regionId
+ WHERE CustomerId = @customerId
+ ELSE
+ INSERT INTO Customers (CustomerId, Name, RegionId)
+ VALUES (@customerId, @name, @regionId)";
cmd.Parameters.AddWithValue("@customerId", customerId);
cmd.Parameters.AddWithValue("@name", name);
cmd.Parameters.AddWithValue("@regionId", regionId);
@@ -87,7 +89,7 @@ INSERT INTO Customers (CustomerId, Name, RegionId)
// Execute the command
cmd.ExecuteNonQuery();
}
- });
+ }
}
///
@@ -99,13 +101,13 @@ private static void AddOrder(
int customerId,
int productId)
{
- SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
+ // Looks up the key in the shard map and opens a connection to the shard
+ using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString))
{
- // Looks up the key in the shard map and opens a connection to the shard
- using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString))
+ // Create a simple command that will insert a new order
+ using (SqlCommand cmd = conn.CreateCommand())
{
- // Create a simple command that will insert a new order
- SqlCommand cmd = conn.CreateCommand();
+ cmd.RetryLogicProvider = SqlDatabaseUtils.SqlRetryProvider;
// Create a simple command
cmd.CommandText = @"INSERT INTO dbo.Orders (CustomerId, OrderDate, ProductId)
@@ -118,7 +120,7 @@ private static void AddOrder(
// Execute the command
cmd.ExecuteNonQuery();
}
- });
+ }
ConsoleUtils.WriteInfo("Inserted order for customer ID: {0}", customerId);
}
@@ -137,4 +139,4 @@ private static int GetCustomerId(int maxid)
return s_r.Next(0, maxid);
}
}
-}
+}
\ No newline at end of file
diff --git a/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj b/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj
index d9bfc74..e37f36e 100644
--- a/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj
+++ b/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj
@@ -5,9 +5,6 @@
-
-
-
@@ -18,6 +15,11 @@
-
+
+
+
+
+ Always
+
\ No newline at end of file
diff --git a/Samples/ElasticScaleStarterKit/LICENSE b/Samples/ElasticScaleStarterKit/LICENSE
index b8b569d..06f7e30 100644
--- a/Samples/ElasticScaleStarterKit/LICENSE
+++ b/Samples/ElasticScaleStarterKit/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Microsoft
+Copyright (c) 2024 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Samples/ElasticScaleStarterKit/Program.cs b/Samples/ElasticScaleStarterKit/Program.cs
index 2a87424..f25c02c 100644
--- a/Samples/ElasticScaleStarterKit/Program.cs
+++ b/Samples/ElasticScaleStarterKit/Program.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Configuration;
using System.Diagnostics;
using System.Linq;
using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement;
@@ -19,6 +20,8 @@ public static void Main()
Console.WriteLine("*** Welcome to Elastic Database Tools Starter Kit ***");
Console.WriteLine("***********************************************************");
Console.WriteLine();
+ Console.WriteLine("Authentication method used: {0}", ConfigurationManager.AppSettings["SqlAuthenticationMethod"]);
+ Console.WriteLine();
// Verify that we can connect to the Sql Database that is specified in App.config settings
if (!SqlDatabaseUtils.TryConnectToSqlDatabase())
diff --git a/Samples/ElasticScaleStarterKit/README.txt b/Samples/ElasticScaleStarterKit/README.txt
index d11ba40..d3f42b3 100644
--- a/Samples/ElasticScaleStarterKit/README.txt
+++ b/Samples/ElasticScaleStarterKit/README.txt
@@ -1,12 +1,17 @@
-
-Prerequisites:
-- Visual Studio 2012 or later, Professional Edition or higher
-- Nuget 2.7 or later
-- .NET Framework 4.5 or later
-- Microsoft Azure SQL Database
+Prerequisites
+-------------
+- .Net 6.0 or later
+- Visual Studio 2022
+- Microsoft Azure SQL Database or Microsoft SQL Server instance on local machine
+Getting Started
+---------------
Before running this project, please fill in the values in App.config.
For detailed instructions and background information, please refer to Getting Started web page
for Azure SQL Database Elastic Scale: http://go.microsoft.com/fwlink/?LinkID=510913
+Authentication Info
+-------------------
+https://learn.microsoft.com/en-us/sql/connect/ado-net/sql/azure-active-directory-authentication?view=sql-server-ver16#setting-microsoft-entra-authentication
+https://learn.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-overview?view=azuresql#connect-by-using-microsoft-entra-identities
\ No newline at end of file
diff --git a/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs b/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs
index 6049668..e5566e0 100644
--- a/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs
+++ b/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs
@@ -3,12 +3,10 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Threading;
-using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
+using Microsoft.Data.SqlClient;
namespace ElasticScaleStarterKit
{
@@ -22,6 +20,22 @@ internal static class SqlDatabaseUtils
///
public const string MasterDatabaseName = "master";
+ // Create a retry logic provider
+ public static SqlRetryLogicBaseProvider SqlRetryProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(SqlRetryPolicy);
+
+ ///
+ /// Gets the retry policy to use for connections to SQL Server.
+ ///
+ private static SqlRetryLogicOption SqlRetryPolicy => new()
+ {
+ // Tries 5 times before throwing an exception
+ NumberOfTries = 5,
+ // Preferred gap time to delay before retry
+ DeltaTime = TimeSpan.FromSeconds(1),
+ // Maximum gap time for each delay time before retry
+ MaxTimeInterval = TimeSpan.FromSeconds(20)
+ };
+
///
/// Returns true if we can connect to the database.
///
@@ -34,11 +48,9 @@ public static bool TryConnectToSqlDatabase()
try
{
- using (ReliableSqlConnection conn = new ReliableSqlConnection(
- connectionString,
- SqlRetryPolicy,
- SqlRetryPolicy))
+ using (SqlConnection conn = new SqlConnection(connectionString))
{
+ conn.RetryLogicProvider = SqlRetryProvider;
conn.Open();
}
@@ -55,91 +67,96 @@ public static bool TryConnectToSqlDatabase()
public static bool DatabaseExists(string server, string db)
{
- using (ReliableSqlConnection conn = new ReliableSqlConnection(
- Configuration.GetConnectionString(server, MasterDatabaseName),
- SqlRetryPolicy,
- SqlRetryPolicy))
+ using (SqlConnection conn = new SqlConnection(Configuration.GetConnectionString(server, MasterDatabaseName)))
{
+ conn.RetryLogicProvider = SqlRetryProvider;
conn.Open();
- SqlCommand cmd = conn.CreateCommand();
- cmd.CommandText = "select count(*) from sys.databases where name = @dbname";
- cmd.Parameters.AddWithValue("@dbname", db);
- cmd.CommandTimeout = 60;
- int count = conn.ExecuteCommand(cmd);
+ using (SqlCommand cmd = conn.CreateCommand())
+ {
+ cmd.RetryLogicProvider = SqlRetryProvider;
+
+ cmd.CommandText = "select count(*) from sys.databases where name = @dbname";
+ cmd.Parameters.AddWithValue("@dbname", db);
+ cmd.CommandTimeout = 60;
+
+ int count = (int)cmd.ExecuteScalar();
- bool exists = count > 0;
- return exists;
+ bool exists = count > 0;
+ return exists;
+ }
}
}
public static bool DatabaseIsOnline(string server, string db)
{
- using (ReliableSqlConnection conn = new ReliableSqlConnection(
- Configuration.GetConnectionString(server, MasterDatabaseName),
- SqlRetryPolicy,
- SqlRetryPolicy))
+ using (SqlConnection conn = new SqlConnection(Configuration.GetConnectionString(server, MasterDatabaseName)))
{
+ conn.RetryLogicProvider = SqlRetryProvider;
conn.Open();
- SqlCommand cmd = conn.CreateCommand();
- cmd.CommandText = "select count(*) from sys.databases where name = @dbname and state = 0"; // online
- cmd.Parameters.AddWithValue("@dbname", db);
- cmd.CommandTimeout = 60;
- int count = conn.ExecuteCommand(cmd);
+ using (SqlCommand cmd = conn.CreateCommand())
+ {
+ cmd.RetryLogicProvider = SqlRetryProvider;
+
+ cmd.CommandText = "select count(*) from sys.databases where name = @dbname and state = 0"; // online
+ cmd.Parameters.AddWithValue("@dbname", db);
+ cmd.CommandTimeout = 60;
+
+ int count = (int)cmd.ExecuteScalar();
- bool exists = count > 0;
- return exists;
+ bool exists = count > 0;
+ return exists;
+ }
}
}
public static void CreateDatabase(string server, string db)
{
ConsoleUtils.WriteInfo("Creating database {0}", db);
- using (ReliableSqlConnection conn = new ReliableSqlConnection(
- Configuration.GetConnectionString(server, MasterDatabaseName),
- SqlRetryPolicy,
- SqlRetryPolicy))
+ using (SqlConnection conn = new SqlConnection(Configuration.GetConnectionString(server, MasterDatabaseName)))
{
+ conn.RetryLogicProvider = SqlRetryProvider;
conn.Open();
- SqlCommand cmd = conn.CreateCommand();
-
- // Determine if we are connecting to Azure SQL DB
- cmd.CommandText = "SELECT SERVERPROPERTY('EngineEdition')";
- cmd.CommandTimeout = 60;
- int engineEdition = conn.ExecuteCommand(cmd);
- if (engineEdition == 5)
+ using (SqlCommand cmd = conn.CreateCommand())
{
- // Azure SQL DB
- SqlRetryPolicy.ExecuteAction(() =>
+ // Determine if we are connecting to Azure SQL DB
+ cmd.CommandText = "SELECT SERVERPROPERTY('EngineEdition')";
+ cmd.CommandTimeout = 60;
+ cmd.RetryLogicProvider = SqlRetryProvider;
+
+ int engineEdition = (int)cmd.ExecuteScalar();
+
+ if (engineEdition == 5)
+ {
+ // Azure SQL DB
+ if (!DatabaseExists(server, db))
+ {
+ // Begin creation (which is async for Standard/Premium editions)
+ cmd.CommandText = string.Format(
+ "CREATE DATABASE {0} (EDITION = '{1}')",
+ BracketEscapeName(db),
+ Configuration.DatabaseEdition);
+ cmd.CommandTimeout = 180;
+ cmd.ExecuteNonQuery();
+ }
+
+ // Wait for the operation to complete
+ while (!DatabaseIsOnline(server, db))
{
- if (!DatabaseExists(server, db))
- {
- // Begin creation (which is async for Standard/Premium editions)
- cmd.CommandText = string.Format(
- "CREATE DATABASE {0} (EDITION = '{1}')",
- BracketEscapeName(db),
- Configuration.DatabaseEdition);
- cmd.CommandTimeout = 60;
- cmd.ExecuteNonQuery();
- }
- });
-
- // Wait for the operation to complete
- while (!DatabaseIsOnline(server, db))
+ ConsoleUtils.WriteInfo("Waiting for database {0} to come online...", db);
+ Thread.Sleep(TimeSpan.FromSeconds(5));
+ }
+
+ ConsoleUtils.WriteInfo("Database {0} is online", db);
+ }
+ else
{
- ConsoleUtils.WriteInfo("Waiting for database {0} to come online...", db);
- Thread.Sleep(TimeSpan.FromSeconds(5));
+ // Other edition of SQL DB
+ cmd.CommandText = string.Format("CREATE DATABASE {0}", BracketEscapeName(db));
+ cmd.ExecuteNonQuery();
}
-
- ConsoleUtils.WriteInfo("Database {0} is online", db);
- }
- else
- {
- // Other edition of SQL DB
- cmd.CommandText = string.Format("CREATE DATABASE {0}", BracketEscapeName(db));
- conn.ExecuteCommand(cmd);
}
}
}
@@ -147,34 +164,33 @@ public static void CreateDatabase(string server, string db)
public static void DropDatabase(string server, string db)
{
ConsoleUtils.WriteInfo("Dropping database {0}", db);
- using (ReliableSqlConnection conn = new ReliableSqlConnection(
- Configuration.GetConnectionString(server, MasterDatabaseName),
- SqlRetryPolicy,
- SqlRetryPolicy))
+ using (SqlConnection conn = new SqlConnection(Configuration.GetConnectionString(server, MasterDatabaseName)))
{
+ conn.RetryLogicProvider = SqlRetryProvider;
conn.Open();
- SqlCommand cmd = conn.CreateCommand();
- // Determine if we are connecting to Azure SQL DB
- cmd.CommandText = "SELECT SERVERPROPERTY('EngineEdition')";
- cmd.CommandTimeout = 60;
- int engineEdition = conn.ExecuteCommand(cmd);
-
- // Drop the database
- if (engineEdition == 5)
+ using (SqlCommand cmd = conn.CreateCommand())
{
- // Azure SQL DB
+ // Determine if we are connecting to Azure SQL DB
+ cmd.CommandText = "SELECT SERVERPROPERTY('EngineEdition')";
+ cmd.CommandTimeout = 60;
+ int engineEdition = (int)cmd.ExecuteScalar();
- cmd.CommandText = string.Format("DROP DATABASE {0}", BracketEscapeName(db));
- cmd.ExecuteNonQuery();
- }
- else
- {
- cmd.CommandText = string.Format(
- @"ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE
- DROP DATABASE {0}",
- BracketEscapeName(db));
- cmd.ExecuteNonQuery();
+ // Drop the database
+ if (engineEdition == 5)
+ {
+ // Azure SQL DB
+ cmd.CommandText = string.Format("DROP DATABASE {0}", BracketEscapeName(db));
+ cmd.ExecuteNonQuery();
+ }
+ else
+ {
+ cmd.CommandText = string.Format(
+ @"ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE
+ DROP DATABASE {0}",
+ BracketEscapeName(db));
+ cmd.ExecuteNonQuery();
+ }
}
}
}
@@ -182,22 +198,24 @@ public static void DropDatabase(string server, string db)
public static void ExecuteSqlScript(string server, string db, string schemaFile)
{
ConsoleUtils.WriteInfo("Executing script {0}", schemaFile);
- using (ReliableSqlConnection conn = new ReliableSqlConnection(
- Configuration.GetConnectionString(server, db),
- SqlRetryPolicy,
- SqlRetryPolicy))
+ using (SqlConnection conn = new SqlConnection(Configuration.GetConnectionString(server, db)))
{
+ conn.RetryLogicProvider = SqlRetryProvider;
conn.Open();
- SqlCommand cmd = conn.CreateCommand();
- // Read the commands from the sql script file
- IEnumerable commands = ReadSqlScript(schemaFile);
-
- foreach (string command in commands)
+ using (SqlCommand cmd = conn.CreateCommand())
{
- cmd.CommandText = command;
- cmd.CommandTimeout = 60;
- conn.ExecuteCommand(cmd);
+ cmd.RetryLogicProvider = SqlRetryProvider;
+
+ // Read the commands from the sql script file
+ IEnumerable commands = ReadSqlScript(schemaFile);
+
+ foreach (string command in commands)
+ {
+ cmd.CommandText = command;
+ cmd.CommandTimeout = 60;
+ cmd.ExecuteNonQuery();
+ }
}
}
}
@@ -233,69 +251,5 @@ private static string BracketEscapeName(string sqlName)
{
return '[' + sqlName.Replace("]", "]]") + ']';
}
-
- ///
- /// Gets the retry policy to use for connections to SQL Server.
- ///
- public static RetryPolicy SqlRetryPolicy
- {
- get
- {
- return new RetryPolicy(10, TimeSpan.FromSeconds(5));
- }
- }
-
- ///
- /// Extended sql transient error detection strategy that performs additional transient error
- /// checks besides the ones done by the enterprise library.
- ///
- private class ExtendedSqlDatabaseTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
- {
- ///
- /// Enterprise transient error detection strategy.
- ///
- private SqlDatabaseTransientErrorDetectionStrategy _sqltransientErrorDetectionStrategy = new SqlDatabaseTransientErrorDetectionStrategy();
-
- ///
- /// Checks with enterprise library's default handler to see if the error is transient, additionally checks
- /// for such errors using the code in the in function.
- ///
- /// Exception being checked.
- /// true if exception is considered transient, false otherwise.
- public bool IsTransient(Exception ex)
- {
- return _sqltransientErrorDetectionStrategy.IsTransient(ex) || IsTransientException(ex);
- }
-
- ///
- /// Detects transient errors not currently considered as transient by the enterprise library's strategy.
- ///
- /// Input exception.
- /// true if exception is considered transient, false otherwise.
- private static bool IsTransientException(Exception ex)
- {
- SqlException se = ex as SqlException;
-
- if (se != null && se.InnerException != null)
- {
- Win32Exception we = se.InnerException as Win32Exception;
-
- if (we != null)
- {
- switch (we.NativeErrorCode)
- {
- case 0x102:
- // Transient wait expired error resulting in timeout
- return true;
- case 0x121:
- // Transient semaphore wait expired error resulting in timeout
- return true;
- }
- }
- }
-
- return false;
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/Samples/ElasticScaleStarterKit/appsettings.json b/Samples/ElasticScaleStarterKit/appsettings.json
deleted file mode 100644
index 3bb3c89..0000000
--- a/Samples/ElasticScaleStarterKit/appsettings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "ServerName": "(localdb)\\v11.0",
- "IntegratedSecurity": true,
- "UserName": "MyUserName",
- "Password": "MyPassword",
- "DatabaseEdition": "Basic"
-}
\ No newline at end of file
diff --git a/Samples/ShardSqlCmd/LICENSE b/Samples/ShardSqlCmd/LICENSE
index b8b569d..06f7e30 100644
--- a/Samples/ShardSqlCmd/LICENSE
+++ b/Samples/ShardSqlCmd/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Microsoft
+Copyright (c) 2024 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Samples/ShardSqlCmd/Program.cs b/Samples/ShardSqlCmd/Program.cs
index 5ce8a42..7dde625 100644
--- a/Samples/ShardSqlCmd/Program.cs
+++ b/Samples/ShardSqlCmd/Program.cs
@@ -5,12 +5,12 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
-using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Azure.SqlDatabase.ElasticScale.Query;
using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement;
+using Microsoft.Data.SqlClient;
namespace ShardSqlCmd
{
diff --git a/Samples/ShardSqlCmd/ShardSqlCmd.csproj b/Samples/ShardSqlCmd/ShardSqlCmd.csproj
index 5457ab3..7e4d5a9 100644
--- a/Samples/ShardSqlCmd/ShardSqlCmd.csproj
+++ b/Samples/ShardSqlCmd/ShardSqlCmd.csproj
@@ -1,14 +1,13 @@
-
net6.0;net8.0
Exe
-
+
-
+
diff --git a/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj b/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj
index 4c70550..e10e48e 100644
--- a/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj
+++ b/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj
@@ -5,11 +5,11 @@
© Microsoft Corporation. All rights reserved.
Microsoft Azure SQL Database: Elastic Database Client Library
en-US
- 2.4.1
+ 2.4.2-preview1
Microsoft
- net6.0;net8.0
+ netstandard2.0;net6.0
Microsoft;Elastic;Scale;Azure;SQL;DB;Database;Shard;Sharding;Management;Query;azureofficial
- Updated dependent nugets (due to CVEs), added support for ActiveDirectoryDefault Entra auth
+ Elastic Scale Client now targets .Net Standard 2.0 along with .Net 6.0 and sample apps are updated to support Entra auth
Icon.png
https://github.com/Azure/elastic-db-tools
MIT
diff --git a/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj b/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj
index b108ad6..a069890 100644
--- a/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj
+++ b/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj
@@ -1,6 +1,6 @@
- net6.0;net8.0
+ netstandard2.0
$(NoWarn);SYSLIB0011;
diff --git a/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj b/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj
index acad1fa..5e2aecb 100644
--- a/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj
+++ b/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj
@@ -1,6 +1,6 @@
- net6.0;net8.0
+ net6.0;net8.0;net481
true
false
$(NoWarn);CS8073;
@@ -8,7 +8,6 @@
-
diff --git a/Test/ElasticScale.Query.UnitTests/MultiShardQueryE2ETests.cs b/Test/ElasticScale.Query.UnitTests/MultiShardQueryE2ETests.cs
index 72e6924..a53f702 100644
--- a/Test/ElasticScale.Query.UnitTests/MultiShardQueryE2ETests.cs
+++ b/Test/ElasticScale.Query.UnitTests/MultiShardQueryE2ETests.cs
@@ -24,533 +24,579 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests;
-
-///
-/// Tests for end to end scenarios where a user
-/// connects to his shards, executes commands against them
-/// and receives results
-///
-[TestClass]
-public class MultiShardQueryE2ETests
+namespace Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests
{
- #region Global vars
-
-
- ///
- /// Handle on connections to all shards
- ///
- private MultiShardConnection _shardConnection;
-
///
- /// Handle to the ShardMap with our Test databases.
+ /// Tests for end to end scenarios where a user
+ /// connects to his shards, executes commands against them
+ /// and receives results
///
- private ShardMap _shardMap;
-
- #endregion
-
- #region Boilerplate
-
- ///
- /// Gets or sets the test context which provides
- /// information about and functionality for the current test run.
- ///
- public TestContext TestContext { get; set; }
-
- [ClassInitialize()]
- public static void MyClassInitialize(TestContext testContext)
+ [TestClass]
+ public class MultiShardQueryE2ETests
{
- // Drop and recreate the test databases, tables, and data that we will use to verify
- // the functionality.
- // For now I have hardcoded the server location and database names. A better approach would be
- // to make the server location configurable and the database names be guids.
- // Not the top priority right now, though.
- //
- SqlConnection.ClearAllPools();
- MultiShardTestUtils.DropAndCreateDatabases();
- MultiShardTestUtils.CreateAndPopulateTables();
- }
+ #region Global vars
- ///
- /// Blow away our three test databases that we drove the tests off of.
- /// Doing this so that we don't leave objects littered around.
- ///
- [ClassCleanup()]
- public static void MyClassCleanup()
- {
- // We need to clear the connection pools so that we don't get a database still in use error
- // resulting from our attenpt to drop the databases below.
- //
- SqlConnection.ClearAllPools();
- MultiShardTestUtils.DropDatabases();
- }
- ///
- /// Open up a clean connection to each test database prior to each test.
- ///
- [TestInitialize()]
- public void MyTestInitialize()
- {
- _shardMap = MultiShardTestUtils.CreateAndGetTestShardMap();
+ ///
+ /// Handle on connections to all shards
+ ///
+ private MultiShardConnection _shardConnection;
- _shardConnection = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- }
+ ///
+ /// Handle to the ShardMap with our Test databases.
+ ///
+ private ShardMap _shardMap;
- ///
- /// Close our connections to each test database after each test.
- ///
- [TestCleanup()]
- public void MyTestCleanup() =>
- // Close connections after each test.
- //
- _shardConnection.Dispose();
+ #endregion
- #endregion
+ #region Boilerplate
- ///
- /// Check that we can iterate through 3 result sets as expected.
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSimpleSelect_PartialResults() => TestSimpleSelect(MultiShardExecutionPolicy.PartialResults);
+ ///
+ /// Gets or sets the test context which provides
+ /// information about and functionality for the current test run.
+ ///
+ public TestContext TestContext { get; set; }
- ///
- /// Check that we can iterate through 3 result sets as expected.
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSimpleSelect_CompleteResults() => TestSimpleSelect(MultiShardExecutionPolicy.CompleteResults);
+ [ClassInitialize()]
+ public static void MyClassInitialize(TestContext testContext)
+ {
+ // Drop and recreate the test databases, tables, and data that we will use to verify
+ // the functionality.
+ // For now I have hardcoded the server location and database names. A better approach would be
+ // to make the server location configurable and the database names be guids.
+ // Not the top priority right now, though.
+ //
+ SqlConnection.ClearAllPools();
+ MultiShardTestUtils.DropAndCreateDatabases();
+ MultiShardTestUtils.CreateAndPopulateTables();
+ }
- public void TestSimpleSelect(MultiShardExecutionPolicy policy)
- {
- // What we're doing:
- // Grab all rows from each test database.
- // Load them into a MultiShardDataReader.
- // Iterate through the rows and make sure that we have 9 total.
- //
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
- cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
- cmd.ExecutionPolicy = policy;
-
- using var sdr = cmd.ExecuteReader();
- var recordsRetrieved = 0;
- Logger.Log("Starting to get records");
- while (sdr.Read())
+ ///
+ /// Blow away our three test databases that we drove the tests off of.
+ /// Doing this so that we don't leave objects littered around.
+ ///
+ [ClassCleanup()]
+ public static void MyClassCleanup()
{
- recordsRetrieved++;
- var dbNameField = sdr.GetString(0);
- var testIntField = sdr.GetFieldValue(1);
- var testBigIntField = sdr.GetFieldValue(2);
- var shardIdPseudoColumn = sdr.GetFieldValue(3);
- var logRecord =
- string.Format(
- "RecordRetrieved: dbNameField: {0}, TestIntField: {1}, TestBigIntField: {2}, shardIdPseudoColumnField: {3}, RecordCount: {4}",
- dbNameField, testIntField, testBigIntField, shardIdPseudoColumn, recordsRetrieved);
- Logger.Log(logRecord);
- Debug.WriteLine(logRecord);
+ // We need to clear the connection pools so that we don't get a database still in use error
+ // resulting from our attenpt to drop the databases below.
+ //
+ SqlConnection.ClearAllPools();
+ MultiShardTestUtils.DropDatabases();
}
- sdr.Close();
+ ///
+ /// Open up a clean connection to each test database prior to each test.
+ ///
+ [TestInitialize()]
+ public void MyTestInitialize()
+ {
+ _shardMap = MultiShardTestUtils.CreateAndGetTestShardMap();
- Assert.AreEqual(recordsRetrieved, 9);
- }
+ _shardConnection = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
+ }
+ ///
+ /// Close our connections to each test database after each test.
+ ///
+ [TestCleanup()]
+ public void MyTestCleanup() =>
+ // Close connections after each test.
+ //
+ _shardConnection.Dispose();
- ///
- /// Check that we can return an empty result set that has a schema table
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_NoRows_CompleteResults() => TestSelectNoRows("select 1 where 0 = 1", MultiShardExecutionPolicy.CompleteResults);
+ #endregion
- ///
- /// Check that we can return an empty result set that has a schema table
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_NoRows_PartialResults() => TestSelectNoRows("select 1 where 0 = 1", MultiShardExecutionPolicy.PartialResults);
+ ///
+ /// Check that we can iterate through 3 result sets as expected.
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSimpleSelect_PartialResults() => TestSimpleSelect(MultiShardExecutionPolicy.PartialResults);
- public void TestSelectNoRows(string commandText, MultiShardExecutionPolicy policy)
- {
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = commandText;
- cmd.ExecutionPolicy = policy;
+ ///
+ /// Check that we can iterate through 3 result sets as expected.
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSimpleSelect_CompleteResults() => TestSimpleSelect(MultiShardExecutionPolicy.CompleteResults);
- // Read first
- using (var sdr = cmd.ExecuteReader())
+ public void TestSimpleSelect(MultiShardExecutionPolicy policy)
{
- Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
- while (sdr.Read())
+ // What we're doing:
+ // Grab all rows from each test database.
+ // Load them into a MultiShardDataReader.
+ // Iterate through the rows and make sure that we have 9 total.
+ //
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
{
- Assert.Fail("Should not have gotten any records.");
- }
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
+ cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
+ cmd.ExecutionPolicy = policy;
- Assert.IsFalse(sdr.HasRows);
- }
+ using (var sdr = cmd.ExecuteReader())
+ {
+ var recordsRetrieved = 0;
+ Logger.Log("Starting to get records");
+ while (sdr.Read())
+ {
+ recordsRetrieved++;
+ var dbNameField = sdr.GetString(0);
+ var testIntField = sdr.GetFieldValue(1);
+ var testBigIntField = sdr.GetFieldValue(2);
+ var shardIdPseudoColumn = sdr.GetFieldValue(3);
+ var logRecord =
+ string.Format(
+ "RecordRetrieved: dbNameField: {0}, TestIntField: {1}, TestBigIntField: {2}, shardIdPseudoColumnField: {3}, RecordCount: {4}",
+ dbNameField, testIntField, testBigIntField, shardIdPseudoColumn, recordsRetrieved);
+ Logger.Log(logRecord);
+ Debug.WriteLine(logRecord);
+ }
- // HasRows first
- using (var sdr = cmd.ExecuteReader())
- {
- Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
- Assert.IsFalse(sdr.HasRows);
- while (sdr.Read())
- {
- Assert.Fail("Should not have gotten any records.");
+ sdr.Close();
+
+ Assert.AreEqual(recordsRetrieved, 9);
+ }
+ }
}
}
- }
- ///
- /// Check that we can return an empty result set that does not have a schema table
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_NonQuery_CompleteResults() => TestSelectNonQuery("if (0 = 1) select 1 ", MultiShardExecutionPolicy.CompleteResults);
+ ///
+ /// Check that we can return an empty result set that has a schema table
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_NoRows_CompleteResults() => TestSelectNoRows("select 1 where 0 = 1", MultiShardExecutionPolicy.CompleteResults);
- ///
- /// Check that we can return a completely empty result set as expected.
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_NonQuery_PartialResults() => TestSelectNonQuery("if (0 = 1) select 1", MultiShardExecutionPolicy.PartialResults);
+ ///
+ /// Check that we can return an empty result set that has a schema table
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_NoRows_PartialResults() => TestSelectNoRows("select 1 where 0 = 1", MultiShardExecutionPolicy.PartialResults);
- public void TestSelectNonQuery(string commandText, MultiShardExecutionPolicy policy)
- {
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = commandText;
- cmd.ExecutionPolicy = policy;
-
- using var sdr = cmd.ExecuteReader();
- Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
-
- // TODO: This is a weird error message, but it's good enough for now
- // Fixing this will require significant refactoring of MultiShardDataReader,
- // we should fix it when we finish implementing async adding of child readers
- _ = AssertExtensions.AssertThrows(() => sdr.Read());
- }
+ public void TestSelectNoRows(string commandText, MultiShardExecutionPolicy policy)
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = commandText;
+ cmd.ExecutionPolicy = policy;
- ///
- /// Check that ExecuteReader throws when all shards have an exception
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_Failure_PartialResults()
- {
- var e = TestSelectFailure(
- "raiserror('blah', 16, 0)",
- MultiShardExecutionPolicy.PartialResults);
+ // Read first
+ using (var sdr = cmd.ExecuteReader())
+ {
+ Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
+ while (sdr.Read())
+ {
+ Assert.Fail("Should not have gotten any records.");
+ }
- // All children should have failed
- Assert.AreEqual(_shardMap.GetShards().Count(), e.InnerExceptions.Count);
- }
+ Assert.IsFalse(sdr.HasRows);
+ }
- ///
- /// Check that ExecuteReader throws when all shards have an exception
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_Failure_CompleteResults()
- {
- var e = TestSelectFailure(
- "raiserror('blah', 16, 0)",
- MultiShardExecutionPolicy.CompleteResults);
-
- // We don't know exactly how many child exceptions will happen, because the
- // first exception that is seen will cause the children to be canceled.
- AssertExtensions.AssertGreaterThanOrEqualTo(1, e.InnerExceptions.Count);
- AssertExtensions.AssertLessThanOrEqualTo(_shardMap.GetShards().Count(), e.InnerExceptions.Count);
- }
+ // HasRows first
+ using (var sdr = cmd.ExecuteReader())
+ {
+ Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
+ Assert.IsFalse(sdr.HasRows);
+ while (sdr.Read())
+ {
+ Assert.Fail("Should not have gotten any records.");
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Check that we can return an empty result set that does not have a schema table
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_NonQuery_CompleteResults() => TestSelectNonQuery("if (0 = 1) select 1 ", MultiShardExecutionPolicy.CompleteResults);
- public MultiShardAggregateException TestSelectFailure(string commandText, MultiShardExecutionPolicy policy)
- {
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = commandText;
- cmd.ExecutionPolicy = policy;
- // ExecuteReader should fail
- var aggregateException =
- AssertExtensions.AssertThrows(() => cmd.ExecuteReader());
+ ///
+ /// Check that we can return a completely empty result set as expected.
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_NonQuery_PartialResults() => TestSelectNonQuery("if (0 = 1) select 1", MultiShardExecutionPolicy.PartialResults);
- // Sanity check the exceptions are the correct type
- foreach (var e in aggregateException.InnerExceptions)
+ public void TestSelectNonQuery(string commandText, MultiShardExecutionPolicy policy)
{
- Assert.IsInstanceOfType(e, typeof(MultiShardException));
- Assert.IsInstanceOfType(e.InnerException, typeof(SqlException));
- }
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = commandText;
+ cmd.ExecutionPolicy = policy;
- // Return the exception so that the caller can do additional validation
- return aggregateException;
- }
+ using (var sdr = cmd.ExecuteReader())
+ {
+ Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
- ///
- /// Check that we can return a partially succeeded reader when PartialResults policy is on
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_PartialFailure_PartialResults()
- {
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = GetPartialFailureQuery();
- cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
+ // TODO: This is a weird error message, but it's good enough for now
+ // Fixing this will require significant refactoring of MultiShardDataReader,
+ // we should fix it when we finish implementing async adding of child readers
+ _ = AssertExtensions.AssertThrows(() => sdr.Read());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Check that ExecuteReader throws when all shards have an exception
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_Failure_PartialResults()
+ {
+ var e = TestSelectFailure(
+ "raiserror('blah', 16, 0)",
+ MultiShardExecutionPolicy.PartialResults);
- using var sdr = cmd.ExecuteReader();
- // Exactly one should have failed
- Assert.AreEqual(1, sdr.MultiShardExceptions.Count);
+ // All children should have failed
+ Assert.AreEqual(_shardMap.GetShards().Count(), e.InnerExceptions.Count);
+ }
- // We should be able to read
- while (sdr.Read())
+ ///
+ /// Check that ExecuteReader throws when all shards have an exception
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_Failure_CompleteResults()
{
+ var e = TestSelectFailure(
+ "raiserror('blah', 16, 0)",
+ MultiShardExecutionPolicy.CompleteResults);
+
+ // We don't know exactly how many child exceptions will happen, because the
+ // first exception that is seen will cause the children to be canceled.
+ AssertExtensions.AssertGreaterThanOrEqualTo(1, e.InnerExceptions.Count);
+ AssertExtensions.AssertLessThanOrEqualTo(_shardMap.GetShards().Count(), e.InnerExceptions.Count);
}
- }
- ///
- /// Check that we fail a partially successful command when CompleteResults policy is on
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSelect_PartialFailure_CompleteResults()
- {
- var query = GetPartialFailureQuery();
- var e = TestSelectFailure(query, MultiShardExecutionPolicy.CompleteResults);
+ public MultiShardAggregateException TestSelectFailure(string commandText, MultiShardExecutionPolicy policy)
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = commandText;
+ cmd.ExecutionPolicy = policy;
- // Exactly one should have failed
- Assert.AreEqual(1, e.InnerExceptions.Count);
- }
+ // ExecuteReader should fail
+ var aggregateException =
+ AssertExtensions.AssertThrows(() => cmd.ExecuteReader());
- ///
- /// Gets a command that fails on one shard, but succeeds on others
- ///
- private string GetPartialFailureQuery()
- {
- var shardLocations = _shardMap.GetShards().Select(s => s.Location);
+ // Sanity check the exceptions are the correct type
+ foreach (var e in aggregateException.InnerExceptions)
+ {
+ Assert.IsInstanceOfType(e, typeof(MultiShardException));
+ Assert.IsInstanceOfType(e.InnerException, typeof(SqlException));
+ }
- // Pick an arbitrary one of those shards
- var chosenShardLocation = shardLocations.First();
+ // Return the exception so that the caller can do additional validation
+ return aggregateException;
+ }
+ }
+ }
- // This query assumes that the chosen shard location's db name is distinct from all others
- // In other words, only one shard location should have a database equal to the chosen location
- Assert.AreEqual(1, shardLocations.Count(l => l.Database.Equals(chosenShardLocation.Database)));
+ ///
+ /// Check that we can return a partially succeeded reader when PartialResults policy is on
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_PartialFailure_PartialResults()
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = GetPartialFailureQuery();
+ cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
- // We also assume that there is more than one shard
- AssertExtensions.AssertGreaterThan(1, shardLocations.Count());
+ using (var sdr = cmd.ExecuteReader())
+ {
+ // Exactly one should have failed
+ Assert.AreEqual(1, sdr.MultiShardExceptions.Count);
- // The command will fail only on the chosen shard
- return string.Format("if db_name() = '{0}' raiserror('blah', 16, 0) else select 1",
- shardLocations.First().Database);
- }
+ // We should be able to read
+ while (sdr.Read())
+ {
+ }
+ }
+ }
+ }
+ }
- ///
- /// Basic test for async api(s)
- /// Also demonstrates the async pattern of this library
- /// The Sync api is implicitly tested in MultiShardDataReaderTests::TestSimpleSelect
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestQueryShardsAsync()
- {
- // Create new sharded connection so we can test the OpenAsync call as well.
- //
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
- cmd.CommandType = CommandType.Text;
-
- using var sdr = ExecAsync(conn, cmd).Result;
- var recordsRetrieved = 0;
- while (sdr.Read())
+ ///
+ /// Check that we fail a partially successful command when CompleteResults policy is on
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSelect_PartialFailure_CompleteResults()
{
- recordsRetrieved++;
- var dbNameField = sdr.GetString(0);
- var testIntField = sdr.GetFieldValue(1);
- var testBigIntField = sdr.GetFieldValue(2);
- Logger.Log("RecordRetrieved: dbNameField: {0}, TestIntField: {1}, TestBigIntField: {2}, RecordCount: {3}",
- dbNameField, testIntField, testBigIntField, recordsRetrieved);
- }
+ var query = GetPartialFailureQuery();
+ var e = TestSelectFailure(query, MultiShardExecutionPolicy.CompleteResults);
- Assert.AreEqual(recordsRetrieved, 9);
- }
+ // Exactly one should have failed
+ Assert.AreEqual(1, e.InnerExceptions.Count);
+ }
- ///
- /// Basic test for ensuring that we include/don't include the $ShardName pseudo column as desired.
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestShardNamePseudoColumnOption()
- {
- var pseudoColumnOptions = new bool[2];
- pseudoColumnOptions[0] = true;
- pseudoColumnOptions[1] = false;
-
- // do the loop over the options.
- // add the excpetion handling when referencing the pseudo column
- //
- foreach (var pseudoColumnPresent in pseudoColumnOptions)
+ ///
+ /// Gets a command that fails on one shard, but succeeds on others
+ ///
+ private string GetPartialFailureQuery()
{
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
- cmd.CommandType = CommandType.Text;
+ var shardLocations = _shardMap.GetShards().Select(s => s.Location);
- cmd.ExecutionPolicy = MultiShardExecutionPolicy.CompleteResults;
- cmd.ExecutionOptions = pseudoColumnPresent ? MultiShardExecutionOptions.IncludeShardNameColumn :
- MultiShardExecutionOptions.None;
- using var sdr = cmd.ExecuteReader(CommandBehavior.Default);
- Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
+ // Pick an arbitrary one of those shards
+ var chosenShardLocation = shardLocations.First();
- var recordsRetrieved = 0;
+ // This query assumes that the chosen shard location's db name is distinct from all others
+ // In other words, only one shard location should have a database equal to the chosen location
+ Assert.AreEqual(1, shardLocations.Count(l => l.Database.Equals(chosenShardLocation.Database)));
- var expectedFieldCount = pseudoColumnPresent ? 4 : 3;
- var expectedVisibleFieldCount = pseudoColumnPresent ? 4 : 3;
- Assert.AreEqual(expectedFieldCount, sdr.FieldCount);
- Assert.AreEqual(expectedVisibleFieldCount, sdr.VisibleFieldCount);
+ // We also assume that there is more than one shard
+ AssertExtensions.AssertGreaterThan(1, shardLocations.Count());
- while (sdr.Read())
- {
- recordsRetrieved++;
- var dbNameField = sdr.GetString(0);
- var testIntField = sdr.GetFieldValue(1);
- var testBigIntField = sdr.GetFieldValue(2);
+ // The command will fail only on the chosen shard
+ return string.Format("if db_name() = '{0}' raiserror('blah', 16, 0) else select 1",
+ shardLocations.First().Database);
+ }
- try
+ ///
+ /// Basic test for async api(s)
+ /// Also demonstrates the async pattern of this library
+ /// The Sync api is implicitly tested in MultiShardDataReaderTests::TestSimpleSelect
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestQueryShardsAsync()
+ {
+ // Create new sharded connection so we can test the OpenAsync call as well.
+ //
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
{
- var shardIdPseudoColumn = sdr.GetFieldValue(3);
- if (!pseudoColumnPresent)
+ cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
+ cmd.CommandType = CommandType.Text;
+
+ using (var sdr = ExecAsync(conn, cmd).Result)
{
- Assert.Fail("Should not have been able to pull the pseudo column.");
+ var recordsRetrieved = 0;
+ while (sdr.Read())
+ {
+ recordsRetrieved++;
+ var dbNameField = sdr.GetString(0);
+ var testIntField = sdr.GetFieldValue(1);
+ var testBigIntField = sdr.GetFieldValue(2);
+ Logger.Log("RecordRetrieved: dbNameField: {0}, TestIntField: {1}, TestBigIntField: {2}, RecordCount: {3}",
+ dbNameField, testIntField, testBigIntField, recordsRetrieved);
+ }
+ Assert.AreEqual(recordsRetrieved, 9);
}
}
- catch (IndexOutOfRangeException)
+ }
+ }
+
+ ///
+ /// Basic test for ensuring that we include/don't include the $ShardName pseudo column as desired.
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestShardNamePseudoColumnOption()
+ {
+ var pseudoColumnOptions = new bool[2];
+ pseudoColumnOptions[0] = true;
+ pseudoColumnOptions[1] = false;
+
+ // do the loop over the options.
+ // add the excpetion handling when referencing the pseudo column
+ //
+ foreach (var pseudoColumnPresent in pseudoColumnOptions)
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
{
- if (pseudoColumnPresent)
+ using (var cmd = conn.CreateCommand())
{
- Assert.Fail("Should not have encountered an exception.");
+ cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
+ cmd.CommandType = CommandType.Text;
+
+ cmd.ExecutionPolicy = MultiShardExecutionPolicy.CompleteResults;
+ cmd.ExecutionOptions = pseudoColumnPresent ? MultiShardExecutionOptions.IncludeShardNameColumn :
+ MultiShardExecutionOptions.None;
+ using (var sdr = cmd.ExecuteReader(CommandBehavior.Default))
+ {
+ Assert.AreEqual(0, sdr.MultiShardExceptions.Count);
+
+ var recordsRetrieved = 0;
+
+ var expectedFieldCount = pseudoColumnPresent ? 4 : 3;
+ var expectedVisibleFieldCount = pseudoColumnPresent ? 4 : 3;
+ Assert.AreEqual(expectedFieldCount, sdr.FieldCount);
+ Assert.AreEqual(expectedVisibleFieldCount, sdr.VisibleFieldCount);
+
+ while (sdr.Read())
+ {
+ recordsRetrieved++;
+ var dbNameField = sdr.GetString(0);
+ var testIntField = sdr.GetFieldValue(1);
+ var testBigIntField = sdr.GetFieldValue(2);
+
+ try
+ {
+ var shardIdPseudoColumn = sdr.GetFieldValue(3);
+ if (!pseudoColumnPresent)
+ {
+ Assert.Fail("Should not have been able to pull the pseudo column.");
+ }
+ }
+ catch (IndexOutOfRangeException)
+ {
+ if (pseudoColumnPresent)
+ {
+ Assert.Fail("Should not have encountered an exception.");
+ }
+ }
+ }
+
+ Assert.AreEqual(recordsRetrieved, 9);
+ }
}
}
}
-
- Assert.AreEqual(recordsRetrieved, 9);
}
- }
- ///
- /// Basic test for ensuring that we don't fail due to a schema mismatch on the shards.
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestSchemaMismatchErrorPropagation()
- {
- // First we need to alter the schema on one of the shards - we'll choose the last one.
- //
- var origColName = "Test_bigint_Field";
- var newColName = "ModifiedName";
-
- MultiShardTestUtils.ChangeColumnNameOnShardedTable(2, origColName, newColName);
-
- // Then create new sharded connection so we can test the error handling logic.
- // We'll wrap this all in a try-catch-finally block so that we can change the schema back
- // to what the other tests will expect it to be in the finally.
- //
- try
+ ///
+ /// Basic test for ensuring that we don't fail due to a schema mismatch on the shards.
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestSchemaMismatchErrorPropagation()
{
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- // Need to do a SELECT * in order to get the column name error as a schema mismatcherror. If we name it explicitly
- // we will get a command execution error instead.
+ // First we need to alter the schema on one of the shards - we'll choose the last one.
//
- cmd.CommandText = "SELECT * FROM ConsistentShardedTable";
- cmd.CommandType = CommandType.Text;
+ var origColName = "Test_bigint_Field";
+ var newColName = "ModifiedName";
- using var sdr = ExecAsync(conn, cmd).Result;
- // The number of errors we have depends on which shard executed first.
- // So, we know it should be 1 OR 2.
+ MultiShardTestUtils.ChangeColumnNameOnShardedTable(2, origColName, newColName);
+
+ // Then create new sharded connection so we can test the error handling logic.
+ // We'll wrap this all in a try-catch-finally block so that we can change the schema back
+ // to what the other tests will expect it to be in the finally.
//
- Assert.IsTrue(
-sdr.MultiShardExceptions.Count is 1 or 2,
- string.Format("Expected 1 or 2 execution erros, but saw {0}", sdr.MultiShardExceptions.Count));
+ try
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
+ {
+ // Need to do a SELECT * in order to get the column name error as a schema mismatcherror. If we name it explicitly
+ // we will get a command execution error instead.
+ //
+ cmd.CommandText = "SELECT * FROM ConsistentShardedTable";
+ cmd.CommandType = CommandType.Text;
- var recordsRetrieved = 0;
- while (sdr.Read())
+ using (var sdr = ExecAsync(conn, cmd).Result)
+ {
+ // The number of errors we have depends on which shard executed first.
+ // So, we know it should be 1 OR 2.
+ //
+ Assert.IsTrue(
+ sdr.MultiShardExceptions.Count == 1 || sdr.MultiShardExceptions.Count == 2,
+ string.Format("Expected 1 or 2 execution erros, but saw {0}", sdr.MultiShardExceptions.Count));
+
+ var recordsRetrieved = 0;
+ while (sdr.Read())
+ {
+ recordsRetrieved++;
+ var dbNameField = sdr.GetString(0);
+ }
+
+ // We should see 9 records less 3 for each one that had a schema error.
+ var expectedRecords = 9 - (3 * sdr.MultiShardExceptions.Count);
+
+ Assert.AreEqual(recordsRetrieved, expectedRecords);
+ }
+ }
+ }
+ }
+ finally
{
- recordsRetrieved++;
- var dbNameField = sdr.GetString(0);
+ MultiShardTestUtils.ChangeColumnNameOnShardedTable(2, newColName, origColName);
}
-
- // We should see 9 records less 3 for each one that had a schema error.
- var expectedRecords = 9 - (3 * sdr.MultiShardExceptions.Count);
-
- Assert.AreEqual(recordsRetrieved, expectedRecords);
- }
- finally
- {
- MultiShardTestUtils.ChangeColumnNameOnShardedTable(2, newColName, origColName);
}
- }
-
- private async Task ExecAsync(MultiShardConnection conn, MultiShardCommand cmd)
- {
- cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
- cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
- return await cmd.ExecuteReaderAsync(CommandBehavior.Default, CancellationToken.None);
- }
-
- ///
- /// Try connecting to a non-existant shard
- /// Verify exception is propagated to the user
- ///
- [TestMethod]
- [ExpectedException(typeof(SqlException))]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestQueryShardsInvalidConnectionSync()
- {
- var badShard = new ShardLocation("badLocation", "badDatabase");
- var bldr = new SqlConnectionStringBuilder
+ private async Task ExecAsync(MultiShardConnection conn, MultiShardCommand cmd)
{
- DataSource = badShard.DataSource,
- InitialCatalog = badShard.Database
- };
- var badConn = new SqlConnection(bldr.ConnectionString);
- try
- {
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- conn.GetShardConnections().Add(new Tuple(badShard,
- badConn));
- using var cmd = conn.CreateCommand();
- cmd.CommandText = "select 1";
- _ = cmd.ExecuteReader();
+ cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
+ cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
+
+ return await cmd.ExecuteReaderAsync(CommandBehavior.Default, CancellationToken.None);
}
- catch (Exception ex)
+
+ ///
+ /// Try connecting to a non-existant shard
+ /// Verify exception is propagated to the user
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(SqlException))]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestQueryShardsInvalidConnectionSync()
{
- if (ex is MultiShardAggregateException maex)
+ var badShard = new ShardLocation("badLocation", "badDatabase");
+ var bldr = new SqlConnectionStringBuilder
{
- Logger.Log("Exception encountered: " + maex.ToString());
- throw ((MultiShardException)maex.InnerException).InnerException;
+ DataSource = badShard.DataSource,
+ InitialCatalog = badShard.Database
+ };
+ var badConn = new SqlConnection(bldr.ConnectionString);
+ try
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ conn.GetShardConnections().Add(new Tuple(badShard, badConn));
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = "select 1";
+ _ = cmd.ExecuteReader();
+ }
+ }
}
+ catch (Exception ex)
+ {
+ if (ex is MultiShardAggregateException maex)
+ {
+ Logger.Log("Exception encountered: " + maex.ToString());
+ throw ((MultiShardException)maex.InnerException).InnerException;
+ }
- throw;
+ throw;
+ }
}
- }
- ///
- /// Tests passing a tvp as a param
- /// using a datatable
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestQueryShardsTvpParam()
- {
- try
+ ///
+ /// Tests passing a tvp as a param
+ /// using a datatable
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestQueryShardsTvpParam()
{
- // Install schema
- var createTbl =
-@"
+ try
+ {
+ // Install schema
+ var createTbl =
+ @"
CREATE TABLE dbo.PageView
(
PageViewID BIGINT NOT NULL,
@@ -560,8 +606,8 @@ CREATE TYPE dbo.PageViewTableType AS TABLE
(
PageViewID BIGINT NOT NULL
);";
- var createProc =
-@"CREATE PROCEDURE dbo.procMergePageView
+ var createProc =
+ @"CREATE PROCEDURE dbo.procMergePageView
@Display dbo.PageViewTableType READONLY
AS
BEGIN
@@ -571,79 +617,81 @@ USING @Display AS S
WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END";
- using (var cmd = _shardConnection.CreateCommand())
- {
- cmd.CommandText = createTbl;
- cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
- _ = cmd.ExecuteReader();
-
- cmd.CommandText = createProc;
- cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
- }
+ using (var cmd = _shardConnection.CreateCommand())
+ {
+ cmd.CommandText = createTbl;
+ cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
+ _ = cmd.ExecuteReader();
- Logger.Log("Schema installed..");
+ cmd.CommandText = createProc;
+ cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
+ }
- // Create the data table
- var table = new DataTable();
- _ = table.Columns.Add("PageViewID", typeof(long));
- var idCount = 3;
- for (var i = 0; i < idCount; i++)
- {
- _ = table.Rows.Add(i);
- }
+ Logger.Log("Schema installed..");
- // Execute the command
- using (var cmd = _shardConnection.CreateCommand())
- {
- cmd.CommandType = CommandType.StoredProcedure;
- cmd.CommandText = "dbo.procMergePageView";
+ // Create the data table
+ var table = new DataTable();
+ _ = table.Columns.Add("PageViewID", typeof(long));
+ var idCount = 3;
+ for (var i = 0; i < idCount; i++)
+ {
+ _ = table.Rows.Add(i);
+ }
- var param = new SqlParameter("@Display", table)
+ // Execute the command
+ using (var cmd = _shardConnection.CreateCommand())
{
- TypeName = "dbo.PageViewTableType",
- SqlDbType = SqlDbType.Structured
- };
- _ = cmd.Parameters.Add(param);
+ cmd.CommandType = CommandType.StoredProcedure;
+ cmd.CommandText = "dbo.procMergePageView";
- cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
- cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
- }
+ var param = new SqlParameter("@Display", table)
+ {
+ TypeName = "dbo.PageViewTableType",
+ SqlDbType = SqlDbType.Structured
+ };
+ _ = cmd.Parameters.Add(param);
+
+ cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
+ cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
+ }
- Logger.Log("Command executed..");
+ Logger.Log("Command executed..");
- using (var cmd = _shardConnection.CreateCommand())
- {
- // Validate that the pageviewcount was updated
- cmd.CommandText = "select PageViewCount from PageView";
- cmd.CommandType = CommandType.Text;
- cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
- cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
- using var sdr = cmd.ExecuteReader(CommandBehavior.Default);
- while (sdr.Read())
+ using (var cmd = _shardConnection.CreateCommand())
{
- var pageCount = (long)sdr["PageViewCount"];
- Logger.Log("Page view count: {0} obtained from shard: {1}", pageCount, sdr.GetFieldValue(1));
- Assert.AreEqual(2, pageCount);
+ // Validate that the pageviewcount was updated
+ cmd.CommandText = "select PageViewCount from PageView";
+ cmd.CommandType = CommandType.Text;
+ cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
+ cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
+ using (var sdr = cmd.ExecuteReader(CommandBehavior.Default))
+ {
+ while (sdr.Read())
+ {
+ var pageCount = (long)sdr["PageViewCount"];
+ Logger.Log("Page view count: {0} obtained from shard: {1}", pageCount, sdr.GetFieldValue(1));
+ Assert.AreEqual(2, pageCount);
+ }
+ }
}
}
- }
- catch (Exception ex)
- {
- if (ex is AggregateException aex)
+ catch (Exception ex)
{
- Logger.Log("Exception encountered: {0}", aex.InnerException.ToString());
+ if (ex is AggregateException aex)
+ {
+ Logger.Log("Exception encountered: {0}", aex.InnerException.ToString());
+ }
+ else
+ {
+ Logger.Log(ex.Message);
+ }
+
+ throw;
}
- else
+ finally
{
- Logger.Log(ex.Message);
- }
-
- throw;
- }
- finally
- {
- var dropSchema =
-@"if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[procMergePageView]') and objectproperty(id, N'IsProcedure') = 1)
+ var dropSchema =
+ @"if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[procMergePageView]') and objectproperty(id, N'IsProcedure') = 1)
begin
drop procedure dbo.procMergePageView
end
@@ -655,351 +703,374 @@ drop table dbo.Pageview
begin
drop type dbo.PageViewTableType
end";
- using var cmd = _shardConnection.CreateCommand();
- cmd.CommandText = dropSchema;
- cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
+ using (var cmd = _shardConnection.CreateCommand())
+ {
+ cmd.CommandText = dropSchema;
+ cmd.ExecuteNonQueryAsync(CancellationToken.None, MultiShardExecutionPolicy.PartialResults).Wait();
+ }
+ }
}
- }
- ///
- /// Verifies that the command cancellation events are fired
- /// upon cancellation of a command that is in progress
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestQueryShardsCommandCancellationHandler()
- {
- var cancelledShards = new List();
- var cts = new CancellationTokenSource();
-
- using var cmd = _shardConnection.CreateCommand();
- var barrier = new Barrier(cmd.Connection.Shards.Count() + 1);
+ ///
+ /// Verifies that the command cancellation events are fired
+ /// upon cancellation of a command that is in progress
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestQueryShardsCommandCancellationHandler()
+ {
+ var cancelledShards = new List();
+ var cts = new CancellationTokenSource();
- // If the threads don't meet the barrier by this time, then give up and fail the test
- var barrierTimeout = TimeSpan.FromSeconds(10);
+ using (var cmd = _shardConnection.CreateCommand())
+ {
+ var barrier = new Barrier(cmd.Connection.Shards.Count() + 1);
- cmd.CommandText = "WAITFOR DELAY '00:01:00'";
- cmd.CommandTimeoutPerShard = 12;
+ // If the threads don't meet the barrier by this time, then give up and fail the test
+ var barrierTimeout = TimeSpan.FromSeconds(10);
- cmd.ShardExecutionCanceled += (obj, args) =>
- {
- cancelledShards.Add(args.ShardLocation);
- };
+ cmd.CommandText = "WAITFOR DELAY '00:01:00'";
+ cmd.CommandTimeoutPerShard = 12;
- cmd.ShardExecutionBegan += (obj, args) =>
- {
- // If ShardExecutionBegan were only signaled by one thread,
- // then this would hang forever.
- _ = barrier.SignalAndWait(barrierTimeout);
- };
+ cmd.ShardExecutionCanceled += (obj, args) =>
+ {
+ cancelledShards.Add(args.ShardLocation);
+ };
- Task cmdTask = cmd.ExecuteReaderAsync(cts.Token);
+ cmd.ShardExecutionBegan += (obj, args) =>
+ {
+ // If ShardExecutionBegan were only signaled by one thread,
+ // then this would hang forever.
+ _ = barrier.SignalAndWait(barrierTimeout);
+ };
- var syncronized = barrier.SignalAndWait(barrierTimeout);
- Assert.IsTrue(syncronized);
+ Task cmdTask = cmd.ExecuteReaderAsync(cts.Token);
- // Cancel the command once execution begins
- // Sleeps are bad but this is just to really make sure
- // sqlclient has had a chance to begin command execution
- // Will not effect the test outcome
- Thread.Sleep(TimeSpan.FromSeconds(2));
- cts.Cancel();
+ var syncronized = barrier.SignalAndWait(barrierTimeout);
+ Assert.IsTrue(syncronized);
- // Validate that the task was cancelled
- _ = AssertExtensions.WaitAndAssertThrows(cmdTask);
+ // Cancel the command once execution begins
+ // Sleeps are bad but this is just to really make sure
+ // sqlclient has had a chance to begin command execution
+ // Will not effect the test outcome
+ Thread.Sleep(TimeSpan.FromSeconds(2));
+ cts.Cancel();
- // Validate that the cancellation event was fired for all shards
- var allShards = _shardConnection.GetShardConnections().Select(l => l.Item1).ToList();
- CollectionAssert.AreEquivalent(allShards, cancelledShards, "Expected command canceled event to be fired for all shards!");
- }
+ // Validate that the task was cancelled
+ _ = AssertExtensions.WaitAndAssertThrows(cmdTask);
- ///
- /// Close the connection to one of the shards behind
- /// MultiShardConnection's back. Verify that we reopen the connection with the built-in retry policy
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestQueryShardsInvalidShardStateSync()
- {
- // Get a shard and close it's connection
- var shardSqlConnections = _shardConnection.GetShardConnections();
- shardSqlConnections[1].Item2.Close();
+ // Validate that the cancellation event was fired for all shards
+ var allShards = _shardConnection.GetShardConnections().Select(l => l.Item1).ToList();
+ CollectionAssert.AreEquivalent(allShards, cancelledShards, "Expected command canceled event to be fired for all shards!");
+ }
+ }
- try
+ ///
+ /// Close the connection to one of the shards behind
+ /// MultiShardConnection's back. Verify that we reopen the connection with the built-in retry policy
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestQueryShardsInvalidShardStateSync()
{
- // Execute
- using var cmd = _shardConnection.CreateCommand();
- cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
- cmd.CommandType = CommandType.Text;
+ // Get a shard and close it's connection
+ var shardSqlConnections = _shardConnection.GetShardConnections();
+ shardSqlConnections[1].Item2.Close();
- using var sdr = cmd.ExecuteReader();
- }
- catch (Exception ex)
- {
- if (ex is AggregateException aex)
+ try
{
- Logger.Log("Exception encountered: " + ex.Message);
- throw aex.InnerExceptions.FirstOrDefault((e) => e is InvalidOperationException);
+ // Execute
+ using (var cmd = _shardConnection.CreateCommand())
+ {
+ cmd.CommandText = "SELECT dbNameField, Test_int_Field, Test_bigint_Field FROM ConsistentShardedTable";
+ cmd.CommandType = CommandType.Text;
+
+ using (var sdr = cmd.ExecuteReader())
+ { }
+ }
}
+ catch (Exception ex)
+ {
+ if (ex is AggregateException aex)
+ {
+ Logger.Log("Exception encountered: " + ex.Message);
+ throw aex.InnerExceptions.FirstOrDefault((e) => e is InvalidOperationException);
+ }
- throw;
+ throw;
+ }
}
- }
-
- ///
- /// Validate the MultiShardConnectionString's connectionString param.
- /// - Shouldn't be null
- /// - No DataSource/InitialCatalog should be set
- /// - ApplicationName should be enhanced with a MSQ library
- /// specific suffix and should be capped at 128 chars
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestInvalidMultiShardConnectionString()
- {
- MultiShardConnection conn;
- try
+ ///
+ /// Validate the MultiShardConnectionString's connectionString param.
+ /// - Shouldn't be null
+ /// - No DataSource/InitialCatalog should be set
+ /// - ApplicationName should be enhanced with a MSQ library
+ /// specific suffix and should be capped at 128 chars
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestInvalidMultiShardConnectionString()
{
- conn = new MultiShardConnection(_shardMap.GetShards(), connectionString: null);
- }
- catch (Exception ex)
- {
- Assert.IsTrue(ex is ArgumentNullException, "Expected ArgumentNullException!");
- }
+ MultiShardConnection conn;
- try
- {
- conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardMapManagerConnectionString);
- }
- catch (Exception ex)
- {
- Assert.IsTrue(ex is ArgumentException, "Expected ArgumentException!");
- }
+ try
+ {
+ conn = new MultiShardConnection(_shardMap.GetShards(), connectionString: null);
+ }
+ catch (Exception ex)
+ {
+ Assert.IsTrue(ex is ArgumentNullException, "Expected ArgumentNullException!");
+ }
- // Validate that the ApplicationName is updated properly
- var applicationStringBldr = new StringBuilder();
- for (var i = 0; i < ApplicationNameHelper.MaxApplicationNameLength; i++)
- {
- _ = applicationStringBldr.Append('x');
- }
+ try
+ {
+ conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardMapManagerConnectionString);
+ }
+ catch (Exception ex)
+ {
+ Assert.IsTrue(ex is ArgumentException, "Expected ArgumentException!");
+ }
- var applicationName = applicationStringBldr.ToString();
- var connStringBldr = new SqlConnectionStringBuilder(MultiShardTestUtils.ShardConnectionString)
- {
- ApplicationName = applicationName
- };
- conn = new MultiShardConnection(_shardMap.GetShards(), connStringBldr.ConnectionString);
-
- var updatedApplicationName = new SqlConnectionStringBuilder
- (conn.GetShardConnections()[0].Item2.ConnectionString).ApplicationName;
- Assert.IsTrue(updatedApplicationName.Length == ApplicationNameHelper.MaxApplicationNameLength &&
- updatedApplicationName.EndsWith(MultiShardConnection.ApplicationNameSuffix), "ApplicationName not appended with {0}!",
- MultiShardConnection.ApplicationNameSuffix);
- }
+ // Validate that the ApplicationName is updated properly
+ var applicationStringBldr = new StringBuilder();
+ for (var i = 0; i < ApplicationNameHelper.MaxApplicationNameLength; i++)
+ {
+ _ = applicationStringBldr.Append('x');
+ }
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestCreateConnectionWithNoShards()
- {
- using var conn = new MultiShardConnection(new Shard[] { }, string.Empty);
- Assert.Fail("Should have failed in the MultiShardConnection c-tor.");
- }
+ var applicationName = applicationStringBldr.ToString();
+ var connStringBldr = new SqlConnectionStringBuilder(MultiShardTestUtils.ShardConnectionString)
+ {
+ ApplicationName = applicationName
+ };
+ conn = new MultiShardConnection(_shardMap.GetShards(), connStringBldr.ConnectionString);
- ///
- /// Regression test for VSTS Bug# 3936154
- /// - Execute a command that will result in a failure in a loop
- /// - Without the fix (disabling the command behavior)s, the test will hang and timeout.
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- [Timeout(300000)]
- public void TestFailedCommandWithConnectionCloseCmdBehavior() => Parallel.For(0, 100, i =>
- {
- try
- {
- using var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = "select * from table_does_not_exist";
- cmd.CommandType = CommandType.Text;
-
- using var sdr = cmd.ExecuteReader();
- while (sdr.Read())
- {
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine("Encountered exception: {0} in iteration: {1}",
- ex.ToString(), i);
- }
- finally
- {
- Console.WriteLine("Completed execution of iteration: {0}", i);
- }
- });
+ var updatedApplicationName = new SqlConnectionStringBuilder
+ (conn.GetShardConnections()[0].Item2.ConnectionString).ApplicationName;
+ Assert.IsTrue(updatedApplicationName.Length == ApplicationNameHelper.MaxApplicationNameLength &&
+ updatedApplicationName.EndsWith(MultiShardConnection.ApplicationNameSuffix), "ApplicationName not appended with {0}!",
+ MultiShardConnection.ApplicationNameSuffix);
+ }
- ///
- /// This test induces failures via a ProxyServer in order to validate that:
- /// a) we are handling reader failures as expected, and
- /// b) we get all-or-nothing semantics on our reads from a single row
- ///
- [TestMethod]
- [TestCategory("ExcludeFromGatedCheckin")]
- public void TestShardResultFailures()
- {
- var proxyServer = GetProxyServer();
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestCreateConnectionWithNoShards()
+ {
+ using (var conn = new MultiShardConnection(new Shard[] { }, string.Empty))
+ {
+ Assert.Fail("Should have failed in the MultiShardConnection c-tor.");
+ }
+ }
- try
+ ///
+ /// Regression test for VSTS Bug# 3936154
+ /// - Execute a command that will result in a failure in a loop
+ /// - Without the fix (disabling the command behavior)s, the test will hang and timeout.
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ [Timeout(300000)]
+ public void TestFailedCommandWithConnectionCloseCmdBehavior() =>
+ Parallel.For(0, 100, i =>
+ {
+ try
+ {
+ using (var conn = new MultiShardConnection(_shardMap.GetShards(), MultiShardTestUtils.ShardConnectionString))
+ {
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = "select * from table_does_not_exist";
+ cmd.CommandType = CommandType.Text;
+
+ using (var sdr = cmd.ExecuteReader())
+ {
+ while (sdr.Read())
+ {
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Encountered exception: {0} in iteration: {1}",
+ ex.ToString(), i);
+ }
+ finally
+ {
+ Console.WriteLine("Completed execution of iteration: {0}", i);
+ }
+ });
+
+ ///
+ /// This test induces failures via a ProxyServer in order to validate that:
+ /// a) we are handling reader failures as expected, and
+ /// b) we get all-or-nothing semantics on our reads from a single row
+ ///
+ [TestMethod]
+ [TestCategory("ExcludeFromGatedCheckin")]
+ public void TestShardResultFailures()
{
- // Start up the proxy server. Do it in a try so we can shut it down in the finally.
- // Also, we have to generate the proxyShardconnections *AFTER* we start up the server
- // so that we know what port the proxy is listening on. More on the placement
- // of the connection generation below.
- //
- proxyServer.Start();
+ var proxyServer = GetProxyServer();
- // PreKillReads is the number of successful reads to perform before killing
- // all the connections. We start at 0 to test the no failure case as well.
- //
- for (var preKillReads = 0; preKillReads <= 10; preKillReads++)
+ try
{
- // Additionally, since we are running inside a loop, we need to regenerate the proxy shard connections each time
- // so that we don't re-use dead connections. If we do that we will end up hung in the read call.
+ // Start up the proxy server. Do it in a try so we can shut it down in the finally.
+ // Also, we have to generate the proxyShardconnections *AFTER* we start up the server
+ // so that we know what port the proxy is listening on. More on the placement
+ // of the connection generation below.
//
- var proxyShardConnections = GetProxyShardConnections(proxyServer);
- using var conn = new MultiShardConnection(proxyShardConnections);
- using var cmd = conn.CreateCommand();
- cmd.CommandText = "SELECT db_name() as dbName1, REPLICATE(db_name(), 1000) as longExpr, db_name() as dbName2 FROM ConsistentShardedTable";
- cmd.CommandType = CommandType.Text;
-
- cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
- cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
-
- using var sdr = cmd.ExecuteReader(CommandBehavior.Default);
- var tuplesRead = 0;
+ proxyServer.Start();
- while (sdr.Read())
+ // PreKillReads is the number of successful reads to perform before killing
+ // all the connections. We start at 0 to test the no failure case as well.
+ //
+ for (var preKillReads = 0; preKillReads <= 10; preKillReads++)
{
- // Read part of the tuple first before killing the connections and
- // then attempting to read the rest of the tuple.
+ // Additionally, since we are running inside a loop, we need to regenerate the proxy shard connections each time
+ // so that we don't re-use dead connections. If we do that we will end up hung in the read call.
//
- tuplesRead++;
-
- try
+ var proxyShardConnections = GetProxyShardConnections(proxyServer);
+ using (var conn = new MultiShardConnection(proxyShardConnections))
{
- // The longExpr should contain the first dbName field multiple times.
- //
- var dbName1 = sdr.GetString(0);
- var longExpr = sdr.GetString(1);
- Assert.IsTrue(longExpr.Contains(dbName1));
-
- if (tuplesRead == preKillReads)
+ using (var cmd = conn.CreateCommand())
{
- proxyServer.KillAllConnections();
+ cmd.CommandText = "SELECT db_name() as dbName1, REPLICATE(db_name(), 1000) as longExpr, db_name() as dbName2 FROM ConsistentShardedTable";
+ cmd.CommandType = CommandType.Text;
+
+ cmd.ExecutionPolicy = MultiShardExecutionPolicy.PartialResults;
+ cmd.ExecutionOptions = MultiShardExecutionOptions.IncludeShardNameColumn;
+
+ using (var sdr = cmd.ExecuteReader(CommandBehavior.Default))
+ {
+ var tuplesRead = 0;
+
+ while (sdr.Read())
+ {
+ // Read part of the tuple first before killing the connections and
+ // then attempting to read the rest of the tuple.
+ //
+ tuplesRead++;
+
+ try
+ {
+ // The longExpr should contain the first dbName field multiple times.
+ //
+ var dbName1 = sdr.GetString(0);
+ var longExpr = sdr.GetString(1);
+ Assert.IsTrue(longExpr.Contains(dbName1));
+
+ if (tuplesRead == preKillReads)
+ {
+ proxyServer.KillAllConnections();
+ }
+
+ // The second dbName field should be the same as the first dbName field.
+ //
+ var dbName2 = sdr.GetString(2);
+ Assert.AreEqual(dbName1, dbName2);
+
+ // The shardId should contain both the first and the second dbName fields.
+ //
+ var shardId = sdr.GetString(3);
+ Assert.IsTrue(shardId.Contains(dbName1));
+ Assert.IsTrue(shardId.Contains(dbName2));
+ }
+ catch (Exception ex)
+ {
+ // We've seen some failures here due to an attempt to access a socket after it has
+ // been disposed. The only place where we are attempting to access the socket
+ // is in the call to proxyServer.KillAllConnections. Unfortunately, it's not clear
+ // what is causing that problem since it only appears to repro in the lab.
+ // I (errobins) would rather not blindly start changing things in the code (either
+ // our code above, our exception handling code here, or the proxyServer code) until
+ // we know which socket we are trying to access when we hit this problem.
+ // So, the first step I will take is to pull additional exception information
+ // so that we can see some more information about what went wrong the next time it repros.
+ //
+ Assert.Fail("Unexpected exception, rethrowing. Here is some info: \n Message: {0} \n Source: {1} \n StackTrace: {2}",
+ ex.Message, ex.Source, ex.StackTrace);
+ throw;
+ }
+ }
+
+ Assert.IsTrue((tuplesRead <= preKillReads) || (0 == preKillReads),
+ string.Format("Tuples read was {0}, Pre-kill reads was {1}", tuplesRead, preKillReads));
+ }
}
-
- // The second dbName field should be the same as the first dbName field.
- //
- var dbName2 = sdr.GetString(2);
- Assert.AreEqual(dbName1, dbName2);
-
- // The shardId should contain both the first and the second dbName fields.
- //
- var shardId = sdr.GetString(3);
- Assert.IsTrue(shardId.Contains(dbName1));
- Assert.IsTrue(shardId.Contains(dbName2));
- }
- catch (Exception ex)
- {
- // We've seen some failures here due to an attempt to access a socket after it has
- // been disposed. The only place where we are attempting to access the socket
- // is in the call to proxyServer.KillAllConnections. Unfortunately, it's not clear
- // what is causing that problem since it only appears to repro in the lab.
- // I (errobins) would rather not blindly start changing things in the code (either
- // our code above, our exception handling code here, or the proxyServer code) until
- // we know which socket we are trying to access when we hit this problem.
- // So, the first step I will take is to pull additional exception information
- // so that we can see some more information about what went wrong the next time it repros.
- //
- Assert.Fail("Unexpected exception, rethrowing. Here is some info: \n Message: {0} \n Source: {1} \n StackTrace: {2}",
- ex.Message, ex.Source, ex.StackTrace);
- throw;
}
}
-
- Assert.IsTrue((tuplesRead <= preKillReads) || (0 == preKillReads),
- string.Format("Tuples read was {0}, Pre-kill reads was {1}", tuplesRead, preKillReads));
+ }
+ finally
+ {
+ // Be sure to shut down the proxy server.
+ //
+ var proxyLog = proxyServer.EventLog.ToString();
+ Logger.Log(proxyLog);
+ proxyServer.Stop();
}
}
- finally
- {
- // Be sure to shut down the proxy server.
- //
- var proxyLog = proxyServer.EventLog.ToString();
- Logger.Log(proxyLog);
- proxyServer.Stop();
- }
- }
- ///
- /// Helper that sets up a proxy server for us and points it at our local host, 1433 SQL Server.
- ///
- ///
- /// The newly created proxy server for our local sql server host.
- ///
- ///
- /// Note that we are not inducing any network delay (the first arg). We coul dchange this if desired.
- ///
- private ProxyServer GetProxyServer()
- {
- var proxy = new ProxyServer(simulatedPacketDelay: 0, simulatedInDelay: true, simulatedOutDelay: true, bufferSize: 8192)
+ ///
+ /// Helper that sets up a proxy server for us and points it at our local host, 1433 SQL Server.
+ ///
+ ///
+ /// The newly created proxy server for our local sql server host.
+ ///
+ ///
+ /// Note that we are not inducing any network delay (the first arg). We coul dchange this if desired.
+ ///
+ private ProxyServer GetProxyServer()
{
- RemoteEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1433)
- };
+ var proxy = new ProxyServer(simulatedPacketDelay: 0, simulatedInDelay: true, simulatedOutDelay: true, bufferSize: 8192)
+ {
+ RemoteEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1433)
+ };
- return proxy;
- }
+ return proxy;
+ }
- ///
- /// Helper that provides us with ShardConnections based on the shard map (for the database), but routed through the proxy.
- ///
- /// The proxy to route the connections through.
- ///
- /// The List of {ShardLocation, DbConnection} tuples that we can use to instantiate our multi-shard connection.
- ///
- ///
- /// Since our shards all reside in the local instance we can just point them at a single proxy server. If we were using
- /// actual physically distributed shards, then I think we would need a separate proxy for each shard. We could
- /// augment these tests to use a separate proxy per shard, if we wanted, in order to be able to simulate
- /// a richer variety of failures. For now, we just simulate total failures of all shards.
- ///
- private List> GetProxyShardConnections(ProxyServer proxy)
- {
- // We'll do this by looking at our pre-existing connections and working from that.
- //
- var baseConnString = MultiShardTestUtils.ShardConnectionString.ToString();
- var rVal = new List>();
- foreach (var shard in _shardMap.GetShards())
+ ///
+ /// Helper that provides us with ShardConnections based on the shard map (for the database), but routed through the proxy.
+ ///
+ /// The proxy to route the connections through.
+ ///
+ /// The List of {ShardLocation, DbConnection} tuples that we can use to instantiate our multi-shard connection.
+ ///
+ ///
+ /// Since our shards all reside in the local instance we can just point them at a single proxy server. If we were using
+ /// actual physically distributed shards, then I think we would need a separate proxy for each shard. We could
+ /// augment these tests to use a separate proxy per shard, if we wanted, in order to be able to simulate
+ /// a richer variety of failures. For now, we just simulate total failures of all shards.
+ ///
+ private List> GetProxyShardConnections(ProxyServer proxy)
{
- // Location doesn't really matter, so just use the same one.
+ // We'll do this by looking at our pre-existing connections and working from that.
//
- var curLoc = shard.Location;
-
- // The connection, however, does matter, so set up a connection
- var builder = new SqlConnectionStringBuilder(baseConnString)
+ var baseConnString = MultiShardTestUtils.ShardConnectionString.ToString();
+ var rVal = new List>();
+ foreach (var shard in _shardMap.GetShards())
{
- DataSource = "localhost," + proxy.LocalPort,
- InitialCatalog = curLoc.Database
- };
+ // Location doesn't really matter, so just use the same one.
+ //
+ var curLoc = shard.Location;
- var curConn = new SqlConnection(builder.ToString());
+ // The connection, however, does matter, so set up a connection
+ var builder = new SqlConnectionStringBuilder(baseConnString)
+ {
+ DataSource = "localhost," + proxy.LocalPort,
+ InitialCatalog = curLoc.Database
+ };
- var curTuple = new Tuple(curLoc, curConn);
- rVal.Add(curTuple);
- }
+ var curConn = new SqlConnection(builder.ToString());
- return rVal;
+ var curTuple = new Tuple(curLoc, curConn);
+ rVal.Add(curTuple);
+ }
+
+ return rVal;
+ }
}
}
diff --git a/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs b/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs
index e16cb1b..902d03c 100644
--- a/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs
+++ b/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs
@@ -32,7 +32,7 @@ internal static class MultiShardTestUtils
///
/// User password to use when connecting to shards during a fanout query.
///
- private static string s_testPassword = "dogmat1C";
+ private static string s_testPassword = "J8X2ndQTZ8cvu1r";
///
/// Table name for the sharded table we will issue fanout queries against.
diff --git a/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj b/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj
index 9530734..7c1036d 100644
--- a/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj
+++ b/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj
@@ -1,6 +1,6 @@
- net6.0;net8.0
+ net6.0;net8.0;net481
true
false
0649;$(NoWarn)
@@ -8,7 +8,6 @@
-