From 64a6b6a85c571466390dc928348209ebda9f3273 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Thu, 13 May 2021 14:39:19 -0700 Subject: [PATCH] Fix intellisense parsing for Azure and MI connections (#1211) * Fix intellisense parsing for Azure connections * formatting * Refactor to use compat level * update logic * Update log message * spelling --- .../ConnectedBindingContext.cs | 161 +++++++++++++----- 1 file changed, 119 insertions(+), 42 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs index 0add6ec4..edb7d312 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs @@ -6,11 +6,14 @@ using System; using System.Threading; using Microsoft.SqlServer.Management.Common; +using SMO = Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.SmoMetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.Common; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Parser; +using Microsoft.SqlTools.Utility; +using System.Linq; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { @@ -25,6 +28,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private ServerConnection serverConnection; + private SMO.Server server; + /// /// Connected binding context constructor /// @@ -55,7 +60,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // reset the parse options so the get recreated for the current connection this.parseOptions = null; - } + // Set up a SMO Server to query when determing parse options and we don't have a metadataprovider + this.server = new SMO.Server(this.serverConnection); + } + } + + public SMO.Server Server + { + get + { + // Use the Server from the SmoMetadataProvider if we have it to avoid + // unnecessary overhead of querying a new object + return this.SmoMetadataProvider?.SmoServer ?? this.server; + } } /// @@ -92,26 +109,30 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Gets the Language Service ServerVersion /// - public ServerVersion ServerVersion - { - get - { - return this.ServerConnection != null - ? this.ServerConnection.ServerVersion - : null; + public ServerVersion ServerVersion + { + get + { + return this.ServerConnection?.ServerVersion; } } /// /// Gets the current DataEngineType /// - public DatabaseEngineType DatabaseEngineType - { - get - { - return this.ServerConnection != null - ? this.ServerConnection.DatabaseEngineType - : DatabaseEngineType.Standalone; + public DatabaseEngineType DatabaseEngineType + { + get + { + return this.ServerConnection?.DatabaseEngineType ?? DatabaseEngineType.Standalone; + } + } + + public DatabaseEngineEdition DatabaseEngineEdition + { + get + { + return this.ServerConnection?.DatabaseEngineEdition ?? DatabaseEngineEdition.Standard; } } @@ -122,12 +143,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { get { - return this.IsConnected - ? GetTransactSqlVersion(this.ServerVersion) + return this.IsConnected + ? GetTransactSqlVersion(this.Server) : TransactSqlVersion.Current; } } - + /// /// Gets the current DatabaseCompatibilityLevel /// @@ -136,7 +157,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices get { return this.IsConnected - ? GetDatabaseCompatibilityLevel(this.ServerVersion) + ? GetDatabaseCompatibilityLevel(this.Server) : DatabaseCompatibilityLevel.Current; } } @@ -162,55 +183,111 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// - /// Gets the database compatibility level from a server version + /// Gets the database compatibility level for a given server connection /// - /// - private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion) + /// + private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(SMO.Server server) { - int versionMajor = Math.Max(serverVersion.Major, 8); - - switch (versionMajor) + if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) { - case 8: + return DatabaseCompatibilityLevel.Azure; + } + + // Get the actual compat level of the database we're connected to + switch (GetServerCompatibilityLevel(server)) + { + case SMO.CompatibilityLevel.Version80: return DatabaseCompatibilityLevel.Version80; - case 9: + case SMO.CompatibilityLevel.Version90: return DatabaseCompatibilityLevel.Version90; - case 10: + case SMO.CompatibilityLevel.Version100: return DatabaseCompatibilityLevel.Version100; - case 11: + case SMO.CompatibilityLevel.Version110: return DatabaseCompatibilityLevel.Version110; - case 12: + case SMO.CompatibilityLevel.Version120: return DatabaseCompatibilityLevel.Version120; - case 13: + case SMO.CompatibilityLevel.Version130: return DatabaseCompatibilityLevel.Version130; + case SMO.CompatibilityLevel.Version140: + return DatabaseCompatibilityLevel.Version140; + case SMO.CompatibilityLevel.Version150: + return DatabaseCompatibilityLevel.Version150; default: return DatabaseCompatibilityLevel.Current; } } /// - /// Gets the transaction sql version from a server version + /// Gets the transaction sql version for a given server connection /// - /// - private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion) + /// + private static TransactSqlVersion GetTransactSqlVersion(SMO.Server server) { - int versionMajor = Math.Max(serverVersion.Major, 9); - - switch (versionMajor) + if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) { - case 9: - case 10: + return TransactSqlVersion.Azure; + } + + // Determine the language version to use - we can't just use VersionMajor directly because there are engine versions (such as MI) + // whose language version they support is higher than the actual server version. So we choose the highest compat level from + // between the server version and compat level + var compatLevel = Math.Max(server.VersionMajor * 10, (int)GetServerCompatibilityLevel(server)); + switch (compatLevel) + { + case 90: + case 100: // In case of 10.0 we still use Version 10.5 as it is the closest available. return TransactSqlVersion.Version105; - case 11: + case 110: return TransactSqlVersion.Version110; - case 12: + case 120: return TransactSqlVersion.Version120; - case 13: + case 130: return TransactSqlVersion.Version130; + case 140: + return TransactSqlVersion.Version140; + case 150: + return TransactSqlVersion.Version150; default: return TransactSqlVersion.Current; } } + + /// + /// Gets the SMO compatibility level for the given server, defaulting to the highest available level if an + /// error occurs while querying. + /// + /// The server object to get the compat level of + /// + private static SMO.CompatibilityLevel GetServerCompatibilityLevel(SMO.Server server) + { + // Set the default fields so that we avoid the overhead of querying for properties we don't need right now + server.SetDefaultInitFields(typeof(SMO.Database), nameof(SMO.Database.CompatibilityLevel)); + + SMO.CompatibilityLevel compatLevel; + try + { + // First try the master DB since it will have the highest compat level for that instance + compatLevel = server.Databases["master"].CompatibilityLevel; + Logger.Write(System.Diagnostics.TraceEventType.Information, $"Got compat level for binding context {compatLevel} after querying master"); + } + catch + { + // If we can't get it from master then fall back to the current database + try + { + compatLevel = server.Databases[server.ConnectionContext.DatabaseName].CompatibilityLevel; + Logger.Write(System.Diagnostics.TraceEventType.Information, $"Got compat level for binding context {compatLevel} after querying connection DB"); + } + catch + { + // There's nothing else we can do so just default to the highest available version + compatLevel = Enum.GetValues(typeof(SMO.CompatibilityLevel)).Cast().Max(); + Logger.Write(System.Diagnostics.TraceEventType.Information, $"Failed to get compat level for binding context from querying server - using default of {compatLevel}"); + } + + } + return compatLevel; + } } }