From 4ae9534ac834bf91e17abf9cdf4614fd763f96ff Mon Sep 17 00:00:00 2001 From: Alex Ma Date: Wed, 16 Aug 2023 13:17:40 -0700 Subject: [PATCH] Added retry policy for sleeping serverless error for SqlConnections (#2155) --- .../BatchParser/ConnectSqlCmdCommand.cs | 4 +- .../ExecutionEngineCode/ExecutionEngine.cs | 1 + .../ReliableConnectionHelper.cs | 3 +- .../ReliableSqlConnection.cs | 8 ++- .../ReliableConnection/SqlRetryProviders.cs | 66 +++++++++++++++++ .../Agent/Jobs/AgentNotebookHelper.cs | 1 + .../Connection/ConnectionInfo.cs | 2 +- .../Connection/ConnectionService.cs | 70 +++++++++---------- .../Connection/ISqlConnectionFactory.cs | 4 +- .../Connection/SqlConnectionFactory.cs | 6 +- .../Scripting/ScriptAsScriptingOperation.cs | 1 + .../Scripting/SmoScriptingOperation.cs | 1 + .../Connection/ConnectionServiceTests.cs | 30 ++++---- .../QueryExecution/Common.cs | 3 +- .../Utility/TestObjects.cs | 3 +- 15 files changed, 140 insertions(+), 63 deletions(-) create mode 100644 src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/SqlRetryProviders.cs diff --git a/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ConnectSqlCmdCommand.cs b/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ConnectSqlCmdCommand.cs index 28e6ba47..3be8a80b 100644 --- a/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ConnectSqlCmdCommand.cs +++ b/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ConnectSqlCmdCommand.cs @@ -4,7 +4,6 @@ // using Microsoft.SqlServer.Management.Common; using System; -using System.Data; using System.Data.Common; using Microsoft.Data.SqlClient; @@ -64,13 +63,14 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser return null; } - IDbConnection conn = null; + SqlConnection conn = null; try { string connString = ci.ConnectionString; connString += ";Pooling=false"; //turn off connection pooling (this is done in other tools so following the same pattern) conn = new SqlConnection(connString); + conn.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider(); conn.Open(); return conn as DbConnection; diff --git a/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ExecutionEngineCode/ExecutionEngine.cs b/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ExecutionEngineCode/ExecutionEngine.cs index 350c8e04..1e68ee1b 100644 --- a/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ExecutionEngineCode/ExecutionEngine.cs +++ b/src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/ExecutionEngineCode/ExecutionEngine.cs @@ -922,6 +922,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode try { connection = new SqlConnection(connectionStringBuilder.ConnectionString); + connection.RetryLogicProvider = SqlRetryProviders.ServerlessDBRetryProvider(); connection.Open(); } catch (SqlException ex) diff --git a/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableConnectionHelper.cs b/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableConnectionHelper.cs index ea2bc879..e25c1f97 100644 --- a/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableConnectionHelper.cs +++ b/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableConnectionHelper.cs @@ -91,7 +91,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection connectionRetryPolicy = RetryPolicyFactory.CreateNoRetryPolicy(); } - ReliableSqlConnection connection = new ReliableSqlConnection(connectionString, connectionRetryPolicy, commandRetryPolicy, azureAccountToken); + ReliableSqlConnection connection = new ReliableSqlConnection(connectionString, connectionRetryPolicy, commandRetryPolicy, azureAccountToken, SqlRetryProviders.ServerlessDBRetryProvider()); try { @@ -426,6 +426,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection Validate.IsNotNull(nameof(readResult), readResult); using (var sqlConnection = new SqlConnection(connectionString)) { + sqlConnection.RetryLogicProvider = SqlRetryProviders.ServerlessDBRetryProvider(); sqlConnection.Open(); ExecuteReader(sqlConnection, commandText, readResult, initializeCommand, catchException); } diff --git a/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableSqlConnection.cs b/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableSqlConnection.cs index 9403fa5d..8801d348 100644 --- a/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableSqlConnection.cs +++ b/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/ReliableSqlConnection.cs @@ -60,9 +60,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection /// The connection string used to open the SQL Azure database. /// The retry policy defining whether to retry a request if a connection fails to be established. /// The retry policy defining whether to retry a request if a command fails to be executed. - public ReliableSqlConnection(string connectionString, RetryPolicy connectionRetryPolicy, RetryPolicy commandRetryPolicy, string azureAccountToken) + /// Optional retry provider to handle errors in a special way + public ReliableSqlConnection(string connectionString, RetryPolicy connectionRetryPolicy, RetryPolicy commandRetryPolicy, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider = null) { _underlyingConnection = new SqlConnection(connectionString); + + if (retryProvider != null) { + _underlyingConnection.RetryLogicProvider = retryProvider; + } + _connectionRetryPolicy = connectionRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); _commandRetryPolicy = commandRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); diff --git a/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/SqlRetryProviders.cs b/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/SqlRetryProviders.cs new file mode 100644 index 00000000..b32a75e5 --- /dev/null +++ b/src/Microsoft.SqlTools.ManagedBatchParser/ReliableConnection/SqlRetryProviders.cs @@ -0,0 +1,66 @@ +// +// 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.Collections.Generic; +using Microsoft.Data.SqlClient; +using Microsoft.SqlTools.BatchParser.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection +{ + public static class SqlRetryProviders + { + /// + /// Approved list of transient errors that require additional time to wait before connecting again. + /// + private static readonly HashSet _retryableServerlessConnectivityError; + + /// + /// Max intervals between retries in seconds to wake up serverless instances. + /// + private const int _serverlessMaxIntervalTime = 30; + + /// + /// Maximum number of retries to wake up serverless instances. + /// + private const int _serverlessMaxRetries = 4; + + static SqlRetryProviders() + { + _retryableServerlessConnectivityError = new HashSet + { + //// SQL Error Code: 40613 + //// Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer + //// support, and provide them the session tracing ID of ZZZZZ. + 40613, + }; + } + + /// + /// Wait for SqlConnection to handle sleeping serverless instances (allows for them to wake up, otherwise it will result in errors). + /// + public static SqlRetryLogicBaseProvider ServerlessDBRetryProvider() + { + var serverlessRetryLogic = new SqlRetryLogicOption + { + NumberOfTries = _serverlessMaxRetries, + MaxTimeInterval = TimeSpan.FromSeconds(_serverlessMaxIntervalTime), + DeltaTime = TimeSpan.FromSeconds(1), + TransientErrors = _retryableServerlessConnectivityError + }; + + var provider = SqlConfigurableRetryFactory.CreateFixedRetryProvider(serverlessRetryLogic); + + provider.Retrying += (object s, SqlRetryingEventArgs e) => + { + Logger.Information($"attempt {e.RetryCount + 1} - current delay time:{e.Delay}"); + Logger.Information((e.Exceptions[e.Exceptions.Count - 1] is SqlException ex) ? $"{ex.Number}-{ex.Message}" : $"{e.Exceptions[e.Exceptions.Count - 1].Message}"); + }; + + return provider; + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs index 91d8284c..b42d7c64 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs @@ -43,6 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent } using (SqlConnection connection = new SqlConnection(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails))) { + connection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider(); connection.Open(); using (SqlCommand sqlQueryCommand = new SqlCommand(sqlQuery, connection)) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionInfo.cs index 6d902f4b..ea5cff08 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionInfo.cs @@ -52,7 +52,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// /// Properties used for creating/opening the SQL connection. /// - public ConnectionDetails ConnectionDetails { get; private set; } + public ConnectionDetails ConnectionDetails { get; set; } /// /// A map containing all connections to the database that are associated with diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 5b0e3f1c..9029abf7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -43,13 +43,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection public const int MaxTolerance = 2 * 60; // two minutes - standard tolerance across ADS for AAD tokens - public const int MaxServerlessReconnectTries = 5; // Max number of tries to wait for a serverless database to start up when its paused before giving up. - // SQL Error Code Constants // Referenced from: https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver16 private const int DoesNotMeetPWReqs = 18466; // Password does not meet complexity requirements. private const int PWCannotBeUsed = 18463; // Password cannot be used at this time. + // Default SQL constants (required to ensure connections such as serverless are able to wake up, connect, and retry properly). + private const int DefaultConnectTimeout = 30; + private const int DefaultCommandTimeout = 30; /// /// Singleton service instance @@ -385,7 +386,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection TrySetConnectionType(connectionParams); - connectionParams.Connection.ApplicationName = GetApplicationNameWithFeature(connectionParams.Connection.ApplicationName, connectionParams.Purpose); + // Fill in any details that are necessary (timeouts and application name) to ensure connection doesn't immediately disconnect if not specified (such as for serverless). + connectionParams.Connection = FillInDefaultDetailsForConnections(connectionParams.Connection, connectionParams.Purpose); + // If there is no ConnectionInfo in the map, create a new ConnectionInfo, // but wait until later when we are connected to add it to the map. ConnectionInfo connectionInfo; @@ -409,7 +412,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } // Try to open a connection with the given ConnectParams - ConnectionCompleteParams? response = await this.TryOpenConnectionWithRetry(connectionInfo, connectionParams); + ConnectionCompleteParams? response = await this.TryOpenConnection(connectionInfo, connectionParams); if (response != null) { return response; @@ -432,34 +435,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection return completeParams; } - private async Task TryOpenConnectionWithRetry(ConnectionInfo connectionInfo, ConnectParams connectionParams) - { - int counter = 0; - ConnectionCompleteParams? response = null; - while (counter <= MaxServerlessReconnectTries) - { - // The OpenAsync function used in TryOpenConnection does not retry when a database is sleeping. - // SqlClient will be implemented at a later time, which will have automatic retries. - response = await TryOpenConnection(connectionInfo, connectionParams); - // If a serverless database is sleeping, it will return this error number and will need to be retried. - // See here for details: https://docs.microsoft.com/en-us/azure/azure-sql/database/serverless-tier-overview?view=azuresql#connectivity - if (response?.ErrorNumber == 40613) - { - counter++; - if (counter != MaxServerlessReconnectTries) - { - Logger.Information($"Database for connection {connectionInfo.OwnerUri} is paused, retrying connection. Attempt #{counter}"); - } - } - else - { - // Every other response, we can stop. - break; - } - } - return response; - } - private void TryCloseConnectionTemporaryConnection(ConnectParams connectionParams, ConnectionInfo connectionInfo) { try @@ -667,6 +642,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection return serverEdition; } + internal static ConnectionDetails FillInDefaultDetailsForConnections(ConnectionDetails inputConnectionDetails, string featureName) { + ConnectionDetails newConnectionDetails = inputConnectionDetails; + + if(string.IsNullOrWhiteSpace(newConnectionDetails.ApplicationName)) + { + newConnectionDetails.ApplicationName = ApplicationName; + } + else + { + newConnectionDetails.ApplicationName = GetApplicationNameWithFeature(newConnectionDetails.ApplicationName, featureName); + } + + newConnectionDetails.ConnectTimeout = Math.Max(DefaultConnectTimeout, newConnectionDetails.ConnectTimeout ?? 0); + + newConnectionDetails.CommandTimeout = Math.Max(DefaultCommandTimeout, newConnectionDetails.CommandTimeout ?? 0); + + return newConnectionDetails; + } + /// /// Tries to create and open a connection with the given ConnectParams. /// @@ -688,8 +682,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection // build the connection string from the input parameters string connectionString = BuildConnectionString(connectionInfo.ConnectionDetails); - // create a sql connection instance - connection = connectionInfo.Factory.CreateSqlConnection(connectionString, connectionInfo.ConnectionDetails.AzureAccountToken); + // create a sql connection instance (with enabled serverless retry logic to handle sleeping serverless databases) + connection = connectionInfo.Factory.CreateSqlConnection(connectionString, connectionInfo.ConnectionDetails.AzureAccountToken, SqlRetryProviders.ServerlessDBRetryProvider()); connectionInfo.AddConnection(connectionParams.Type, connection); // Add a cancellation token source so that the connection OpenAsync() can be cancelled @@ -1894,9 +1888,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection // allow pooling connections for language service feature to improve intellisense connection retention and performance. bool shouldForceDisablePooling = !EnableConnectionPooling && featureName != Constants.LanguageServiceFeature; - // increase the connection and command timeout to at least 30 seconds and and build connection string - connInfo.ConnectionDetails.ConnectTimeout = Math.Max(30, connInfo.ConnectionDetails.ConnectTimeout ?? 0); - connInfo.ConnectionDetails.CommandTimeout = Math.Max(30, connInfo.ConnectionDetails.CommandTimeout ?? 0); // enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections connInfo.ConnectionDetails.PersistSecurityInfo = true; @@ -1905,7 +1896,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection { connInfo.ConnectionDetails.Pooling = false; } - connInfo.ConnectionDetails.ApplicationName = GetApplicationNameWithFeature(connInfo.ConnectionDetails.ApplicationName, featureName); + + // increase the connection and command timeout to at least 30 seconds and set application name. + connInfo.ConnectionDetails = FillInDefaultDetailsForConnections(connInfo.ConnectionDetails, featureName); // generate connection string string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails, shouldForceDisablePooling); @@ -1916,6 +1909,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection // open a dedicated binding server connection SqlConnection sqlConn = new SqlConnection(connectionString); + sqlConn.RetryLogicProvider = SqlRetryProviders.ServerlessDBRetryProvider(); // Fill in Azure authentication token if needed if (connInfo.ConnectionDetails.AzureAccountToken != null && connInfo.ConnectionDetails.AuthenticationType == AzureMFA) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ISqlConnectionFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ISqlConnectionFactory.cs index abb38a42..b80b2d5d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ISqlConnectionFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ISqlConnectionFactory.cs @@ -6,6 +6,7 @@ #nullable disable using System.Data.Common; +using Microsoft.Data.SqlClient; namespace Microsoft.SqlTools.ServiceLayer.Connection { @@ -17,6 +18,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// /// Create a new SQL Connection object /// - DbConnection CreateSqlConnection(string connectionString, string azureAccountToken); + /// Optional retry provider to handle errors in a special way + DbConnection CreateSqlConnection(string connectionString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider = null); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/SqlConnectionFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/SqlConnectionFactory.cs index 932fe5ea..9df1d034 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/SqlConnectionFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/SqlConnectionFactory.cs @@ -7,6 +7,7 @@ using System.Data.Common; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; +using Microsoft.Data.SqlClient; namespace Microsoft.SqlTools.ServiceLayer.Connection { @@ -20,11 +21,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// /// Creates a new SqlConnection object /// - public DbConnection CreateSqlConnection(string connectionString, string azureAccountToken) + /// Optional retry provider to handle errors in a special way + public DbConnection CreateSqlConnection(string connectionString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider = null) { RetryPolicy connectionRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy(); RetryPolicy commandRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy(); - return new ReliableSqlConnection(connectionString, connectionRetryPolicy, commandRetryPolicy, azureAccountToken); + return new ReliableSqlConnection(connectionString, connectionRetryPolicy, commandRetryPolicy, azureAccountToken, retryProvider); } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs index 7c45fba4..88585b28 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs @@ -47,6 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken) : base(parameters) { SqlConnection sqlConnection = new SqlConnection(this.Parameters.ConnectionString); + sqlConnection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider(); if (azureAccountToken != null) { sqlConnection.AccessToken = azureAccountToken; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Scripting/SmoScriptingOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/Scripting/SmoScriptingOperation.cs index a666ee3f..24abe388 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Scripting/SmoScriptingOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Scripting/SmoScriptingOperation.cs @@ -78,6 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting string serverName = null; using (SqlConnection connection = new SqlConnection(connectionString)) { + connection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider(); if (azureAccessToken != null) { connection.AccessToken = azureAccessToken; diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs index 33eb96ee..67590536 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs @@ -86,7 +86,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection }); var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockConnection.Object); @@ -152,7 +152,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection .Returns(() => Task.Run(() => { })); var mockFactory = new Mock(); - mockFactory.SetupSequence(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.SetupSequence(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockConnection.Object) .Returns(mockConnection2.Object); @@ -215,7 +215,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection }); var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockConnection.Object); @@ -304,7 +304,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection connectionMock.Setup(c => c.Database).Returns(expectedDbName); var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(connectionMock.Object); var connectionService = new ConnectionService(mockFactory.Object); @@ -345,8 +345,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection var dummySqlConnection = new TestSqlConnection(null); var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) - .Returns((string connString, string azureAccountToken) => + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((string connString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider) => { dummySqlConnection.ConnectionString = connString; SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(connString); @@ -1020,7 +1020,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection { // Setup mock connection factory to inject query results var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(CreateMockDbConnection(new[] { testdata })); var connectionService = new ConnectionService(mockFactory.Object); @@ -1654,8 +1654,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection var connection = new TestSqlConnection(null); var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) - .Returns((string connString, string azureAccountToken) => + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((string connString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider) => { connection.ConnectionString = connString; SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(connString); @@ -1704,8 +1704,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection var connection = mockConnection.Object; var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) - .Returns((string connString, string azureAccountToken) => + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((string connString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider) => { connection.ConnectionString = connString; SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(connString); @@ -1757,8 +1757,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection var connection = mockConnection.Object; var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) - .Returns((string connString, string azureAccountToken) => + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((string connString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider) => { connection.ConnectionString = connString; SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(connString); @@ -1846,7 +1846,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection { // Set up mock connection factory var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new TestSqlConnection(null)); var connectionService = new ConnectionService(mockFactory.Object); @@ -1865,7 +1865,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection }); // Validate that the connection factory gets called with details NOT including an account token - mockFactory.Verify(factory => factory.CreateSqlConnection(It.IsAny(), It.Is(accountToken => accountToken == azureAccountToken)), Times.Once()); + mockFactory.Verify(factory => factory.CreateSqlConnection(It.IsAny(), It.Is(accountToken => accountToken == azureAccountToken), It.IsAny()), Times.Once()); } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Common.cs index ec6a2480..d2d9d523 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Common.cs @@ -20,6 +20,7 @@ using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Microsoft.Data.SqlClient; using Moq; using Moq.Protected; using HostingProtocol = Microsoft.SqlTools.Hosting.Protocol; @@ -232,7 +233,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution private static ISqlConnectionFactory CreateMockFactory(TestResultSet[] data, bool throwOnExecute, bool throwOnRead) { var mockFactory = new Mock(); - mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny())) + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => CreateTestConnection(data, throwOnExecute, throwOnRead)); return mockFactory.Object; diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestObjects.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestObjects.cs index ded6a4f1..822d4c28 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestObjects.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestObjects.cs @@ -14,6 +14,7 @@ using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +using Microsoft.Data.SqlClient; using Moq; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility @@ -353,7 +354,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility /// public class TestSqlConnectionFactory : ISqlConnectionFactory { - public DbConnection CreateSqlConnection(string connectionString, string azureAccountToken) + public DbConnection CreateSqlConnection(string connectionString, string azureAccountToken, SqlRetryLogicBaseProvider retryProvider = null) { return new TestSqlConnection(null) {