mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Fix: SqlDw connection setup change (#221)
* adding sqldw check and caching * adding tests for caching layer * simplifying caching logic and fixing tests * cleaning up previous logic * removing locale files i accidently pushed
This commit is contained in:
@@ -17,17 +17,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class CachedServerInfo
|
internal static class CachedServerInfo
|
||||||
{
|
{
|
||||||
|
public enum CacheVariable {
|
||||||
|
IsSqlDw,
|
||||||
|
IsAzure
|
||||||
|
}
|
||||||
|
|
||||||
private struct CachedInfo
|
private struct CachedInfo
|
||||||
{
|
{
|
||||||
public bool IsAzure;
|
public bool IsAzure;
|
||||||
public DateTime LastUpdate;
|
public DateTime LastUpdate;
|
||||||
|
public bool IsSqlDw;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConcurrentDictionary<string, CachedInfo> _cache;
|
private static ConcurrentDictionary<string, CachedInfo> _cache;
|
||||||
private static object _cacheLock;
|
private static object _cacheLock;
|
||||||
private const int _maxCacheSize = 1024;
|
private const int _maxCacheSize = 1024;
|
||||||
private const int _deleteBatchSize = 512;
|
private const int _deleteBatchSize = 512;
|
||||||
|
|
||||||
private const int MinimalQueryTimeoutSecondsForAzure = 300;
|
private const int MinimalQueryTimeoutSecondsForAzure = 300;
|
||||||
|
|
||||||
static CachedServerInfo()
|
static CachedServerInfo()
|
||||||
@@ -70,19 +75,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
|
|
||||||
public static void AddOrUpdateIsAzure(IDbConnection connection, bool isAzure)
|
public static void AddOrUpdateIsAzure(IDbConnection connection, bool isAzure)
|
||||||
{
|
{
|
||||||
Validate.IsNotNull(nameof(connection), connection);
|
AddOrUpdateCache(connection, isAzure, CacheVariable.IsAzure);
|
||||||
|
|
||||||
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
|
|
||||||
AddOrUpdateIsAzure(builder.DataSource, isAzure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddOrUpdateIsAzure(string dataSource, bool isAzure)
|
public static void AddOrUpdateIsSqlDw(IDbConnection connection, bool isSqlDw)
|
||||||
|
{
|
||||||
|
AddOrUpdateCache(connection, isSqlDw, CacheVariable.IsSqlDw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddOrUpdateCache(IDbConnection connection, bool newState, CacheVariable cacheVar)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(connection), connection);
|
||||||
|
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
|
||||||
|
AddOrUpdateCache(builder.DataSource, newState, cacheVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void AddOrUpdateCache(string dataSource, bool newState, CacheVariable cacheVar)
|
||||||
{
|
{
|
||||||
Validate.IsNotNullOrWhitespaceString(nameof(dataSource), dataSource);
|
Validate.IsNotNullOrWhitespaceString(nameof(dataSource), dataSource);
|
||||||
CachedInfo info;
|
CachedInfo info;
|
||||||
bool hasFound = _cache.TryGetValue(dataSource, out info);
|
bool hasFound = _cache.TryGetValue(dataSource, out info);
|
||||||
|
|
||||||
if (hasFound && info.IsAzure == isAzure)
|
if ((cacheVar == CacheVariable.IsSqlDw && hasFound && info.IsSqlDw == newState) ||
|
||||||
|
(cacheVar == CacheVariable.IsAzure && hasFound && info.IsAzure == newState))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,13 +123,44 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.IsAzure = isAzure;
|
if (cacheVar == CacheVariable.IsSqlDw)
|
||||||
|
{
|
||||||
|
info.IsSqlDw = newState;
|
||||||
|
}
|
||||||
|
else if (cacheVar == CacheVariable.IsAzure)
|
||||||
|
{
|
||||||
|
info.IsAzure = newState;
|
||||||
|
}
|
||||||
info.LastUpdate = DateTime.UtcNow;
|
info.LastUpdate = DateTime.UtcNow;
|
||||||
_cache.AddOrUpdate(dataSource, info, (key, oldValue) => info);
|
_cache.AddOrUpdate(dataSource, info, (key, oldValue) => info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryGetIsSqlDw(IDbConnection connection, out bool isSqlDw)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(connection), connection);
|
||||||
|
|
||||||
|
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
|
||||||
|
return TryGetIsSqlDw(builder.DataSource, out isSqlDw);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetIsSqlDw(string dataSource, out bool isSqlDw)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrWhitespaceString(nameof(dataSource), dataSource);
|
||||||
|
CachedInfo info;
|
||||||
|
bool hasFound = _cache.TryGetValue(dataSource, out info);
|
||||||
|
|
||||||
|
if(hasFound)
|
||||||
|
{
|
||||||
|
isSqlDw = info.IsSqlDw;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSqlDw = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static string SafeGetDataSourceFromConnection(IDbConnection connection)
|
private static string SafeGetDataSourceFromConnection(IDbConnection connection)
|
||||||
{
|
{
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||||
{
|
{
|
||||||
@@ -27,6 +28,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
private const string MultiSubnetFailover = "MultiSubnetFailover";
|
private const string MultiSubnetFailover = "MultiSubnetFailover";
|
||||||
private const string DacFxApplicationName = "DacFx";
|
private const string DacFxApplicationName = "DacFx";
|
||||||
|
|
||||||
|
private const int SqlDwEngineEditionId = (int)DatabaseEngineEdition.SqlDataWarehouse;
|
||||||
|
|
||||||
// See MSDN documentation for "SERVERPROPERTY (SQL Azure Database)" for "EngineEdition" property:
|
// See MSDN documentation for "SERVERPROPERTY (SQL Azure Database)" for "EngineEdition" property:
|
||||||
// http://msdn.microsoft.com/en-us/library/ee336261.aspx
|
// http://msdn.microsoft.com/en-us/library/ee336261.aspx
|
||||||
private const int SqlAzureEngineEditionId = 5;
|
private const int SqlAzureEngineEditionId = 5;
|
||||||
@@ -459,6 +462,56 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
return engineEditionId == SqlAzureEngineEditionId;
|
return engineEditionId == SqlAzureEngineEditionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the type of database that a connection is being made to is SQL data warehouse.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection"></param>
|
||||||
|
/// <returns>True if the database is a SQL data warehouse</returns>
|
||||||
|
public static bool IsSqlDwDatabase(IDbConnection connection)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(connection), connection);
|
||||||
|
|
||||||
|
Func<string, bool> executeCommand = commandText =>
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
ExecuteReader(connection,
|
||||||
|
commandText,
|
||||||
|
readResult: (reader) =>
|
||||||
|
{
|
||||||
|
reader.Read();
|
||||||
|
int engineEditionId = int.Parse(reader[0].ToString(), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
result = IsSqlDwEngineId(engineEditionId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isSqlDw = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
isSqlDw = executeCommand(SqlConnectionHelperScripts.EngineEdition);
|
||||||
|
}
|
||||||
|
catch (SqlException)
|
||||||
|
{
|
||||||
|
// The default query contains a WITH (NOLOCK). This doesn't work for Azure DW, so when things don't work out,
|
||||||
|
// we'll fall back to a version without NOLOCK and try again.
|
||||||
|
isSqlDw = executeCommand(SqlConnectionHelperScripts.EngineEditionWithLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSqlDw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the engine edition id of a given database with that of SQL data warehouse.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="engineEditionId"></param>
|
||||||
|
/// <returns>True if the engine edition id is that of SQL data warehouse</returns>
|
||||||
|
private static bool IsSqlDwEngineId(int engineEditionId)
|
||||||
|
{
|
||||||
|
return engineEditionId == SqlDwEngineEditionId;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the exceptions typically thrown when a SQLConnection is being opened
|
/// Handles the exceptions typically thrown when a SQLConnection is being opened
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
private readonly RetryPolicy _connectionRetryPolicy;
|
private readonly RetryPolicy _connectionRetryPolicy;
|
||||||
private RetryPolicy _commandRetryPolicy;
|
private RetryPolicy _commandRetryPolicy;
|
||||||
private Guid _azureSessionId;
|
private Guid _azureSessionId;
|
||||||
|
private bool _isSqlDwDatabase;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the ReliableSqlConnection class with a given connection string
|
/// Initializes a new instance of the ReliableSqlConnection class with a given connection string
|
||||||
@@ -96,6 +97,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a connection is being made to a SQL DW database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conn">A connection object.</param>
|
||||||
|
private bool IsSqlDwConnection(IDbConnection conn)
|
||||||
|
{
|
||||||
|
//Set the connection only if it has not been set earlier.
|
||||||
|
//This is assuming that it is highly unlikely for a connection to change between instances.
|
||||||
|
//Hence any subsequent calls to this method will just return the cached value and not
|
||||||
|
//verify again if this is a SQL DW database connection or not.
|
||||||
|
if (!CachedServerInfo.TryGetIsSqlDw(conn, out _isSqlDwDatabase))
|
||||||
|
{
|
||||||
|
_isSqlDwDatabase = ReliableConnectionHelper.IsSqlDwDatabase(conn);
|
||||||
|
CachedServerInfo.AddOrUpdateIsSqlDw(conn, _isSqlDwDatabase);;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _isSqlDwDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
|
||||||
internal static void SetLockAndCommandTimeout(IDbConnection conn)
|
internal static void SetLockAndCommandTimeout(IDbConnection conn)
|
||||||
{
|
{
|
||||||
@@ -120,7 +140,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void SetDefaultAnsiSettings(IDbConnection conn)
|
internal static void SetDefaultAnsiSettings(IDbConnection conn, bool isSqlDw)
|
||||||
{
|
{
|
||||||
Validate.IsNotNull(nameof(conn), conn);
|
Validate.IsNotNull(nameof(conn), conn);
|
||||||
|
|
||||||
@@ -136,8 +156,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
using (IDbCommand cmd = conn.CreateCommand())
|
using (IDbCommand cmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
cmd.CommandTimeout = CachedServerInfo.GetQueryTimeoutSeconds(conn);
|
cmd.CommandTimeout = CachedServerInfo.GetQueryTimeoutSeconds(conn);
|
||||||
|
if (!isSqlDw)
|
||||||
|
{
|
||||||
cmd.CommandText = @"SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
|
cmd.CommandText = @"SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
|
||||||
SET NUMERIC_ROUNDABORT OFF;";
|
SET NUMERIC_ROUNDABORT OFF;";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cmd.CommandText = @"SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET ARITHABORT ON; SET CONCAT_NULL_YIELDS_NULL ON; SET QUOTED_IDENTIFIER ON;"; //SQL DW does not support NUMERIC_ROUNDABORT
|
||||||
|
}
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +370,7 @@ SET NUMERIC_ROUNDABORT OFF;";
|
|||||||
_underlyingConnection.Open();
|
_underlyingConnection.Open();
|
||||||
}
|
}
|
||||||
SetLockAndCommandTimeout(_underlyingConnection);
|
SetLockAndCommandTimeout(_underlyingConnection);
|
||||||
SetDefaultAnsiSettings(_underlyingConnection);
|
SetDefaultAnsiSettings(_underlyingConnection, IsSqlDwConnection(_underlyingConnection));
|
||||||
});
|
});
|
||||||
|
|
||||||
return _underlyingConnection;
|
return _underlyingConnection;
|
||||||
|
|||||||
@@ -145,5 +145,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
|||||||
return "Unable to retrieve Azure session-id.";
|
return "Unable to retrieve Azure session-id.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string ServerInfoCacheMiss
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "Server Info does not have the requested property in the cache";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// 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 Xunit;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for Sever Information Caching Class
|
||||||
|
/// </summary>
|
||||||
|
public class CachedServerInfoTests
|
||||||
|
{
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)] // is SqlDW instance
|
||||||
|
[InlineData(false)] // is not a SqlDw Instance
|
||||||
|
public void AddOrUpdateIsSqlDw(bool state)
|
||||||
|
{
|
||||||
|
// Set sqlDw result into cache
|
||||||
|
bool isSqlDwResult;
|
||||||
|
CachedServerInfo.AddOrUpdateCache("testDataSource", state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||||
|
|
||||||
|
// Expect the same returned result
|
||||||
|
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResult));
|
||||||
|
Assert.Equal(isSqlDwResult, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)] // is SqlDW instance
|
||||||
|
[InlineData(false)] // is not a SqlDw Instance
|
||||||
|
public void AddOrUpdateIsSqlDwFalseToggle(bool state)
|
||||||
|
{
|
||||||
|
// Set sqlDw result into cache
|
||||||
|
bool isSqlDwResult;
|
||||||
|
CachedServerInfo.AddOrUpdateCache("testDataSource", state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||||
|
|
||||||
|
// Expect the same returned result
|
||||||
|
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResult));
|
||||||
|
Assert.Equal(isSqlDwResult, state);
|
||||||
|
|
||||||
|
// Toggle isSqlDw cache state
|
||||||
|
bool isSqlDwResultToggle;
|
||||||
|
CachedServerInfo.AddOrUpdateCache("testDataSource", !state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||||
|
|
||||||
|
// Expect the oppisite returned result
|
||||||
|
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResultToggle));
|
||||||
|
Assert.Equal(isSqlDwResultToggle, !state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddOrUpdateIsSqlDwFalseToggle()
|
||||||
|
{
|
||||||
|
bool state = true;
|
||||||
|
// Set sqlDw result into cache
|
||||||
|
bool isSqlDwResult;
|
||||||
|
bool isSqlDwResult2;
|
||||||
|
CachedServerInfo.AddOrUpdateCache("testDataSource", state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||||
|
CachedServerInfo.AddOrUpdateCache("testDataSource2", !state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||||
|
|
||||||
|
// Expect the same returned result
|
||||||
|
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResult));
|
||||||
|
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource2", out isSqlDwResult2));
|
||||||
|
|
||||||
|
// Assert cache is set on a per connection basis
|
||||||
|
Assert.Equal(isSqlDwResult, state);
|
||||||
|
Assert.Equal(isSqlDwResult2, !state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AskforSqlDwBeforeCached()
|
||||||
|
{
|
||||||
|
bool isSqlDwResult;
|
||||||
|
Assert.False(CachedServerInfo.TryGetIsSqlDw("testDataSourceWithNoCache", out isSqlDwResult));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user