// // 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.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 { /// /// Class for the binding context for connected sessions /// public class ConnectedBindingContext : IBindingContext { private ParseOptions parseOptions; private ManualResetEvent bindingLock; private ServerConnection serverConnection; private SMO.Server server; /// /// Connected binding context constructor /// public ConnectedBindingContext() { this.bindingLock = new ManualResetEvent(initialState: true); this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout; this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); } /// /// Gets or sets a flag indicating if the binder is connected /// public bool IsConnected { get; set; } /// /// Gets or sets the binding server connection /// public ServerConnection ServerConnection { get { return this.serverConnection; } set { this.serverConnection = value; // 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; } } /// /// Gets or sets the metadata display info provider /// public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; } /// /// Gets or sets the SMO metadata provider /// public SmoMetadataProvider SmoMetadataProvider { get; set; } /// /// Gets or sets the binder /// public IBinder Binder { get; set; } /// /// Gets the binding lock object /// public ManualResetEvent BindingLock { get { return this.bindingLock; } } /// /// Gets or sets the binding operation timeout in milliseconds /// public int BindingTimeout { get; set; } /// /// Gets the Language Service ServerVersion /// public ServerVersion ServerVersion { get { return this.ServerConnection?.ServerVersion; } } /// /// Gets the current DataEngineType /// public DatabaseEngineType DatabaseEngineType { get { return this.ServerConnection?.DatabaseEngineType ?? DatabaseEngineType.Standalone; } } public DatabaseEngineEdition DatabaseEngineEdition { get { return this.ServerConnection?.DatabaseEngineEdition ?? DatabaseEngineEdition.Standard; } } /// /// Gets the current connections TransactSqlVersion /// public TransactSqlVersion TransactSqlVersion { get { return this.IsConnected ? GetTransactSqlVersion(this.Server) : TransactSqlVersion.Current; } } /// /// Gets the current DatabaseCompatibilityLevel /// public DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get { return this.IsConnected ? GetDatabaseCompatibilityLevel(this.Server) : DatabaseCompatibilityLevel.Current; } } /// /// Gets the current ParseOptions /// public ParseOptions ParseOptions { get { this.parseOptions ??= new ParseOptions( batchSeparator: LanguageService.DefaultBatchSeperator, isQuotedIdentifierSet: true, compatibilityLevel: DatabaseCompatibilityLevel, transactSqlVersion: TransactSqlVersion); return this.parseOptions; } } /// /// Gets the database compatibility level for a given server connection /// /// private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(SMO.Server server) { if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) { 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 SMO.CompatibilityLevel.Version90: return DatabaseCompatibilityLevel.Version90; case SMO.CompatibilityLevel.Version100: return DatabaseCompatibilityLevel.Version100; case SMO.CompatibilityLevel.Version110: return DatabaseCompatibilityLevel.Version110; case SMO.CompatibilityLevel.Version120: return DatabaseCompatibilityLevel.Version120; 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 for a given server connection /// /// private static TransactSqlVersion GetTransactSqlVersion(SMO.Server server) { if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) { 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 110: return TransactSqlVersion.Version110; case 120: return TransactSqlVersion.Version120; 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; } } }