diff --git a/bin/nuget/Microsoft.SqlServer.Smo.140.17049.0.nupkg b/bin/nuget/Microsoft.SqlServer.Smo.140.17049.0.nupkg deleted file mode 100644 index e79d8fc1..00000000 Binary files a/bin/nuget/Microsoft.SqlServer.Smo.140.17049.0.nupkg and /dev/null differ diff --git a/bin/nuget/Microsoft.SqlServer.Smo.140.17050.0.nupkg b/bin/nuget/Microsoft.SqlServer.Smo.140.17050.0.nupkg new file mode 100644 index 00000000..6b3fbe7d Binary files /dev/null and b/bin/nuget/Microsoft.SqlServer.Smo.140.17050.0.nupkg differ diff --git a/docs/samples/jsonrpc/netcore/executequery/project.json b/docs/samples/jsonrpc/netcore/executequery/project.json index 6a081889..c6e8feaa 100644 --- a/docs/samples/jsonrpc/netcore/executequery/project.json +++ b/docs/samples/jsonrpc/netcore/executequery/project.json @@ -12,7 +12,7 @@ "Newtonsoft.Json": "9.0.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.17049.0", + "Microsoft.SqlServer.Smo": "140.17050.0", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", "System.ComponentModel.TypeConverter": "4.1.0", diff --git a/docs/samples/smo/netcore/ModifySetting/project.json b/docs/samples/smo/netcore/ModifySetting/project.json index 912dedb8..fb27dece 100644 --- a/docs/samples/smo/netcore/ModifySetting/project.json +++ b/docs/samples/smo/netcore/ModifySetting/project.json @@ -5,7 +5,7 @@ "emitEntryPoint": true }, "dependencies": { - "Microsoft.SqlServer.Smo": "140.17049.0" + "Microsoft.SqlServer.Smo": "140.17050.0" }, "frameworks": { "netcoreapp1.0": { diff --git a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/AdminServicesProviderOptions.cs b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/AdminServicesProviderOptions.cs new file mode 100644 index 00000000..9d34f2d6 --- /dev/null +++ b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/AdminServicesProviderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.Hosting.Contracts +{ + /// + /// Defines the admin services provider options that the DMP server implements. + /// + public class AdminServicesProviderOptions + { + public ServiceOption[] DatabaseInfoOptions { get; set; } + + public ServiceOption[] DatabaseFileInfoOptions { get; set; } + + public ServiceOption[] FileGroupInfoOptions { get; set; } + } +} + diff --git a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ConnectionProviderOptions.cs b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ConnectionProviderOptions.cs index 50089435..94dc400b 100644 --- a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ConnectionProviderOptions.cs +++ b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ConnectionProviderOptions.cs @@ -22,41 +22,14 @@ namespace Microsoft.SqlTools.Hosting.Contracts public string Name { get; set; } } - public class ConnectionOption + public class ConnectionOption : ServiceOption { - public static readonly string ValueTypeString = "string"; - public static readonly string ValueTypeMultiString = "multistring"; - public static readonly string ValueTypePassword = "password"; - public static readonly string ValueTypeNumber = "number"; - public static readonly string ValueTypeCategory = "category"; - public static readonly string ValueTypeBoolean = "boolean"; - public static readonly string SpecialValueServerName = "serverName"; public static readonly string SpecialValueDatabaseName = "databaseName"; public static readonly string SpecialValueAuthType = "authType"; public static readonly string SpecialValueUserName = "userName"; public static readonly string SpecialValuePasswordName = "password"; - public string Name { get; set; } - - public string DisplayName { get; set; } - - public string Description {get; set; } - - public string GroupName {get; set; } - - /// - /// Type of the parameter. Can be either string, number, or category. - /// - public string ValueType { get; set; } - - public string DefaultValue { get; set; } - - /// - /// Set of permitted values if ValueType is category. - /// - public CategoryValue[] CategoryValues { get; set; } - /// /// Determines if the parameter is one of the 'special' known values. /// Can be either Server Name, Database Name, Authentication Type, @@ -68,11 +41,6 @@ namespace Microsoft.SqlTools.Hosting.Contracts /// Flag to indicate that this option is part of the connection identity /// public bool IsIdentity { get; set; } - - /// - /// Flag to indicate that this option is required - /// - public bool IsRequired { get; set; } } } diff --git a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/DmpServerCapabilities.cs b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/DmpServerCapabilities.cs index 9b204021..13ec99c3 100644 --- a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/DmpServerCapabilities.cs +++ b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/DmpServerCapabilities.cs @@ -17,5 +17,7 @@ namespace Microsoft.SqlTools.Hosting.Contracts public string ProviderDisplayName { get; set; } public ConnectionProviderOptions ConnectionProvider { get; set; } + + public AdminServicesProviderOptions AdminServicesProvider { get; set; } } } diff --git a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ServiceOption.cs b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ServiceOption.cs new file mode 100644 index 00000000..b394ebf8 --- /dev/null +++ b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/ServiceOption.cs @@ -0,0 +1,48 @@ + // +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.Hosting.Contracts +{ + public class ServiceOption + { + public static readonly string ValueTypeString = "string"; + public static readonly string ValueTypeMultiString = "multistring"; + public static readonly string ValueTypePassword = "password"; + public static readonly string ValueTypeNumber = "number"; + public static readonly string ValueTypeCategory = "category"; + public static readonly string ValueTypeBoolean = "boolean"; + public static readonly string ValueTypeObject = "object"; + + public string Name { get; set; } + + public string DisplayName { get; set; } + + public string Description {get; set; } + + public string GroupName {get; set; } + + /// + /// Type of the parameter. Can be either string, number, or category. + /// + public string ValueType { get; set; } + + public string DefaultValue { get; set; } + + public string ObjectType { get; set; } + + /// + /// Set of permitted values if ValueType is category. + /// + public CategoryValue[] CategoryValues { get; set; } + + /// + /// Flag to indicate that this option is required + /// + public bool IsRequired { get; set; } + + public bool IsArray { get; set; } + } +} + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminService.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminService.cs index 13152390..88d8edc8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminService.cs @@ -5,10 +5,12 @@ using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.SqlContext; using System; using System.Threading.Tasks; +using System.Xml; namespace Microsoft.SqlTools.ServiceLayer.Admin { @@ -19,6 +21,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { private static readonly Lazy instance = new Lazy(() => new AdminService()); + private static ConnectionService connectionService = null; + /// /// Default, parameterless constructor. /// @@ -26,6 +30,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { } + /// + /// Internal for testing purposes only + /// + internal static ConnectionService ConnectionServiceInstance + { + get + { + if (AdminService.connectionService == null) + { + AdminService.connectionService = ConnectionService.Instance; + } + return AdminService.connectionService; + } + + set + { + AdminService.connectionService = value; + } + } + /// /// Gets the singleton instance object /// @@ -41,6 +65,62 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { serviceHost.SetRequestHandler(CreateDatabaseRequest.Type, HandleCreateDatabaseRequest); serviceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest); + serviceHost.SetRequestHandler(DefaultDatabaseInfoRequest.Type, HandleDefaultDatabaseInfoRequest); + } + + public static async Task HandleDefaultDatabaseInfoRequest( + DefaultDatabaseInfoParams optionsParams, + RequestContext requestContext) + { + var response = new DefaultDatabaseInfoResponse(); + + ConnectionInfo connInfo; + AdminService.ConnectionServiceInstance.TryFindConnection( + optionsParams.OwnerUri, + out connInfo); + + XmlDocument xmlDoc = CreateDataContainerDocument(connInfo); + char[] passwordArray = connInfo.ConnectionDetails.Password.ToCharArray(); + unsafe + { + fixed (char* passwordPtr = passwordArray) + { + var dataContainer = new CDataContainer( + CDataContainer.ServerType.SQL, + connInfo.ConnectionDetails.ServerName, + false, + connInfo.ConnectionDetails.UserName, + new System.Security.SecureString(passwordPtr, passwordArray.Length), + xmlDoc.InnerXml); + + var taskHelper = new DatabaseTaskHelper(); + taskHelper.CreateDatabase(dataContainer); + + response.DefaultDatabaseInfo = DatabaseTaskHelper.DatabasePrototypeToDatabaseInfo(taskHelper.Prototype); + } + } + + await requestContext.SendResult(response); + } + + + private static XmlDocument CreateDataContainerDocument(ConnectionInfo connInfo) + { + string xml = + string.Format(@" + + {0} + {0} (SQLServer, user = {1}) + sql + Server[@Name='{0}'] + Database + ", + connInfo.ConnectionDetails.ServerName.ToUpper(), + connInfo.ConnectionDetails.UserName); + + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xml); + return xmlDoc; } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminServicesProviderOptionsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminServicesProviderOptionsHelper.cs new file mode 100644 index 00000000..2977cd71 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/AdminServicesProviderOptionsHelper.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Helper class for providing metadata about admin services + /// + public class AdminServicesProviderOptionsHelper + { + internal const string Name = "name"; + internal const string Owner = "owner"; + internal const string Collation = "collation"; + internal const string FileGroups = "fileGroups"; + internal const string DatabaseFiles = "databaseFiles"; + internal const string PhysicalName = "physicalName"; + + internal static AdminServicesProviderOptions BuildAdminServicesProviderOptions() + { + return new AdminServicesProviderOptions + { + DatabaseInfoOptions = new ServiceOption[] + { + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.Name, + DisplayName = "Name", + Description = "Name of the database", + ValueType = ServiceOption.ValueTypeString, + IsRequired = true, + GroupName = "General" + }, + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.Owner, + DisplayName = "Owner", + Description = "Database owner", + ValueType = ServiceOption.ValueTypeString, + IsRequired = true, + GroupName = "General" + }, + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.Collation, + DisplayName = "Collation", + Description = "Database collation", + ValueType = ServiceOption.ValueTypeString, + IsRequired = true, + GroupName = "General" + }, + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.FileGroups, + DisplayName = "File Groups", + Description = "File groups", + ObjectType = "FileGroupInfo", + ValueType = ServiceOption.ValueTypeObject, + IsRequired = true, + IsArray = true, + GroupName = "General" + }, + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.DatabaseFiles, + DisplayName = "Database Files", + Description = "Database Files", + ObjectType = "DatabaseFileInfo", + ValueType = ServiceOption.ValueTypeObject, + IsRequired = true, + IsArray = true, + GroupName = "General" + } + }, + FileGroupInfoOptions = new ServiceOption[] + { + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.Name, + DisplayName = "Name", + Description = "Name of the file group", + ValueType = ServiceOption.ValueTypeString, + IsRequired = true, + GroupName = "General" + } + }, + DatabaseFileInfoOptions = new ServiceOption[] + { + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.Name, + DisplayName = "Name", + Description = "Name of the database file", + ValueType = ServiceOption.ValueTypeString, + IsRequired = true, + GroupName = "General" + }, + new ServiceOption + { + Name = AdminServicesProviderOptionsHelper.PhysicalName, + DisplayName = "Physical Name", + Description = "Physical name of the database file", + ValueType = ServiceOption.ValueTypeString, + IsRequired = true, + GroupName = "General" + } + } + }; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/AzureSqlDbHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/AzureSqlDbHelper.cs new file mode 100644 index 00000000..66dc9447 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/AzureSqlDbHelper.cs @@ -0,0 +1,466 @@ +// +// 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.Linq; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.SqlServer.Diagnostics.STrace; +using Microsoft.SqlServer.Management.Common; +using Microsoft.Win32; +using SizeUnits = Microsoft.SqlTools.ServiceLayer.Admin.DbSize.SizeUnits; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + public static class AzureSqlDbHelper + { + + private static readonly TraceContext TraceContext = TraceContext.GetTraceContext("AzureSqlDbUtils", typeof(AzureSqlDbHelper).Name); + + /// + /// Registry key for SSMS Azure overrides + /// + private static readonly string SSMSAzureRegKey = + @"HKEY_CURRENT_USER\Software\Microsoft\SQL Server Management Studio\14.0\Azure"; + + /// + /// Registry sub key for the AzureServiceObjectives overrides + /// + private const string AzureServiceObjectivesRegSubKey = @"AzureServiceObjectives"; + + /// + /// Contains the various editions available for an Azure Database + /// + /// ****IMPORTANT**** - If updating this enum make sure that the other logic in this class is updated as well + public enum AzureEdition + { + Web = 0, + Business = 1, + Basic = 2, + Standard = 3, + Premium = 4, + DataWarehouse = 5, + PremiumRS = 6 + } + + /// + /// Provides a mapping of Azure DB Editions to their respective size options + /// + /// Values below are taken from http://msdn.microsoft.com/en-us/library/dn268335.aspx + private static readonly Dictionary> AzureEditionDatabaseSizeMappings = new Dictionary + > + { + { + AzureEdition.Web, new KeyValuePair( + 1, //1GB + new[] + { + new DbSize(100, SizeUnits.MB), + new DbSize(1, SizeUnits.GB), //Default + new DbSize(5, SizeUnits.GB) + }) + }, + { + AzureEdition.Business, new KeyValuePair( + 0, //10GB + new[] + { + new DbSize(10, SizeUnits.GB), //Default + new DbSize(20, SizeUnits.GB), + new DbSize(30, SizeUnits.GB), + new DbSize(40, SizeUnits.GB), + new DbSize(50, SizeUnits.GB), + new DbSize(100, SizeUnits.GB), + new DbSize(150, SizeUnits.GB) + }) + }, + { + AzureEdition.Basic, new KeyValuePair( + 3, //2GB + new[] + { + new DbSize(100, SizeUnits.MB), + new DbSize(500, SizeUnits.MB), + new DbSize(1, SizeUnits.GB), + new DbSize(2, SizeUnits.GB) //Default + }) + }, + { + AzureEdition.Standard, + new KeyValuePair( + 13, //250GB + new[] + { + new DbSize(100, SizeUnits.MB), + new DbSize(500, SizeUnits.MB), + new DbSize(1, SizeUnits.GB), + new DbSize(2, SizeUnits.GB), + new DbSize(5, SizeUnits.GB), + new DbSize(10, SizeUnits.GB), + new DbSize(20, SizeUnits.GB), + new DbSize(30, SizeUnits.GB), + new DbSize(40, SizeUnits.GB), + new DbSize(50, SizeUnits.GB), + new DbSize(100, SizeUnits.GB), + new DbSize(150, SizeUnits.GB), + new DbSize(200, SizeUnits.GB), + new DbSize(250, SizeUnits.GB) //Default + }) + }, + { + AzureEdition.Premium, + new KeyValuePair( + 16, //500GB + new[] + { + new DbSize(100, SizeUnits.MB), + new DbSize(500, SizeUnits.MB), + new DbSize(1, SizeUnits.GB), + new DbSize(2, SizeUnits.GB), + new DbSize(5, SizeUnits.GB), + new DbSize(10, SizeUnits.GB), + new DbSize(20, SizeUnits.GB), + new DbSize(30, SizeUnits.GB), + new DbSize(40, SizeUnits.GB), + new DbSize(50, SizeUnits.GB), + new DbSize(100, SizeUnits.GB), + new DbSize(150, SizeUnits.GB), + new DbSize(200, SizeUnits.GB), + new DbSize(250, SizeUnits.GB), + new DbSize(300, SizeUnits.GB), + new DbSize(400, SizeUnits.GB), + new DbSize(500, SizeUnits.GB), //Default + new DbSize(1024, SizeUnits.GB) //Following portal to display this as GB instead of 1TB + }) + }, + + { + AzureEdition.DataWarehouse, + new KeyValuePair( + 5, //10240GB + new[] + { + new DbSize(250, SizeUnits.GB), + new DbSize(500, SizeUnits.GB), + new DbSize(750, SizeUnits.GB), + new DbSize(1024, SizeUnits.GB), + new DbSize(5120, SizeUnits.GB), + new DbSize(10240, SizeUnits.GB), + new DbSize(20480, SizeUnits.GB), + new DbSize(30720, SizeUnits.GB), + new DbSize(40960, SizeUnits.GB), + new DbSize(51200, SizeUnits.GB) + }) + }, + { + AzureEdition.PremiumRS, + new KeyValuePair( + 16, //500GB + new[] + { + new DbSize(100, SizeUnits.MB), + new DbSize(500, SizeUnits.MB), + new DbSize(1, SizeUnits.GB), + new DbSize(2, SizeUnits.GB), + new DbSize(5, SizeUnits.GB), + new DbSize(10, SizeUnits.GB), + new DbSize(20, SizeUnits.GB), + new DbSize(30, SizeUnits.GB), + new DbSize(40, SizeUnits.GB), + new DbSize(50, SizeUnits.GB), + new DbSize(100, SizeUnits.GB), + new DbSize(150, SizeUnits.GB), + new DbSize(200, SizeUnits.GB), + new DbSize(250, SizeUnits.GB), + new DbSize(300, SizeUnits.GB), + new DbSize(400, SizeUnits.GB), + new DbSize(500, SizeUnits.GB), //Default + }) + }, + }; + + /// + /// Maps Azure DB Editions to their corresponding Service Objective (Performance Level) options. These values are the default but + /// can be overridden by use of the ImportExportWizard registry key (see static initializer above). + /// + /// The key is the index of the default value for the list + /// + private static readonly Dictionary> AzureServiceObjectiveInfo = new Dictionary + > + { + {AzureEdition.Basic, new KeyValuePair(0, new[] {"Basic"})}, + {AzureEdition.Standard, new KeyValuePair(2, new[] {"S0", "S1", "S2", "S3"})}, + {AzureEdition.Premium, new KeyValuePair(0, new[] {"P1", "P2", "P4", "P6", "P11", "P15"})}, + {AzureEdition.PremiumRS, new KeyValuePair(0, new []{"PRS1", "PRS2", "PRS4", "PRS6"})}, + {AzureEdition.DataWarehouse, new KeyValuePair(3, new[] {"DW100", "DW200", "DW300", "DW400", "DW500", "DW600", "DW1000", "DW1200", "DW1500", "DW2000", "DW3000", "DW6000"})} + }; + + /// + /// Static initializer to read in the registry key values for the Service Objective mappings, which allows the user to override the defaults set for + /// the service objective list. We allow them to do this as a temporary measure so that if we change the service objectives in the future we + /// can tell people to use the registry key to use the new values until an updated SSMS can be released. + /// + static AzureSqlDbHelper() + { + //foreach (AzureEdition edition in Enum.GetValues(typeof (AzureEdition))) + //{ + // object value; + + // try + // { + // value = Registry.GetValue( + // string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", SSMSAzureRegKey, + // AzureServiceObjectivesRegSubKey), edition.ToString(), null); + // } + // catch (Exception e) + // { + // //We don't really care if we can't read in an override (just fall back to default) so log and move on + // TraceContext.TraceVerbose("Exception reading service objective overrides for {0} - {1}", edition, e.Message); + // continue; + // } + + // if (value != null) + // { + // TraceContext.TraceVerbose("Found ServiceObjective override for {0}, value is {1}", edition, value); + // //Key is in format : + // //e.g. 2:S0,S1,S2 + // //Only split into 2 parts since the service objectives could + // //be changed in the future to have :'s, so only treat the first + // //as special + // string[] values = value.ToString().Split(new[] {':'}, 2); + // if (values.Length != 2) + // { + // //Badly formatted value, ignore this one + // TraceContext.TraceVerbose("ServiceObjective override for {0} is badly formatted - skipping", edition); + // continue; + // } + + // int defaultIndex; + // if (!int.TryParse(values[0], out defaultIndex)) + // { + // //Invalid default index, ignore this one + // TraceContext.TraceVerbose("ServiceObjective override for {0} has non-parseable default index - skipping", edition); + // continue; + // } + + // //Service objectives are in a comma-separated list + // string[] serviceObjectives = values[1].Split(','); + // if (defaultIndex < 0 || defaultIndex >= serviceObjectives.Length) + // { + // //Index out of bounds, ignore this one + // TraceContext.TraceVerbose("ServiceObjective override for {0} has out of bounds default index - skipping"); + // continue; + // } + // if (AzureServiceObjectiveInfo.ContainsKey(edition)) + // { + // //Overwrite our default values if the registry key for this edition exists + // AzureServiceObjectiveInfo[edition] = new KeyValuePair(defaultIndex, + // serviceObjectives); + // } + // else + // { + // AzureServiceObjectiveInfo.Add(edition, + // new KeyValuePair(defaultIndex, serviceObjectives)); + // } + // } + //} + } + + /// + /// Gets the list of databases sizes applicable for the specified Azure DB edition (if such + /// a mapping exists) as well as the index of the default size for that edition. + /// + /// Outputs an empty array with an index of -1 if no such mapping exists + /// + /// + /// + /// TRUE if a mapping exists, FALSE if it does not + public static bool TryGetDatabaseSizeInfo(AzureEdition edition, out KeyValuePair databaseSizeInfo) + { + if (AzureEditionDatabaseSizeMappings.TryGetValue(edition, out databaseSizeInfo)) + { + return true; + } + + databaseSizeInfo = new KeyValuePair(-1, new DbSize[0]); + + return false; + } + + /// + /// Gets a KeyValuePair containing a list of the ServiceObjective names mapped to a particular Azure DB Edition + /// (if such a mapping exists) as well as the index of the default Service Objective for that edition. + /// Outputs an empty array with a default index of -1 if no such mapping exists. + /// + /// + /// + /// TRUE if a mapping exists, FALSE if it did not + public static bool TryGetServiceObjectiveInfo(AzureEdition edition, + out KeyValuePair serviceObjectiveInfo) + { + if (AzureServiceObjectiveInfo.TryGetValue(edition, out serviceObjectiveInfo)) + { + return true; + } + + serviceObjectiveInfo = new KeyValuePair(-1, new string[0]); + + return false; + } + + /// + /// Gets the default database size for a specified Azure Edition + /// + /// + /// The default size, or NULL if no default exists + public static DbSize GetDatabaseDefaultSize(AzureEdition edition) + { + DbSize defaultSize = null; + + KeyValuePair pair; + + if (AzureEditionDatabaseSizeMappings.TryGetValue(edition, out pair)) + { + defaultSize = pair.Value[pair.Key]; + } + + return defaultSize; + } + + /// + /// Gets the default Service Objective name for a particular Azure DB edition + /// + /// + /// + public static string GetDefaultServiceObjective(AzureEdition edition) + { + string defaultServiceObjective = ""; + + KeyValuePair pair; + + if (AzureServiceObjectiveInfo.TryGetValue(edition, out pair)) + { + //Bounds check since this value can be entered by users + if (pair.Key >= 0 && pair.Key < pair.Value.Length) + { + defaultServiceObjective = pair.Value[pair.Key]; + } + } + + return defaultServiceObjective; + } + + /// + /// Gets the localized Azure Edition display name + /// + /// + /// + public static string GetAzureEditionDisplayName(AzureEdition edition) + { + string result; + switch (edition) + { + //case AzureEdition.Business: + // result = SR.BusinessAzureEdition; + // break; + //case AzureEdition.Web: + // result = SR.WebAzureEdition; + // break; + //case AzureEdition.Basic: + // result = SR.BasicAzureEdition; + // break; + //case AzureEdition.Standard: + // result = SR.StandardAzureEdition; + // break; + //case AzureEdition.Premium: + // result = SR.PremiumAzureEdition; + // break; + //case AzureEdition.DataWarehouse: + // result = SR.DataWarehouseAzureEdition; + // break; + //case AzureEdition.PremiumRS: + // result = SR.PremiumRsAzureEdition; + // break; + default: + result = edition.ToString(); + break; + } + + return result; + } + + /// + /// Parses a display name back into its corresponding AzureEdition. + /// + /// + /// + /// TRUE if the conversion succeeded, FALSE if it did not. + public static bool TryGetAzureEditionFromDisplayName(string displayName, out AzureEdition edition) + { + //if (string.Compare(displayName, SR.BusinessAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.Business; + //} + //else if (string.Compare(displayName, SR.WebAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.Web; + //} + //else if (string.Compare(displayName, SR.BasicAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.Basic; + //} + //else if (string.Compare(displayName, SR.StandardAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.Standard; + //} + //else if (string.Compare(displayName, SR.PremiumAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.Premium; + //} + //else if (string.Compare(displayName, SR.DataWarehouseAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.DataWarehouse; + //} + //else if (string.Compare(displayName, SR.PremiumRsAzureEdition, CultureInfo.CurrentUICulture, CompareOptions.None) == 0) + //{ + // edition = AzureEdition.PremiumRS; + //} + //else + { + //"Default" edition is standard - but since we're returning false the user shouldn't look at this anyways + edition = AzureEdition.Standard; + return false; + } + + // return true; + } + + /// + /// Returns a list of AzureEditions that are valid values for the EDITION option + /// when creating a database. + /// + /// We do this so that the AzureEdition enum can have values such as NONE or DEFAULT added + /// without requiring clients to explicitly filter out those values themselves each time. + /// + public static IEnumerable GetValidAzureEditionOptions(ServerVersion version) + { + //Azure v12 and above doesn't have the Web and Business tiers + if (version.Major >= 12) + { + return new List() + { + AzureEdition.Basic, + AzureEdition.Standard, + AzureEdition.Premium, + AzureEdition.PremiumRS, + AzureEdition.DataWarehouse + }; + } + + //Default for now is to return all values since they're currently all valid + return Enum.GetValues(typeof(AzureEdition)).Cast(); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/CUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/CUtils.cs new file mode 100644 index 00000000..88dafa49 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/CUtils.cs @@ -0,0 +1,717 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlServer.Management.Sdk.Sfc; +using System; +using System.Text; +using System.Xml; +//using System.Drawing; +//using System.Windows.Forms; +using System.Threading; +using System.IO; +//using Microsoft.NetEnterpriseServers; +//using Microsoft.SqlServer.Management.UI.VSIntegration.ObjectExplorer; +using Microsoft.SqlServer.Management.Common; +using SMO = Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Diagnostics; +//using Microsoft.SqlServer.Management.SqlMgmt; +using System.Data.SqlClient; +// using System.Management; +using System.Collections; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Summary description for CUtils. + /// + internal class CUtils + { + + private const int ObjectPermissionsDeniedErrorNumber = 229; + private const int ColumnPermissionsDeniedErrorNumber = 230; + + public CUtils() + { + // + // TODO: Add constructor logic here + // + } + + + //public Bitmap LoadBitmap(string szBitmapName) + //{ + // Bitmap bmp = null; + // Stream s = null; + // string strQualifiedName; + + // strQualifiedName = typeof(CUtils).Namespace + ".Images." + szBitmapName; + + // s = typeof(CUtils).Assembly.GetManifestResourceStream(strQualifiedName); + + // if (s != null) + // { + // bmp = new Bitmap(s); + // return bmp; + // } + + // return null; + //} + + //public Icon LoadIcon(string strName) + //{ + + // Icon ico = null; + // Stream s = null; + // string strQualifiedName; + + // strQualifiedName = typeof(CUtils).Namespace + ".Images." + strName; + + // s = typeof(CUtils).Assembly.GetManifestResourceStream(strQualifiedName); + + // if (s != null) + // { + // int iconSize = DpiUtil.GetScaledImageSize(); + // ico = new Icon(s, iconSize, iconSize); + // return ico; + // } + + // return null; + //} + + //public void LoadAddIcon(ImageList imageList, string strName) + //{ + // Icon ico = null; + // Stream s = null; + // string strQualifiedName; + + // strQualifiedName = typeof(CUtils).Namespace + ".Images." + strName; + + // s = typeof(CUtils).Module.Assembly.GetManifestResourceStream(strQualifiedName); + + // if (s != null) + // { + // try + // { + // ico = new Icon(s, 16, 16); + // imageList.Images.Add(ico); + // } + // finally + // { + // if (ico != null) + // ico.Dispose(); + // } + // } + //} + + public static void UseMaster(SMO.Server server) + { + server.ConnectionContext.ExecuteNonQuery("use master"); + } + + + ///// + ///// returns height of my border (depending on its style) + ///// + //public static int GetBorderHeight(BorderStyle style) + //{ + // if (style == BorderStyle.FixedSingle) + // { + // return SystemInformation.BorderSize.Height; + // } + // else if (style == BorderStyle.Fixed3D) + // { + // return SystemInformation.Border3DSize.Height; + // } + // else + // { + // return 0; + // } + //} + + //public static int GetBorderWidth(BorderStyle style) + //{ + // if (style == BorderStyle.FixedSingle) + // { + // return SystemInformation.BorderSize.Width; + // } + // else if (style == BorderStyle.Fixed3D) + // { + // return SystemInformation.Border3DSize.Width; + // } + // else + // { + // return 0; + // } + //} + + /// + /// Get a SMO Server object that is connected to the connection + /// + /// Conenction info + /// Smo Server object for the connection + public static Microsoft.SqlServer.Management.Smo.Server GetSmoServer(IManagedConnection mc) + { + SqlOlapConnectionInfoBase ci = mc.Connection; + if (ci == null) + { + throw new ArgumentNullException("ci"); + } + + SMO.Server server = null; + + // see what type of connection we have been passed + SqlConnectionInfoWithConnection ciWithCon = ci as SqlConnectionInfoWithConnection; + + if (ciWithCon != null) + { + server = new SMO.Server(ciWithCon.ServerConnection); + } + else + { + SqlConnectionInfo sqlCi = ci as SqlConnectionInfo; + if (sqlCi != null) + { + server = new SMO.Server(new ServerConnection(sqlCi)); + } + } + + if (server == null) + { + throw new InvalidOperationException(); + } + return server; + + } + + + public static int GetServerVersion(SMO.Server server) + { + return server.Information.Version.Major; + } + + /// + /// validates current value of given numeric control. Shows message if the value is not valid + /// + /// + /// + /// true if control's value is valid, false otherwise + //public static bool ValidateNumeric(NumericUpDown numControl, string errMessageToShow, bool displayException) + //{ + // try + // { + // int curValue = int.Parse(numControl.Text, System.Globalization.CultureInfo.CurrentCulture); + // if (curValue < numControl.Minimum || curValue > numControl.Maximum) + // { + // if (true == displayException) + // { + // ExceptionMessageBox box = new ExceptionMessageBox(); + + // box.Caption = SRError.SQLWorkbench; + // box.Message = new Exception(errMessageToShow); + // box.Symbol = ExceptionMessageBoxSymbol.Error; + // box.Buttons = ExceptionMessageBoxButtons.OK; + // box.Options = ExceptionMessageBoxOptions.RightAlign; + // box.Show(null); + // } + + // try + // { + // numControl.Value = Convert.ToDecimal(numControl.Tag, System.Globalization.CultureInfo.CurrentCulture); + // numControl.Update(); + // numControl.Text = Convert.ToString(numControl.Value, System.Globalization.CultureInfo.CurrentCulture); + // numControl.Refresh(); + + // } + // catch + // { + + // } + // numControl.Focus(); + // return false; + // } + + // return true; + // } + // catch + // { + // if (true == displayException) + // { + // ExceptionMessageBox box = new ExceptionMessageBox(); + + // box.Caption = SRError.SQLWorkbench; + // box.Message = new Exception(errMessageToShow); + // box.Symbol = ExceptionMessageBoxSymbol.Error; + // box.Buttons = ExceptionMessageBoxButtons.OK; + // box.Options = ExceptionMessageBoxOptions.RightAlign; + // box.Show(null); + // } + + // numControl.Focus(); + // return false; + // } + //} + + /// + /// Determines the oldest date based on the type of time units and the number of time units + /// + /// + /// + /// + public static DateTime GetOldestDate(int numUnits, TimeUnitType typeUnits) + { + DateTime result = DateTime.Now; + + switch (typeUnits) + { + case TimeUnitType.Week: + { + result = (DateTime.Now).AddDays(-1 * 7 * numUnits); + break; + } + case TimeUnitType.Month: + { + result = (DateTime.Now).AddMonths(-1 * numUnits); + break; + } + case TimeUnitType.Year: + { + result = (DateTime.Now).AddYears(-1 * numUnits); + break; + } + default: + { + result = (DateTime.Now).AddDays(-1 * numUnits); + break; + } + } + + return result; + } + + public static string TokenizeXml(string s) + { + if (null == s) return String.Empty; + + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + foreach (char c in s) + { + switch (c) + { + case '<': + sb.Append("<"); + break; + case '>': + sb.Append(">"); + break; + case '&': + sb.Append("&"); + break; + default: + sb.Append(c); + break; + } + } + return sb.ToString(); + } + + /// + /// Tries to get the SqlException out of an Enumerator exception + /// + /// + /// + public static SqlException GetSqlException(Exception e) + { + SqlException sqlEx = null; + Exception exception = e; + while (exception != null) + { + sqlEx = exception as SqlException; + if (null != sqlEx) + { + break; + } + exception = exception.InnerException; + } + return sqlEx; + } + + /// + /// computes the name of the machine based on server's name (as returned by smoServer.Name) + /// + /// name of server ("",".","Server","Server\Instance",etc) + /// name of the machine hosting sql server instance + public static string GetMachineName(string sqlServerName) + { + System.Diagnostics.Debug.Assert(sqlServerName != null); + + string machineName = sqlServerName; + if (sqlServerName.Trim().Length != 0) + { + // [0] = machine, [1] = instance (if any) + return sqlServerName.Split('\\')[0]; + } + else + { + // we have default instance of default machine + return machineName; + } + } + + /// + /// Determines if a SqlException is Permission denied exception + /// + /// + /// + public static bool IsPermissionDeniedException(SqlException sqlException) + { + bool isPermDenied = false; + if (null != sqlException.Errors) + { + foreach (SqlError sqlError in sqlException.Errors) + { + int errorNumber = GetSqlErrorNumber(sqlError); + + if ((ObjectPermissionsDeniedErrorNumber == errorNumber) || + (ColumnPermissionsDeniedErrorNumber == errorNumber)) + { + isPermDenied = true; + break; + } + } + } + return isPermDenied; + } + + /// + /// Returns the error number of a sql exeception + /// + /// + /// + public static int GetSqlErrorNumber(SqlError sqlerror) + { + return sqlerror.Number; + } + + /// + /// Function doubles up specified character in a string + /// + /// + /// + /// + public static String EscapeString(string s, char cEsc) + { + StringBuilder sb = new StringBuilder(s.Length * 2); + foreach (char c in s) + { + sb.Append(c); + if (cEsc == c) + sb.Append(c); + } + return sb.ToString(); + } + + /// + /// Function doubles up ']' character in a string + /// + /// + /// + public static String EscapeStringCBracket(string s) + { + return CUtils.EscapeString(s, ']'); + } + + /// + /// Function doubles up '\'' character in a string + /// + /// + /// + public static String EscapeStringSQuote(string s) + { + return CUtils.EscapeString(s, '\''); + } + + /// + /// Function removes doubled up specified character from a string + /// + /// + /// + /// + public static String UnEscapeString(string s, char cEsc) + { + StringBuilder sb = new StringBuilder(s.Length); + bool foundBefore = false; + foreach (char c in s) + { + if (cEsc == c) // character to unescape + { + if (foundBefore) // skip second occurrence + { + foundBefore = false; + } + else // set the flag to skip next time around + { + sb.Append(c); + foundBefore = true; + } + } + else + { + sb.Append(c); + foundBefore = false; + } + } + return sb.ToString(); + } + + /// + /// Function removes doubled up ']' character from a string + /// + /// + /// + public static String UnEscapeStringCBracket(string s) + { + return CUtils.UnEscapeString(s, ']'); + } + + /// + /// Function removes doubled up '\'' character from a string + /// + /// + /// + public static String UnEscapeStringSQuote(string s) + { + return CUtils.UnEscapeString(s, '\''); + } + + /// + /// Helper method to convert DMTF format to DateTime from WMI property value + /// ManagementDateTimeConverter.ToDateTime() does not adjust time to UTC offset, + /// hence additional step to adjust datetime. + /// + /// + /// + //public static DateTime GetDateTimeFromDMTFTime(string dateTimeInDMTFFormat) + //{ + // string[] dateTimeInfo = dateTimeInDMTFFormat.Split(new char[] { '+', '-' }); + // DateTime dateTime = ManagementDateTimeConverter.ToDateTime(dateTimeInDMTFFormat); + + // TimeSpan timeSpan = TimeSpan.FromMinutes(Convert.ToDouble(dateTimeInfo[1])); + // if (dateTimeInDMTFFormat.Contains("+")) + // { + // dateTime = dateTime - timeSpan; + // } + // else + // { + // dateTime = dateTime + timeSpan; + // } + // return dateTime; + //} + + /// + /// Helper method to sort ManagementObjectCollection based ArchiveNumber property + /// + /// + /// + //public static ArrayList Sort(ManagementObjectCollection collection) + //{ + + // ArrayList array = new ArrayList(); + // array.AddRange(collection); + // ArchiveNoComparer comparer = new ArchiveNoComparer(); + // array.Sort(comparer); + // return array; + //} + + /// + /// Helper function to execute WQL + /// + /// + /// + /// + //public static ManagementObjectCollection ExecuteWQL(WmiSqlMgmtConnectionInfo wmiCi, string wql) + //{ + // ObjectQuery qry = new ObjectQuery(wql); + // ManagementScope scope = new ManagementScope(wmiCi.Namespace, wmiCi.ConnectionOptions); + // scope.Connect(); + // ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, qry); + // return searcher.Get(); + //} + + /// + /// Get the windows login name with the domain portion in all-caps + /// + /// The windows login name + /// The windows login name with the domain portion in all-caps + public static string CanonicalizeWindowsLoginName(string windowsLoginName) + { + string result; + int lastBackslashIndex = windowsLoginName.LastIndexOf("\\", StringComparison.Ordinal); + + if (-1 != lastBackslashIndex) + { + string domainName = windowsLoginName.Substring(0, lastBackslashIndex).ToUpperInvariant(); + string afterDomain = windowsLoginName.Substring(lastBackslashIndex); + + result = String.Concat(domainName, afterDomain); + } + else + { + result = windowsLoginName; + } + + return result; + + } + + /// + /// Launches object picker and gets the user selected login + /// + /// + /// + /// + /// Returns null in case Object picker doesn't returns any loginName + //public static string GetWindowsLoginNameFromObjectPicker(object sender, + // Smo.Server server, + // string errorMsgToShowForTooManyLogins) + //{ + // string loginName = null; + + // ObjectPickerWrapper.TargetMachine = server.Information.NetName; + // ObjectPickerWrapper.SingleObjectSelection = true; + + // ObjectPickerWrapper.GetUsersList(sender); + + // int userCount = ObjectPickerWrapper.UsersList.Count; + + // // if the user selected one NT login, set the edit control text to the selected login + // if (1 == userCount) + // { + // loginName = ObjectPickerWrapper.UsersList[0].ToString(); + + // if (loginName.Length != 0) + // { + // loginName = CanonicalizeWindowsLoginName(loginName); + // } + // } + // // if the user selected more than one login, display an error + // else if (1 < userCount) + // { + // SqlManagementUserControl sm = sender as SqlManagementUserControl; + // if (sm != null) + // { + // sm.DisplayExceptionMessage(new Exception(errorMsgToShowForTooManyLogins)); + // } + // else + // { + // throw new InvalidOperationException(errorMsgToShowForTooManyLogins); + // } + // } + + // return loginName; + //} + + /// + /// Determines how a feature should behave (e.g. enabled or disabled) for a SQL instance's SKU/edition. + /// + /// A SMO Server object connected to a local or remote SQL instance + /// The setting to check (one of the feature constants used in settings.dat) + /// The value of the setting (e.g. SqlbootConst.SKU_YES, SqlbootConst.VALUE_UNLIMITED, etc) + //public static uint QueryRemoteSqlProductValue(ServerConnection serverConnection, uint setting) + //{ + // if (serverConnection == null) + // { + // throw new ArgumentNullException("serverConnection"); + // } + + // // The instance could be remote, so we use GetSettingValueForSKUAbsolute because it allows us to + // // query the client-side SQLBOOT.DLL to ask what the setting's value would be for the server's SKU. + // // (Most other SQLBOOT APIs can only be used for locally-installed instances.) + // // First we must retrieve the server's edition ID (SKU ID). + // int editionId = (int)serverConnection.ExecuteScalar("SELECT SERVERPROPERTY('EditionId') AS EditionId"); + + // // Then ask SQLBOOT what the setting's value should be for that SKU. + // uint value = Sqlboot.GetSettingValueForSKUAbsolute(setting, unchecked((uint)editionId)); + // return value; + //} + } + + /// + /// Enum of time units types ( used in cleaning up history based on age ) + /// + internal enum TimeUnitType + { + Day, + Week, + Month, + Year + } + + /// + /// class to sort files based on archivenumber + /// + //internal class ArchiveNoComparer : IComparer + //{ + // public int Compare(Object x, Object y) + // { + // ManagementObject lhs = x as ManagementObject; + // ManagementObject rhs = y as ManagementObject; + + // if (null == lhs || null == rhs) + // { + // throw new ArgumentException("Object is not of type ManagementObject"); + // } + + // UInt32 l = Convert.ToUInt32(lhs.Properties["archivenumber"].Value); + // UInt32 r = Convert.ToUInt32(rhs.Properties["archivenumber"].Value); + + // int retVal = l.CompareTo(r); + + // return retVal; + // } + //} + + /// + /// Object used to populate default language in + /// database and user dialogs. + /// + internal class LanguageDisplay + { + private SMO.Language language; + + public string LanguageAlias + { + get + { + return language.Alias; + } + } + + public SMO.Language Language + { + get + { + return language; + } + } + + public LanguageDisplay(SMO.Language language) + { + this.language = language; + } + + public override string ToString() + { + return language.Alias; + } + } +} + + + + + + + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/CreateDatabaseData.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/CreateDatabaseData.cs new file mode 100644 index 00000000..384f6b30 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/CreateDatabaseData.cs @@ -0,0 +1,2733 @@ +// +// 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; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Resources; +using System.Data; +using System.IO; +using System.Text; +//using System.Windows.Forms; +//using System.Drawing.Design; +using Microsoft.SqlServer.Management.Common; +// using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Smo; +using Smo = Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Diagnostics; +//using Microsoft.NetEnterpriseServers; +using System.Globalization; +using System.Data.SqlClient; +using System.Collections.Generic; + +// using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; +using AzureEdition = Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper.AzureEdition; +using DataSet = Microsoft.Data.Tools.DataSets.DataSet; +using DataTable = Microsoft.Data.Tools.DataSets.DataTable; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + public enum DefaultCursor + { + Local, + Global + } + + public static class CDataContainerExtender + { + /// + /// Check if Query Store is supported. + /// + /// The data container. + /// The database. + /// True, if the Query Store feature is supported. + internal static bool IsQueryStoreSupported(this CDataContainer dataContainer, Smo.Database database) + { + // Query store is not supported on SQL DW. + // Query store is only supported on SQL 2016 and above and Azure V12 and above. + return (database.DatabaseEngineEdition != DatabaseEngineEdition.SqlDataWarehouse && + ((dataContainer.SqlServerVersion >= 13) || + (dataContainer.SqlServerVersion >= 12 && + dataContainer.Server.ServerType == DatabaseEngineType.SqlAzureDatabase))); + } + } + + /// + /// FileGroup Prototype + /// + public class FilegroupPrototype + { + #region data members + + private class FilegroupData + { + public string name; + public bool isReadOnly; + public bool isDefault; + public FileGroupType fileGroupType = FileGroupType.RowsFileGroup; + + /// + /// Creates an instance of FilegroupData + /// + public FilegroupData() + { + this.name = String.Empty; + this.isReadOnly = false; + this.isDefault = false; + } + + /// + /// Creates an instance of FilegroupData + /// + public FilegroupData(FileGroupType fileGroupType) + { + this.name = String.Empty; + this.isReadOnly = false; + this.isDefault = false; + this.fileGroupType = fileGroupType; + } + + /// + /// Initializes a new instance of the FilegroupData class. + /// + /// filegroup name + /// Readonly or not + /// Default filegroup or not + /// FileGroupType + public FilegroupData(string name, bool isReadOnly, bool isDefault, FileGroupType fileGroupType) + { + this.name = name; + this.isReadOnly = isReadOnly; + this.isDefault = isDefault; + this.fileGroupType = fileGroupType; + } + + /// + /// Creates an instance of FilegroupData from another instance + /// + /// + public FilegroupData(FilegroupData other) + { + this.name = other.name; + this.isReadOnly = other.isReadOnly; + this.isDefault = other.isDefault; + this.fileGroupType = other.fileGroupType; + } + + /// + /// Clones the instance oc FileGroupData + /// + /// + public FilegroupData Clone() + { + return new FilegroupData(this); + } + } + + private FilegroupData originalState; + private FilegroupData currentState; + private bool filegroupExists; + private bool removed; + private DatabasePrototype parent; + + #endregion + + #region properties + + /// + /// The name of the filegroup + /// + public string Name + { + get { return this.currentState.name; } + + set + { + if (!this.Exists) + { + string oldname = this.currentState.name; + this.currentState.name = value; + + this.NotifyFileGroupNameChanged(oldname, this.currentState.name); + this.parent.NotifyObservers(); + } + } + } + + /// + /// Whether the filegroup is read-only + /// + public bool IsReadOnly + { + get { return this.currentState.isReadOnly; } + + set + { + this.currentState.isReadOnly = value; + this.parent.NotifyObservers(); + } + } + + /// + /// Whether the filegroup is the default filegroup + /// + public bool IsDefault + { + get { return this.currentState.isDefault; } + + set + { + if (this.currentState.isDefault != value) + { + this.currentState.isDefault = value; + NotifyFileGroupDefaultChanged(!value, value); + this.parent.NotifyObservers(); + } + } + } + + /// + /// Whether the filegroup is of filestream type + /// + public bool IsFileStream + { + get { return (this.currentState.fileGroupType == Smo.FileGroupType.FileStreamDataFileGroup); } + } + + /// + /// Whether the filegroup is of memory Optimized type + /// + public bool IsMemoryOptimized + { + get { return (this.currentState.fileGroupType == Smo.FileGroupType.MemoryOptimizedDataFileGroup); } + } + + /// + /// FileGroupType + /// + public FileGroupType FileGroupType + { + get { return this.currentState.fileGroupType; } + + set + { + this.currentState.fileGroupType = value; + this.parent.NotifyObservers(); + } + } + + /// + /// Whether the file group exists on the server + /// + public bool Exists + { + get { return this.filegroupExists; } + + set { this.filegroupExists = value; } + } + + /// + /// Whether the filegroup was removed + /// + public bool Removed + { + get { return this.removed; } + + set + { + this.removed = value; + this.parent.NotifyObservers(); + } + } + + #endregion + + /// + /// File group name changed event + /// + public event FileGroupNameChangedEventHandler OnFileGroupNameChangedHandler; + + /// + /// File group default status changed event + /// + public event FileGroupDefaultChangedEventHandler OnFileGroupDefaultChangedHandler; + + /// + /// File group deleted event + /// + public event FileGroupDeletedEventHandler OnFileGroupDeletedHandler; + + /// + /// Constructor + /// + public FilegroupPrototype(DatabasePrototype parent) + { + this.originalState = new FilegroupData(); + this.currentState = this.originalState.Clone(); + this.parent = parent; + + this.filegroupExists = false; + this.removed = false; + } + + /// + /// Creates an instance of FilegroupPrototype + /// + /// + /// + public FilegroupPrototype(DatabasePrototype parent, FileGroupType filegroupType) + { + this.originalState = new FilegroupData(filegroupType); + this.currentState = this.originalState.Clone(); + this.parent = parent; + + this.filegroupExists = false; + this.removed = false; + } + + /// + /// Initializes a new instance of the FilegroupPrototype class. + /// + /// instance of DatabasePrototype + /// file group name + /// whether it is readonly or not + /// is default or not + /// filegrouptype + /// filegroup exists or not + public FilegroupPrototype(DatabasePrototype parent, string name, bool isReadOnly, bool isDefault, + FileGroupType filegroupType, bool exists) + { + this.originalState = new FilegroupData(name, isReadOnly, isDefault, filegroupType); + this.currentState = this.originalState.Clone(); + this.parent = parent; + + this.filegroupExists = exists; + this.removed = false; + } + + /// + /// Create, Alter, or Drop the filegroup on the server + /// + public void ApplyChanges(Database db) + { + if (this.ChangesExist()) + { + if (this.Removed) + { + if (this.Exists) + { + db.FileGroups[this.Name].Drop(); + } + } + else + { + FileGroup fg = null; + bool filegroupChanged = false; + + if (this.Exists) + { + fg = db.FileGroups[this.Name]; + } + else + { + fg = new FileGroup(db, this.Name, this.FileGroupType); + db.FileGroups.Add(fg); + } + + if (!this.Exists || (fg.ReadOnly != this.IsReadOnly)) + { + fg.ReadOnly = this.IsReadOnly; + filegroupChanged = true; + } + + if (this.Exists && filegroupChanged) + { + fg.Alter(); + } + } + } + } + + /// + /// Would applying changes do anything? + /// + /// True if changes exist, false otherwise + public bool ChangesExist() + { + // name changes can only happen for non-existent filegroups, so no need to check for name changes + + bool result = ( + !this.Exists || + this.Removed || + (this.originalState.isDefault != this.currentState.isDefault) || + (this.originalState.isReadOnly != this.currentState.isReadOnly)); + + return result; + } + + /// + /// Notify listeners that this filegroup has been deleted + /// + /// + public void NotifyFileGroupDeleted(FilegroupPrototype defaultFilegroup) + { + this.Removed = true; + + if (OnFileGroupDeletedHandler != null) + { + FilegroupDeletedEventArgs e = new FilegroupDeletedEventArgs(this, defaultFilegroup); + OnFileGroupDeletedHandler(this, e); + } + } + + /// + /// Notify observers that the file group name has changed + /// + /// The file group name before the change + /// The file group name after the change + private void NotifyFileGroupNameChanged(string oldName, string newName) + { + if (OnFileGroupNameChangedHandler != null) + { + NameChangedEventArgs e = new NameChangedEventArgs(oldName, newName); + OnFileGroupNameChangedHandler(this, e); + } + } + + /// + /// Notify observers that the file group's default status has changed + /// + /// The old default-ness + /// THe new default-ness + private void NotifyFileGroupDefaultChanged(bool oldValue, bool newValue) + { + if (OnFileGroupDefaultChangedHandler != null) + { + BooleanValueChangedEventArgs e = new BooleanValueChangedEventArgs(oldValue, newValue); + OnFileGroupDefaultChangedHandler(this, e); + } + } + } + + + /// + /// File type - Data or Log + /// + public enum FileType + { + Data, + Log, + FileStream + } + + /// + /// File auto-growth data + /// + public class Autogrowth + { + #region data members + + private int growthInPercent; + private double growthInKilobytes; + private double maximumFileSize; + private bool isEnabled; + private bool isGrowthInPercent; + private bool isGrowthRestricted; + + private DatabasePrototype parent; + + #endregion + + #region properties + + /// + /// Whether auto-growth is enabled + /// + public bool IsEnabled + { + get { return this.isEnabled; } + + set { this.isEnabled = value; } + } + + /// + /// Whether auto-growth is in percent + /// + /// + /// true means growth is in percent, false means growth is in megabytes + /// + public bool IsGrowthInPercent + { + get { return this.isGrowthInPercent; } + + set { this.isGrowthInPercent = value; } + } + + /// + /// How much the file grows when it grows, in percent + /// + public int GrowthInPercent + { + get { return this.growthInPercent; } + + set { this.growthInPercent = value; } + } + + /// + /// How much the file grows when it grows in kilobytes + /// + public double GrowthInKilobytes + { + get { return this.growthInKilobytes; } + + set { this.growthInKilobytes = value; } + } + + /// + /// How much the file grows when it grows in kilobytes + /// + public int GrowthInMegabytes + { + get { return DatabaseFilePrototype.KilobytesToMegabytes(this.growthInKilobytes); } + + set { this.growthInKilobytes = DatabaseFilePrototype.MegabytesToKilobytes(value); } + } + + + /// + /// Whether file growth is restricted + /// + public bool IsGrowthRestricted + { + get { return this.isGrowthRestricted; } + + set { this.isGrowthRestricted = value; } + } + + /// + /// The maximum size of the file in megabytes + /// + public int MaximumFileSizeInMegabytes + { + get { return DatabaseFilePrototype.KilobytesToMegabytes(this.maximumFileSize); } + + set { this.maximumFileSize = DatabaseFilePrototype.MegabytesToKilobytes(value); } + } + + /// + /// The maximum size of the file in megabytes + /// + public double MaximumFileSizeInKilobytes + { + get { return this.maximumFileSize; } + + set { this.maximumFileSize = value; } + } + + + #endregion + + /// + /// Constructor + /// + public Autogrowth(DatabasePrototype parent) + { + Reset(); + this.parent = parent; + } + + /// + /// Copy constructor + /// + /// the instance to copy + public Autogrowth(Autogrowth other) + { + this.growthInKilobytes = other.growthInKilobytes; + this.growthInPercent = other.growthInPercent; + this.maximumFileSize = other.maximumFileSize; + this.isEnabled = other.isEnabled; + this.isGrowthInPercent = other.isGrowthInPercent; + this.isGrowthRestricted = other.isGrowthRestricted; + this.parent = other.parent; + } + + /// + /// Constructor - extracts autogrowth information from a SMO DataFile object + /// + /// + /// The file whose autogrowth information is to be extracted + public Autogrowth(DatabasePrototype parent, DataFile file) + { + // this code looks stunningly similar to the code in Autogrowth(LogFile), + // but we can't use polymorphism to handle this because LogFile and DataFile + // have no common base class or shared interface. + + if (FileGrowthType.None == file.GrowthType) + { + this.isEnabled = false; + this.isGrowthInPercent = true; + this.growthInPercent = 10; + this.growthInKilobytes = 10240.0; + this.isGrowthRestricted = false; + this.maximumFileSize = 102400.0; + } + else + { + this.isEnabled = true; + + if (FileGrowthType.Percent == file.GrowthType) + { + this.isGrowthInPercent = true; + this.growthInPercent = (int) file.Growth; + this.growthInKilobytes = 10240.0; + + // paranoia - make sure percent amount is greater than 1 + if (this.growthInPercent < 1) + { + this.growthInPercent = 1; + } + } + else + { + this.isGrowthInPercent = false; + this.growthInKilobytes = file.Growth; + this.growthInPercent = 10; + } + + // note: double-precision math comparisons - can't do != or == + if (1e-16 < file.MaxSize) + { + // max file size is greater than zero + this.isGrowthRestricted = true; + this.maximumFileSize = file.MaxSize; + } + else + { + this.isGrowthRestricted = false; + this.maximumFileSize = 102400.0; + } + } + + this.parent = parent; + } + + /// + /// Constructor - extracts autogrowth information from a SMO LogFile object + /// + /// + /// The file whose autogrowth information is to be extracted + public Autogrowth(DatabasePrototype parent, LogFile file) + { + // this code looks stunningly similar to the code in Autogrowth(DataFile), + // but we can't use polymorphism to handle this because LogFile and DataFile + // have no common base class or shared interface. + + FileGrowthType fileGrowthType = FileGrowthType.None; + + try + { + fileGrowthType = file.GrowthType; + + } + catch (Exception) + { + /// do nothing + } + + if (FileGrowthType.None == fileGrowthType) + { + this.isEnabled = false; + this.isGrowthInPercent = true; + this.growthInPercent = 10; + this.growthInKilobytes = 10240.0; + this.isGrowthRestricted = false; + this.maximumFileSize = 102400.0; + } + else + { + this.isEnabled = true; + + if (FileGrowthType.Percent == fileGrowthType) + { + this.isGrowthInPercent = true; + this.growthInPercent = (int) file.Growth; + this.growthInKilobytes = 10240.0; + + // paranoia - make sure percent amount is greater than 1 + if (this.growthInPercent < 1) + { + this.growthInPercent = 1; + } + } + else + { + this.isGrowthInPercent = false; + this.growthInKilobytes = file.Growth; + this.growthInPercent = 10; + } + + // note: double-precision math comparisons - can't do != or == + if (1e-16 < file.MaxSize) + { + // max file size is greater than zero + this.isGrowthRestricted = true; + this.maximumFileSize = file.MaxSize; + } + else + { + this.isGrowthRestricted = false; + this.maximumFileSize = 102400.0; + } + } + + this.parent = parent; + + } + + /// + /// Reset the property values to their defaults + /// + public void Reset() + { + this.IsEnabled = true; + this.IsGrowthInPercent = true; + this.IsGrowthRestricted = false; + + this.GrowthInPercent = 10; + this.GrowthInKilobytes = 10240.0; + this.MaximumFileSizeInKilobytes = 102400.0; + } + + + /// + /// Determine whether this Autogrowth has the same value as another Autogrowth + /// + /// The Autogrowth to compare with + /// True if values are the same, false otherwise + public bool HasSameValueAs(Autogrowth other) + { + bool result = true; + + if (this.isEnabled != other.isEnabled) + { + result = false; + } + else if (this.isEnabled) + { + if ((this.isGrowthInPercent != other.isGrowthInPercent) || + (this.isGrowthRestricted != other.isGrowthRestricted) || + (this.isGrowthInPercent && (this.growthInPercent != other.growthInPercent)) || + (!this.isGrowthInPercent && (this.growthInKilobytes != other.growthInKilobytes)) || + (this.isGrowthRestricted && (this.maximumFileSize != other.maximumFileSize))) + { + result = false; + } + } + + return result; + } + + /// + /// Represent the auto-growth settings as a single string + /// + /// + /// The format is such that the result can be put into the Autogrowth column + /// of the grid on the CreateDatabaseGeneral form. + /// + /// The string representation + public override string ToString() + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + this.GetType().GetAssembly()); + string result = ""; + + if (this.IsEnabled) + { + if (this.IsGrowthRestricted) + { + if (this.IsGrowthInPercent) + { + result = String.Format(System.Globalization.CultureInfo.CurrentCulture, + manager.GetString("prototype.autogrowth.restrictedGrowthByPercent"), + this.GrowthInPercent, + this.MaximumFileSizeInMegabytes); + } + else + { + result = String.Format(System.Globalization.CultureInfo.CurrentCulture, + manager.GetString("prototype.autogrowth.restrictedGrowthByMB"), + this.GrowthInMegabytes, + this.MaximumFileSizeInMegabytes); + } + } + else + { + if (this.IsGrowthInPercent) + { + result = String.Format(System.Globalization.CultureInfo.CurrentCulture, + manager.GetString("prototype.autogrowth.unrestrictedGrowthByPercent"), + this.GrowthInPercent); + } + else + { + result = String.Format(System.Globalization.CultureInfo.CurrentCulture, + manager.GetString("prototype.autogrowth.unrestrictedGrowthByMB"), + this.GrowthInMegabytes); + } + + + } + + + } + else + { + result = manager.GetString("prototype.autogrowth.disabled"); + } + + return result; + } + } + + + /// + /// Prototype database file + /// + public class DatabaseFilePrototype + { + #region data members + + private class FileData + { + public string name; + public string physicalName; + public string folder; + public FileType fileType; + public FilegroupPrototype filegroup; + public double initialSize; + public Autogrowth autogrowth; + public bool isPrimaryFile; + + /// + /// Creates instance of FileData + /// + /// + /// + public FileData(DatabasePrototype parent, FileType type) + { + this.name = String.Empty; + this.physicalName = String.Empty; + this.folder = String.Empty; + this.fileType = type; + this.filegroup = null; + this.initialSize = 1024.0d; + this.autogrowth = new Autogrowth(parent); + this.isPrimaryFile = false; + } + + /// + /// Creates instaance of FileData + /// + /// + /// + /// + public FileData(DatabasePrototype parent, FilegroupPrototype filegroup, DataFile file) + { + this.autogrowth = new Autogrowth(parent, file); + this.name = file.Name; + + if (file.FileName.EndsWith(":", StringComparison.Ordinal)) + { + // the data file is on a raw device + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + this.physicalName = manager.GetString("general_rawDevice"); + this.folder = file.FileName; + } + else + { + this.physicalName = Path.GetFileName(file.FileName); + this.folder = PathWrapper.GetDirectoryName(file.FileName); + } + + this.initialSize = file.Size; + this.filegroup = filegroup; + this.isPrimaryFile = file.IsPrimaryFile; + + switch (filegroup.FileGroupType) + { + case FileGroupType.RowsFileGroup: + this.fileType = FileType.Data; + break; + + case FileGroupType.FileStreamDataFileGroup: + case FileGroupType.MemoryOptimizedDataFileGroup: + this.fileType = FileType.FileStream; + break; + + default: + throw new InvalidArgumentException("Unsupported filegroup type"); + + } + } + + /// + /// Creates instance of FileData + /// + /// + /// + public FileData(DatabasePrototype parent, LogFile file) + { + this.autogrowth = new Autogrowth(parent, file); + this.name = file.Name; + + try + { + if (file.FileName.EndsWith(":", StringComparison.Ordinal)) + { + // the log file is on a raw device + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + this.physicalName = manager.GetString("general_rawDevice"); + this.folder = file.FileName; + } + else + { + this.physicalName = Path.GetFileName(file.FileName); + this.folder = PathWrapper.GetDirectoryName(file.FileName); + } + + this.initialSize = file.Size; + } + catch (Exception) + { + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + this.physicalName = manager.GetString("unavailable"); + this.folder = manager.GetString("unavailable"); + this.initialSize = 0; + } + + this.fileType = FileType.Log; + this.filegroup = null; + this.isPrimaryFile = false; + } + + /// + /// Creates a instance of FileData from another instance + /// + /// + public FileData(FileData other) + { + this.name = other.name; + this.physicalName = other.physicalName; + this.folder = other.folder; + this.fileType = other.fileType; + this.filegroup = other.filegroup; + this.initialSize = other.initialSize; + this.autogrowth = new Autogrowth(other.autogrowth); + this.isPrimaryFile = other.isPrimaryFile; + } + + /// + /// Clone current instance of FileData + /// + /// + public FileData Clone() + { + return new FileData(this); + } + } + + private FileData originalState; + private FileData currentState; + + private string defaultDataFolder = String.Empty; + private string defaultLogFolder = String.Empty; + private double defaultDataFileSize; + private double defaultLogFileSize; + private Autogrowth defaultDataAutogrowth; + private Autogrowth defaultLogAutogrowth; + private bool usingDefaultFolder; + + private bool fileExists; + private bool removed; + private DatabasePrototype database; + + private const double kilobytesPerMegabyte = 1024.0d; + + #endregion + + #region properties + + /// + /// The logical name of the file, without extension + /// + public string Name + { + get { return this.currentState.name; } + + set + { + this.currentState.name = value; + this.database.NotifyObservers(); + } + } + + /// + /// The physical name of the file + /// + public string PhysicalName + { + get { return this.currentState.physicalName; } + + set + { + this.currentState.physicalName = value; + this.database.NotifyObservers(); + } + } + + /// + /// The folder in which the file is to be created + /// + public string Folder + { + get { return this.currentState.folder; } + + set + { + this.currentState.folder = value; + this.usingDefaultFolder = (0 == String.Compare(value, this.DefaultFolder, StringComparison.Ordinal)); + this.database.NotifyObservers(); + } + } + + /// + /// The type of the file, either Log or Data + /// + public FileType DatabaseFileType + { + get { return this.currentState.fileType; } + + set + { + this.currentState.fileType = value; + + if (this.usingDefaultFolder) + { + this.currentState.folder = this.DefaultFolder; + } + + this.database.NotifyObservers(); + } + } + + /// + /// The prototype of the filegroup that is to contain this file + /// + public FilegroupPrototype FileGroup + { + get { return this.currentState.filegroup; } + + set + { + if ((FileType.Data == this.currentState.fileType || + FileType.FileStream == this.currentState.fileType) && !this.Exists && (value != null)) + { + if (this.currentState.filegroup != null) + { + this.currentState.filegroup.OnFileGroupDeletedHandler -= + new FileGroupDeletedEventHandler(OnFilegroupDeleted); + } + + this.currentState.filegroup = value; + this.currentState.filegroup.OnFileGroupDeletedHandler += + new FileGroupDeletedEventHandler(OnFilegroupDeleted); + this.database.NotifyObservers(); + } + } + } + + /// + /// The initial size of the file in Megabytes + /// + public int InitialSize + { + get + { + // size kept in kilobytes internally + return DatabaseFilePrototype.KilobytesToMegabytes(this.currentState.initialSize); + } + + set + { + // size kept in kilobytes internally + this.currentState.initialSize = DatabaseFilePrototype.MegabytesToKilobytes(value); + this.database.NotifyObservers(); + } + } + + /// + /// Auto-growth data for the file + /// + public Autogrowth Autogrowth + { + get { return this.currentState.autogrowth; } + + set + { + this.currentState.autogrowth = new Autogrowth(value); + this.database.NotifyObservers(); + } + } + + /// + /// Whether this is the primary data file (the file with the .mdf extension) + /// + public bool IsPrimaryFile + { + get { return this.currentState.isPrimaryFile; } + + set + { + this.currentState.isPrimaryFile = value; + this.database.NotifyObservers(); + } + } + + /// + /// The default folder for this file type + /// + public string DefaultFolder + { + get + { + if (this.currentState.fileType == FileType.Log) + { + return this.defaultLogFolder; + } + else + { + return this.defaultDataFolder; + } + } + } + + /// + /// The default size for this file type + /// + public int DefaultSize + { + get + { + if (this.currentState.fileType == FileType.Log) + { + return DatabaseFilePrototype.KilobytesToMegabytes(this.defaultLogFileSize); + } + else + { + return DatabaseFilePrototype.KilobytesToMegabytes(this.defaultDataFileSize); + } + } + set + { + if (this.currentState.fileType == FileType.Log) + { + this.defaultLogFileSize = DatabaseFilePrototype.MegabytesToKilobytes(value); + } + else + { + this.defaultDataFileSize = DatabaseFilePrototype.MegabytesToKilobytes(value); + } + } + } + + /// + /// The default size for this file type + /// + public Autogrowth DefaultAutogrowth + { + get + { + if (this.currentState.fileType == FileType.Log) + { + return this.defaultLogAutogrowth; + } + else + { + return this.defaultDataAutogrowth; + } + } + set + { + if (this.currentState.fileType == FileType.Log) + { + this.defaultLogAutogrowth = value; + } + else + { + this.defaultDataAutogrowth = value; + } + } + } + + /// + /// Whether the file exists on the server + /// + public bool Exists + { + get { return this.fileExists; } + + set { this.fileExists = value; } + } + + /// + /// Whether the file was removed + /// + public bool Removed + { + get { return this.removed; } + + set + { + this.removed = value; + this.database.NotifyObservers(); + } + } + + #endregion + + /// + /// Constructor for new files + /// + /// server information + /// The parent database prototype + /// The type of the file + public DatabaseFilePrototype(CDataContainer context, + DatabasePrototype database, + FileType type) + { + Initialize(context, database, type); + } + + /// + /// Constructor for new files whose name is known + /// + /// server information + /// The parent database prototype + /// The type of the file + /// The name of the file, without extension + public DatabaseFilePrototype(CDataContainer context, + DatabasePrototype database, + FileType type, + string name) + { + Initialize(context, database, type); + + this.currentState.name = name; + } + + /// + /// Constructor for existing data files + /// + /// Prototype database containing the prototype file + /// Prototype file group containing the prototype file + /// The SMO DataFile object whose definition is to be extracted + public DatabaseFilePrototype(DatabasePrototype database, + FilegroupPrototype filegroup, + DataFile file) + { + this.originalState = new FileData(database, filegroup, file); + this.currentState = this.originalState.Clone(); + + this.fileExists = true; + this.removed = false; + this.database = database; + } + + /// + /// Constructor for existing log files + /// + /// Prototype database containing the prototype file + /// The SMO LogFile object whose definition is to be extracted + public DatabaseFilePrototype(DatabasePrototype database, LogFile file) + { + this.originalState = new FileData(database, file); + this.currentState = this.originalState.Clone(); + + this.fileExists = true; + this.removed = false; + this.database = database; + } + + /// + /// Apply the changes to the database + /// + /// The database whose definition is being modified + public void ApplyChanges(Database db) + { + if (this.ChangesExist()) + { + if (this.Removed) + { + this.RemoveFile(db); + } + else + { + switch (this.DatabaseFileType) + { + case FileType.Data: + this.CreateOrAlterDataFile(db); + break; + + case FileType.Log: + this.CreateOrAlterLogFile(db); + break; + + case FileType.FileStream: + this.CreateOrAlterFileStreamFile(db); + break; + + default: + throw new InvalidOperationException("Invalid DatabaseFileType"); + } + } + } + } + + /// + /// Would calling ApplyChanges change anything? + /// + /// True if changes exist, false otherwise + public bool ChangesExist() + { + bool result = ( + !this.Exists || + this.Removed || + (this.currentState.fileType != this.originalState.fileType) || + (this.currentState.filegroup != this.originalState.filegroup) || + (this.currentState.isPrimaryFile != this.originalState.isPrimaryFile) || + (this.currentState.initialSize != this.originalState.initialSize) || + !this.currentState.autogrowth.HasSameValueAs(this.originalState.autogrowth) || + (0 != String.Compare(this.currentState.name, this.originalState.name, StringComparison.Ordinal)) || + (0 != String.Compare(this.currentState.folder, this.originalState.folder, StringComparison.Ordinal))); + + return result; + } + + /// + /// Remove an existing file from the database + /// + /// The database from which the file is to be removed + private void RemoveFile(Database db) + { + if (this.Exists) + { + if (FileType.Log == this.DatabaseFileType) + { + LogFile lf = db.LogFiles[this.originalState.name]; + if (lf != null) + { + lf.Drop(); + } + } + else + { + if (!this.currentState.filegroup.Removed) + { + DataFile df = db.FileGroups[this.FileGroup.Name].Files[this.originalState.name]; + if (df != null) + { + df.Drop(); + } + } + } + } + + } + + /// + /// Create a data file + /// + /// The database object that is to contain the new file + private void CreateOrAlterDataFile(Database db) + { + // note: This method looks strikingly similar to CreateLogFile(), below. + // The initialization code can't be shared because LogFile and DataFile have + // no common base class, so we can't use polymorphism to our advantage. + + // form the file path, create the data file + string fileSuffix = this.IsPrimaryFile ? ".mdf" : ".ndf"; + DataFile file = null; + FileGroup fg = db.FileGroups[this.FileGroup.Name]; + + if (this.Exists) + { + file = db.FileGroups[this.FileGroup.Name].Files[this.originalState.name]; + + if (!file.Name.Equals(this.Name)) + { + file.Rename(this.Name); + } + } + else + { + file = new DataFile(fg, this.Name); + fg.Files.Add(file); + } + + if (this.IsPrimaryFile && !this.Exists) + { + file.IsPrimaryFile = true; + } + + // set the file name (you can't change the file name after the file has been created) + if (!this.Exists) + { + file.FileName = MakeDiskFileName(this.Name, this.PhysicalName, fileSuffix); + } + + // set its initial size + double originalSize = this.originalState.initialSize; + double newSize = this.currentState.initialSize; + + // if the file does not exist or if the existing file size was changed in the UI and is + // significantly larger than the value on the server, set the file size + if (!this.Exists || ((1e-6 < Math.Abs(newSize - originalSize)) && (1e-6 < (newSize - file.Size)))) + { + file.Size = newSize; + } + else if ((newSize < originalSize) && (newSize < file.Size)) + { + file.Shrink(KilobytesToMegabytes(newSize), ShrinkMethod.Default); + } + + if (!this.Exists) + { + // if auto-growth is enabled, set auto-growth data + if (this.Autogrowth.IsEnabled) + { + if (this.Autogrowth.IsGrowthRestricted) + { + file.MaxSize = this.Autogrowth.MaximumFileSizeInKilobytes; + } + + if (this.Autogrowth.IsGrowthInPercent) + { + file.GrowthType = FileGrowthType.Percent; + file.Growth = (double) this.Autogrowth.GrowthInPercent; + } + else + { + file.GrowthType = FileGrowthType.KB; + file.Growth = this.Autogrowth.GrowthInKilobytes; + } + } + else + { + file.GrowthType = FileGrowthType.None; + } + } + else + { + FileGrowthType newFileGrowthType = FileGrowthType.None; + FileGrowthType originalFileGrowthType = FileGrowthType.None; + double newGrowth = 0.0; + double originalGrowth = 0.0; + double newMaxSize = 0.0; + double originalMaxSize = 0.0; + + if (this.currentState.autogrowth.IsEnabled) + { + if (this.currentState.autogrowth.IsGrowthRestricted) + { + newMaxSize = this.currentState.autogrowth.MaximumFileSizeInKilobytes; + } + + if (this.currentState.autogrowth.IsGrowthInPercent) + { + newFileGrowthType = FileGrowthType.Percent; + newGrowth = (double) this.currentState.autogrowth.GrowthInPercent; + } + else + { + newFileGrowthType = FileGrowthType.KB; + newGrowth = this.currentState.autogrowth.GrowthInKilobytes; + } + } + + if (this.originalState.autogrowth.IsEnabled) + { + if (this.originalState.autogrowth.IsGrowthRestricted) + { + originalMaxSize = this.originalState.autogrowth.MaximumFileSizeInKilobytes; + } + + if (this.originalState.autogrowth.IsGrowthInPercent) + { + originalFileGrowthType = FileGrowthType.Percent; + originalGrowth = (double) this.originalState.autogrowth.GrowthInPercent; + } + else + { + originalFileGrowthType = FileGrowthType.KB; + originalGrowth = this.originalState.autogrowth.GrowthInKilobytes; + } + } + + // if file growth type has changed in the UI and is different from the value on the server + if ((newFileGrowthType != originalFileGrowthType) && (file.GrowthType != newFileGrowthType)) + { + // change the file growth type + file.GrowthType = newFileGrowthType; + } + + // if the growth amount has changed in the UI and is different from the value on the server + if ((1e-6 < (Math.Abs(newGrowth - originalGrowth))) && (1e-6 < Math.Abs(newGrowth - file.Growth))) + { + // change the growth amount + file.Growth = newGrowth; + } + + // when file size is unlimited, MaxSize is non-positive. For the + // purposes of our code, convert non-positive to zero, then compare + // vs. the prototype file size. + double fileMaxSize = (0.0 <= file.MaxSize) ? file.MaxSize : 0.0; + + // if the max size has changed in the UI and is different from the value on the server + if ((1e-6 < Math.Abs(originalMaxSize - newMaxSize)) && (1e-6 < Math.Abs(fileMaxSize - newMaxSize))) + { + // set the file max size + file.MaxSize = newMaxSize; + } + } + } + + /// + /// Create a filestream file + /// + /// The database object that is to contain the new file + private void CreateOrAlterFileStreamFile(Database db) + { + // form the file path, create the data file + DataFile file = null; + FileGroup fg = db.FileGroups[this.FileGroup.Name]; + + if (this.Exists) + { + file = db.FileGroups[this.FileGroup.Name].Files[this.originalState.name]; + + if (!file.Name.Equals(this.Name)) + { + file.Rename(this.Name); + } + } + else + { + file = new DataFile(fg, this.Name); + fg.Files.Add(file); + } + + // set the file name (you can't change the file name after the file has been created) + if (!this.Exists) + { + // filestream files ignore the filename but if provided it just puts it + // into the transact-sql query. If in future that query starts using the + // filename in some manner we get that functionality automatically by + // passing in the same name to the server. -anchals + file.FileName = MakeDiskFileName(this.Name, this.PhysicalName, String.Empty); + } + + if (this.database.ServerVersion.Major >= 11) + { + double newMaxSize = 0.0; + double originalMaxSize = 0.0; + if (!this.Exists) + { + if (this.Autogrowth.IsGrowthRestricted) + { + file.MaxSize = this.Autogrowth.MaximumFileSizeInKilobytes; + } + } + else + { + if (this.currentState.autogrowth.IsGrowthRestricted) + { + newMaxSize = this.currentState.autogrowth.MaximumFileSizeInKilobytes; + } + if (this.originalState.autogrowth.IsGrowthRestricted) + { + originalMaxSize = this.originalState.autogrowth.MaximumFileSizeInKilobytes; + } + + // when file size is unlimited, MaxSize is non-positive. For the + // purposes of our code, convert non-positive to zero, then compare + // vs. the prototype file size. + double fileMaxSize = (0.0 <= file.MaxSize) ? file.MaxSize : 0.0; + + // if the max size has changed in the UI and is different from the value on the server + if ((1e-6 < Math.Abs(originalMaxSize - newMaxSize)) && (1e-6 < Math.Abs(fileMaxSize - newMaxSize))) + { + // set the file max size + file.MaxSize = newMaxSize; + } + } + + } + } + + /// + /// Create a log file + /// + /// The database object that is to contain the new file + private void CreateOrAlterLogFile(Database db) + { + // note: This method looks strikingly similar to CreateDataFile(), above. + // The initialization code can't be shared because LogFile and DataFile have + // no common base class, so we can't use polymorphism to our advantage. + + LogFile file = null; + + if (this.Exists) + { + file = db.LogFiles[this.originalState.name]; + + if (!file.Name.Equals(this.Name)) + { + file.Rename(this.Name); + } + } + else + { + file = new LogFile(db, this.Name); + db.LogFiles.Add(file); + } + + // set its path and initial size + if (!this.Exists) + { + file.FileName = MakeDiskFileName(this.Name, this.PhysicalName, ".ldf"); + } + + // set its initial size + double originalSize = this.originalState.initialSize; + double newSize = this.currentState.initialSize; + + // if the file does not exist or if the existing file size was changed in the UI and is + // significantly larger than the value on the server, set the file size + if (!this.Exists || ((1e-6 < Math.Abs(newSize - originalSize)) && (1e-6 < (newSize - file.Size)))) + { + file.Size = newSize; + } + else if ((newSize < originalSize) && (newSize < file.Size)) + { + file.Shrink(KilobytesToMegabytes(newSize), ShrinkMethod.Default); + } + + + // if auto-growth is enabled, set auto-growth data + if (!this.Exists) + { + // if auto-growth is enabled, set auto-growth data + if (this.Autogrowth.IsEnabled) + { + if (this.Autogrowth.IsGrowthRestricted) + { + file.MaxSize = this.Autogrowth.MaximumFileSizeInKilobytes; + } + + if (this.Autogrowth.IsGrowthInPercent) + { + file.GrowthType = FileGrowthType.Percent; + file.Growth = (double) this.Autogrowth.GrowthInPercent; + } + else + { + file.GrowthType = FileGrowthType.KB; + file.Growth = this.Autogrowth.GrowthInKilobytes; + } + } + else + { + file.GrowthType = FileGrowthType.None; + } + } + else + { + FileGrowthType newFileGrowthType = FileGrowthType.None; + FileGrowthType originalFileGrowthType = FileGrowthType.None; + double newGrowth = 0.0; + double originalGrowth = 0.0; + double newMaxSize = 0.0; + double originalMaxSize = 0.0; + + if (this.currentState.autogrowth.IsEnabled) + { + if (this.currentState.autogrowth.IsGrowthRestricted) + { + newMaxSize = this.currentState.autogrowth.MaximumFileSizeInKilobytes; + } + + if (this.currentState.autogrowth.IsGrowthInPercent) + { + newFileGrowthType = FileGrowthType.Percent; + newGrowth = (double) this.currentState.autogrowth.GrowthInPercent; + } + else + { + newFileGrowthType = FileGrowthType.KB; + newGrowth = this.currentState.autogrowth.GrowthInKilobytes; + } + } + + if (this.originalState.autogrowth.IsEnabled) + { + if (this.originalState.autogrowth.IsGrowthRestricted) + { + originalMaxSize = this.originalState.autogrowth.MaximumFileSizeInKilobytes; + } + + if (this.originalState.autogrowth.IsGrowthInPercent) + { + originalFileGrowthType = FileGrowthType.Percent; + originalGrowth = (double) this.originalState.autogrowth.GrowthInPercent; + } + else + { + originalFileGrowthType = FileGrowthType.KB; + originalGrowth = this.originalState.autogrowth.GrowthInKilobytes; + } + } + + // if file growth type has changed in the UI and is different from the value on the server + if ((newFileGrowthType != originalFileGrowthType) && (file.GrowthType != newFileGrowthType)) + { + // change the file growth type + file.GrowthType = newFileGrowthType; + } + + // if the growth amount has changed in the UI and is different from the value on the server + if ((1e-6 < (Math.Abs(newGrowth - originalGrowth))) && (1e-6 < Math.Abs(newGrowth - file.Growth))) + { + // change the growth amount + file.Growth = newGrowth; + } + + // when file size is unlimited, MaxSize is non-positive. For the + // purposes of our code, convert non-positive to zero, then compare + // vs. the prototype file size. + double fileMaxSize = (0.0 <= file.MaxSize) ? file.MaxSize : 0.0; + + // if the max size has changed in the UI and is different from the value on the server + if ((1e-6 < Math.Abs(originalMaxSize - newMaxSize)) && (1e-6 < Math.Abs(fileMaxSize - newMaxSize))) + { + // set the file max size + file.MaxSize = newMaxSize; + } + } + } + + /// + /// Shared construction code for new files + /// + /// + /// The parent database prototype + /// The type of the file + private void Initialize(CDataContainer context, DatabasePrototype database, FileType type) + { + this.originalState = new FileData(database, type); + + + this.fileExists = false; + this.removed = false; + this.database = database; + + GetDefaultValues(context); + GetDefaultAutoGrowthValues(context); + + if (FileType.Data == type) + { + InitializeDataFile(context); + } + else if (FileType.Log == type) + { + InitializeLogFile(context); + } + + this.currentState = this.originalState.Clone(); + } + + /// + /// Get the default folder for log and data files + /// + private void GetDefaultValues(CDataContainer context) + { + Enumerator enumerator = new Enumerator(); + Request request = new Request(); + //object connectionInfo = context.ConnectionInfo; + object connectionInfo = null; + DataTable fileInfo = null; + double size; + + if (null != context.Server) + { + connectionInfo = context.Server.ConnectionContext; + } + else + { + connectionInfo = context.ConnectionInfo; + } + + // get default data file size + request.Urn = "Server/Database[@Name='model']/FileGroup[@Name='PRIMARY']/File"; + request.Fields = new String[1] {"Size"}; + + try + { + fileInfo = enumerator.Process(connectionInfo, request); + size = Convert.ToDouble(fileInfo.Rows[0][0], System.Globalization.CultureInfo.InvariantCulture); + + // file size returned by the enumerator is in kilobytes but the dialog displays + // file size in megabytes + + defaultDataFileSize = DatabaseFilePrototype.RoundUpToNearestMegabyte(size); + } + catch (Exception ex) + { + // user doesn't have access to model so we set the default size + // to be 5 MB + defaultDataFileSize = 5120.0; + } + + // get default log file size + request.Urn = "Server/Database[@Name='model']/LogFile"; + request.Fields = new String[1] {"Size"}; + + try + { + fileInfo = enumerator.Process(connectionInfo, request); + size = Convert.ToDouble(fileInfo.Rows[0][0], System.Globalization.CultureInfo.InvariantCulture); + + defaultLogFileSize = DatabaseFilePrototype.RoundUpToNearestMegabyte(size); + } + catch (Exception ex) + { + // user doesn't have access to model so we set the default size + // to be 1MB + defaultLogFileSize = 1024.0; + } + + // get default data and log folders + request.Urn = "Server/Setting"; + request.Fields = new String[] {"DefaultFile", "DefaultLog"}; + + try + { + fileInfo = enumerator.Process(connectionInfo, request); + defaultDataFolder = fileInfo.Rows[0]["DefaultFile"].ToString(); + defaultLogFolder = fileInfo.Rows[0]["DefaultLog"].ToString(); + + if (defaultDataFolder.Length == 0 || defaultLogFolder.Length == 0) + { + request.Urn = "Server/Information"; + request.Fields = new string[] {"MasterDBPath", "MasterDBLogPath"}; + fileInfo = enumerator.Process(connectionInfo, request); + + if (defaultDataFolder.Length == 0) + { + defaultDataFolder = fileInfo.Rows[0]["MasterDBPath"].ToString(); + } + + if (defaultLogFolder.Length == 0) + { + defaultLogFolder = fileInfo.Rows[0]["MasterDBLogPath"].ToString(); + } + } + + if ((3 <= defaultDataFolder.Length) && (defaultDataFolder[1] == ':') && (defaultDataFolder[2] != '\\')) + { + string drivePart = defaultDataFolder.Substring(0, 2); + string rest = defaultDataFolder.Substring(2); + + defaultDataFolder = String.Format( + System.Globalization.CultureInfo.InvariantCulture, + "{0}\\{1}", + drivePart, + rest); + } + + + if ((3 <= defaultLogFolder.Length) && (defaultLogFolder[1] == ':') && (defaultLogFolder[2] != '\\')) + { + string drivePart = defaultLogFolder.Substring(0, 2); + string rest = defaultLogFolder.Substring(2); + + defaultLogFolder = String.Format( + System.Globalization.CultureInfo.InvariantCulture, + "{0}\\{1}", + drivePart, + rest); + } + } + catch (Exception ex) + { + } + } + + /// + /// Get the default Autogrowth values for log or data files + /// + /// + private void GetDefaultAutoGrowthValues(CDataContainer context) + { + // get autogrowth information + defaultDataAutogrowth = new Autogrowth(this.database); + + // try to get defaults from model database + try + { + // copy default data size and autogrowth from the model database + Database model = context.Server.Databases["model"]; + FileGroup filegroup = model.FileGroups["PRIMARY"]; + DataFile datafile = filegroup.Files[0]; + + defaultDataAutogrowth.IsEnabled = (datafile.GrowthType != FileGrowthType.None); + + if (defaultDataAutogrowth.IsEnabled) + { + defaultDataAutogrowth.MaximumFileSizeInKilobytes = datafile.MaxSize; + defaultDataAutogrowth.IsGrowthRestricted = (0.0 < defaultDataAutogrowth.MaximumFileSizeInKilobytes); + defaultDataAutogrowth.IsGrowthInPercent = datafile.GrowthType == FileGrowthType.Percent; + + if (!defaultDataAutogrowth.IsGrowthRestricted) + { + // we need to set a default maximum size for the file in the + // event the user changes to restricted growth. 0.0 is not + // a valid maximum file size. + + defaultDataAutogrowth.MaximumFileSizeInKilobytes = 102400.0; + } + + if (defaultDataAutogrowth.IsGrowthInPercent) + { + defaultDataAutogrowth.GrowthInPercent = (int) datafile.Growth; + defaultDataAutogrowth.GrowthInMegabytes = 10; + } + else + { + defaultDataAutogrowth.GrowthInKilobytes = + DatabaseFilePrototype.RoundUpToNearestMegabyte(datafile.Growth); + defaultDataAutogrowth.GrowthInPercent = 10; + } + } + } + catch (Exception) + { + // there was an error getting information about the model database, + // so just set the default sizes to default values + defaultDataAutogrowth.Reset(); + } + // get default autogrowth information + defaultLogAutogrowth = new Autogrowth(this.database); + + // try to get defaults from model database + try + { + // copy default data size and autogrowth from the model database + Database model = context.Server.Databases["model"]; + LogFile logfile = model.LogFiles[0]; + + defaultLogAutogrowth.IsEnabled = (logfile.GrowthType != FileGrowthType.None); + + if (defaultLogAutogrowth.IsEnabled) + { + defaultLogAutogrowth.MaximumFileSizeInKilobytes = logfile.MaxSize; + defaultLogAutogrowth.IsGrowthRestricted = (0.0 < defaultLogAutogrowth.MaximumFileSizeInKilobytes); + defaultLogAutogrowth.IsGrowthInPercent = logfile.GrowthType == FileGrowthType.Percent; + + if (!defaultLogAutogrowth.IsGrowthRestricted) + { + // we need to set a default maximum size for the file in the + // event the user changes to restricted growth. 0.0 is not + // a valid maximum file size. + + defaultLogAutogrowth.MaximumFileSizeInKilobytes = 102400.0; + } + + if (defaultLogAutogrowth.IsGrowthInPercent) + { + defaultLogAutogrowth.GrowthInPercent = (int) logfile.Growth; + defaultLogAutogrowth.GrowthInMegabytes = 10; + } + else + { + defaultLogAutogrowth.GrowthInKilobytes = + DatabaseFilePrototype.RoundUpToNearestMegabyte(logfile.Growth); + defaultLogAutogrowth.GrowthInPercent = 10; + } + } + } + catch (Exception) + { + // there was an error getting information about the model database, + // so just set the default sizes to default values + defaultLogAutogrowth.Reset(); + } + } + + /// + /// Initialize a data file, called during construction + /// + /// + private void InitializeDataFile(CDataContainer context) + { + // set our state + this.originalState.folder = this.defaultDataFolder; + this.usingDefaultFolder = true; + this.originalState.autogrowth = defaultDataAutogrowth; + this.originalState.initialSize = this.defaultDataFileSize; + this.originalState.filegroup = this.database.DefaultFilegroup; + + this.database.DefaultFilegroup.OnFileGroupDeletedHandler += + new FileGroupDeletedEventHandler(OnFilegroupDeleted); + } + + /// + /// Initialize a new log file prototype, called during construction + /// + /// + private void InitializeLogFile(CDataContainer context) + { + // set our state + this.originalState.folder = this.defaultLogFolder; + this.usingDefaultFolder = true; + this.originalState.filegroup = null; + this.originalState.autogrowth = defaultLogAutogrowth; + this.originalState.initialSize = this.defaultLogFileSize; + } + + /// + /// Handle deleted events from the filegroup that contains the file + /// + /// + /// + private void OnFilegroupDeleted(object sender, FilegroupDeletedEventArgs e) + { + e.DeletedFilegroup.OnFileGroupDeletedHandler -= new FileGroupDeletedEventHandler(OnFilegroupDeleted); + + // SQL Server deletes all the files in a filegroup when the filegroup is removed + if (this.Exists) + { + this.database.Remove(this); + } + else + { + this.FileGroup = e.DefaultFilegroup; + } + } + + /// + /// Check whether a proposed file name is valid. An exception is thrown if the check fails. + /// + /// The proposed file name to check + private void CheckFileName(string fileName) + { + char[] badFileCharacters = new char[] {'\\', '/', ':', '*', '?', '\"', '<', '>', '|'}; + + bool isAllWhitespace = (fileName.Trim(null).Length == 0); + + if (isAllWhitespace || (0 == fileName.Length) || (-1 != fileName.IndexOfAny(badFileCharacters))) + { + ResourceManager resourceManager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + this.GetType().GetAssembly()); + + string message = String.Empty; + + if (0 == fileName.Length) + { + message = resourceManager.GetString("error.emptyFileName"); + } + else if (isAllWhitespace) + { + message = resourceManager.GetString("error.whitespaceDatabaseName"); + } + else + { + int i = fileName.IndexOfAny(badFileCharacters); + + message = String.Format(System.Globalization.CultureInfo.CurrentCulture, + resourceManager.GetString("error.fileNameContainsIllegalCharacter"), fileName, fileName[i]); + } + + throw new InvalidOperationException(message); + } + } + + /// + /// Get the number of megabytes equivalent to an input number of kilobytes, rounding up. + /// + /// The number of kilobytes to convert + /// The equivalent number of megabytes + internal static int KilobytesToMegabytes(double kilobytes) + { + return (int) Math.Ceiling(kilobytes/kilobytesPerMegabyte); + } + + /// + /// Get the number of kilobytes equivalent to an input number of megabytes. + /// + /// The number of megabytes to convert + /// The equivalent number of kilobytes + internal static double MegabytesToKilobytes(int megabytes) + { + return (((double) megabytes)*kilobytesPerMegabyte); + } + + /// + /// Get the number of kilobytes that is a round number of megabytes + /// larger than the input number of kilobytes. e.g. 1600 kb -> 2048 kb + /// + /// The number of kilobytes to round up + /// The number of kb in the next larger mb + internal static double RoundUpToNearestMegabyte(double kilobytes) + { + double megabytes = Math.Ceiling(kilobytes/kilobytesPerMegabyte); + return (megabytes*kilobytesPerMegabyte); + } + + /// + /// Create the Physical file name for the data file to be created on disk + /// from user preferred logical and physical Name. This also verifies the + /// logical file name provided to the data file. + /// If a valid physical name is provided then that is returned with proper extension. + /// Else logical name is used. + /// + /// MakeDiskFileName returns the actual physical filename that could be used for the T-SQL query + /// It makes sure: + /// 1. provide a way to provide different physical and logical file names to a database file. + /// 2. Its optional if preferred physical name is blank then logical name is used to generate the physical file name. (logical name and + /// physical filename are then same. (except that the physical filename has a proper extension) + /// 3. since logical file name check is not that stringent in t-sql; this check is relaxed only when a physical name is explicitly provided. + /// 4. we don't enable the physical file name input control for filestream files since the filename passed to t-sql in this case is simply ignored. Internally + /// we evaluate the final file name in case of filestreams also in the same manner for consistency (and if in future t-sql starts using the physical filename + /// apart from the path.) + /// 5. Also if the preferred physical filename is without a file extension then default extension is appended. + /// Otherwise the user defined extension is used. + /// + /// Logical name of the data file. Cannot be blank. Its verified. + /// User Preferred Physical name of the file. Can be blank. + /// Preferred suffix of the file. can be String.Empty. If provided physical name doesn't + /// have an extension or is empty then this is used. + /// full filename for the disk. The filename + /// is prefixed with this.Folder path to generate the full name. + /// If logical name is empty, or physical name is invalid. + private string MakeDiskFileName(string logicalName, string preferredPhysicalName, string suffix) + { + ResourceManager resourceManager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + this.GetType().GetAssembly()); + + string filePath = String.Empty; // returned to the caller. + if (String.IsNullOrEmpty(preferredPhysicalName)) + { + // make the file name using the logical name and suffix provided. + this.CheckFileName(logicalName); + filePath = PathWrapper.Combine(this.Folder, logicalName) + suffix; + } + else + { + // do sanity check on name since logical name must exist always. + // The check for logical name is not as stringent as that for physical file name. This is because + // transact sql allows special characters to be present in logical filename but don't allow them + // as physical filename. e.g. '?' can be a valid logical name but not a valid physical name. [anchals] + if (logicalName.Length == 0) + { + string message = String.Empty; + + message = resourceManager.GetString("error.emptyFileName"); + throw new InvalidOperationException(message); + } + + // validate provided physical name and if it does not have an extension + // append the suffix to it. + this.CheckFileName(preferredPhysicalName); + filePath = PathWrapper.Combine(this.Folder, preferredPhysicalName); + } + return filePath; + } + } + + /// + /// Information regarding a name-change event + /// + public class NameChangedEventArgs : EventArgs + { + private string oldName; + private string newName; + + /// + /// The name before the change + /// + public string OldName + { + get { return oldName; } + } + + /// + /// The name after the change + /// + public string NewName + { + get { return newName; } + } + + /// + /// Constructor + /// + /// The name before the change + /// The name after the change + public NameChangedEventArgs(string oldName, string newName) + { + this.oldName = oldName; + this.newName = newName; + } + } + + /// + /// Information regarding a changes to boolean property values + /// + public class BooleanValueChangedEventArgs : EventArgs + { + private bool oldValue; + private bool newValue; + + /// + /// The value before the change + /// + public bool OldValue + { + get { return oldValue; } + } + + /// + /// The value after the change + /// + public bool NewValue + { + get { return newValue; } + } + + /// + /// Constructor + /// + /// The value before the change + /// The value after the change + public BooleanValueChangedEventArgs(bool oldValue, bool newValue) + { + this.oldValue = oldValue; + this.newValue = newValue; + } + } + + /// + /// Information regarding the deletion of a filegroup + /// + public class FilegroupDeletedEventArgs : EventArgs + { + private FilegroupPrototype defaultFilegroup; + private FilegroupPrototype deletedFilegroup; + + /// + /// The default filegroup for the database + /// + public FilegroupPrototype DefaultFilegroup + { + get { return defaultFilegroup; } + } + + /// + /// The filegroup that was deleted + /// + public FilegroupPrototype DeletedFilegroup + { + get { return deletedFilegroup; } + } + + /// + /// Constructor + /// + /// The filegroup that was deleted + /// The default filegroup for the database + public FilegroupDeletedEventArgs(FilegroupPrototype deletedFilegroup, FilegroupPrototype defaultFilegroup) + { + this.deletedFilegroup = deletedFilegroup; + this.defaultFilegroup = defaultFilegroup; + } + } + + public delegate void FileGroupNameChangedEventHandler(object sender, NameChangedEventArgs e); + + public delegate void FileGroupDeletedEventHandler(object sender, FilegroupDeletedEventArgs e); + + public delegate void FileGroupDefaultChangedEventHandler(object sender, BooleanValueChangedEventArgs e); + + internal class DatabaseAlreadyExistsException : Exception + { + private static string format; + + static DatabaseAlreadyExistsException() + { + ResourceManager resourceManager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabaseAlreadyExistsException).GetAssembly()); + format = resourceManager.GetString("error.databaseAlreadyExists"); + } + + public DatabaseAlreadyExistsException(string databaseName) + : base(String.Format(System.Globalization.CultureInfo.CurrentCulture, format, databaseName)) + { + } + } + + internal class DefaultCursorTypes : StringConverter + { + /// + /// This method returns a list of default cursor Types + /// which will be populated as a drop down list. + /// + /// + /// List of DefaultCursor Types + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabasePrototype).GetAssembly()); + List standardValues = null; + TypeConverter.StandardValuesCollection result = null; + + if ( + string.Compare(context.PropertyDescriptor.Name, "DefaultCursorDisplay", + StringComparison.OrdinalIgnoreCase) == 0) + { + standardValues = new List(); + standardValues.Add(manager.GetString("prototype.db.prop.defaultCursor.value.local")); + standardValues.Add(manager.GetString("prototype.db.prop.defaultCursor.value.global")); + } + if (standardValues != null) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } + + internal class ParameterizationTypes : StringConverter + { + /// + /// This method returns a list of parameterization Types + /// which will be populated as a drop down list. + /// + /// + /// List of Parameterization Types + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabasePrototype90).GetAssembly()); + List standardValues = new List(); + TypeConverter.StandardValuesCollection result = null; + + if ( + string.Compare(context.PropertyDescriptor.Name, "Parameterization", StringComparison.OrdinalIgnoreCase) == + 0) + { + standardValues.Add(manager.GetString("prototype.db.prop.parameterization.value.forced")); + standardValues.Add(manager.GetString("prototype.db.prop.parameterization.value.simple")); + } + if (standardValues.Count > 0) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } + + internal class PageVerifyTypes80 : StringConverter + { + /// + /// This method returns a list of pageverify Types + /// which will be populated as a drop down list. + /// + /// + /// List of Page Verify Types + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabasePrototype80).GetAssembly()); + List standardValues = new List(); + TypeConverter.StandardValuesCollection result = null; + + if ( + string.Compare(context.PropertyDescriptor.Name, "PageVerifyDisplay", StringComparison.OrdinalIgnoreCase) == + 0) + { + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.tornPageDetection")); + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.none")); + } + + if (standardValues.Count > 0) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } + + + internal class PageVerifyTypes90 : StringConverter + { + /// + /// This method returns a list of pageverify Types + /// which will be populated as a drop down list. + /// + /// + /// List of Page Verify Types + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabasePrototype80).GetAssembly()); + List standardValues = new List(); + TypeConverter.StandardValuesCollection result = null; + + if ( + string.Compare(context.PropertyDescriptor.Name, "PageVerifyDisplay", StringComparison.OrdinalIgnoreCase) == + 0) + { + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.checksum")); + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.tornPageDetection")); + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.none")); + } + + if (standardValues.Count > 0) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } + + internal class RestrictAccessTypes : StringConverter + { + /// + /// This method returns a list of Access Types + /// which will be populated as a drop down list. + /// + /// + /// List of Restrict Access Types + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabasePrototype).GetAssembly()); + List standardValues = new List(); + TypeConverter.StandardValuesCollection result = null; + + if (string.Compare(context.PropertyDescriptor.Name, "RestrictAccess", StringComparison.OrdinalIgnoreCase) == + 0) + { + standardValues.Add(manager.GetString("prototype.db.prop.restrictAccess.value.multiple")); + standardValues.Add(manager.GetString("prototype.db.prop.restrictAccess.value.single")); + standardValues.Add(manager.GetString("prototype.db.prop.restrictAccess.value.restricted")); + } + if (standardValues.Count > 0) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } + + internal class DatabaseStatusTypes : StringConverter + { + /// + /// This method returns a list of database status Types + /// which will be populated as a drop down list. + /// + /// + /// List of Database Status Types + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + ResourceManager manager = + new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + typeof (DatabasePrototype).GetAssembly()); + List standardValues = new List(); + TypeConverter.StandardValuesCollection result = null; + + if ( + string.Compare(context.PropertyDescriptor.Name, "DatabaseStatusDisplay", + StringComparison.OrdinalIgnoreCase) == 0) + { + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.normal")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.restoring")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.recoveryPending")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.recovering")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.suspect")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.offline")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.inaccessible")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.standby")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.shutdown")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.emergency")); + standardValues.Add(manager.GetString("prototype.db.prop.databaseState.value.autoClosed")); + } + if (standardValues.Count > 0) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } + + ///// + ///// Helper class to provide standard values for populating drop down boxes on + ///// properties displayed in the Properties Grid + ///// + //internal class DynamicValuesConverter : StringConverter + //{ + // /// + // /// This method returns a list of dynamic values + // /// for various Properties in this class. + // /// + // /// + // /// List of Database Status Types + // public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + // { + // var standardValues = new List(); + // StandardValuesCollection result = null; + + // //Handle ServiceLevelObjective values + // if (context.PropertyDescriptor != null && + // string.Compare(context.PropertyDescriptor.Name, "CurrentServiceLevelObjective", + // StringComparison.OrdinalIgnoreCase) == 0) + // { + // var designableObject = context.Instance as DesignableObject; + // if (designableObject != null) + // { + // var prototype = designableObject.ObjectDesigned as DatabasePrototypeAzure; + // if (prototype != null) + // { + // KeyValuePair pair; + // if (AzureSqlDbHelper.TryGetServiceObjectiveInfo(prototype.AzureEdition, out pair)) + // { + // standardValues.AddRange(pair.Value); + // } + // } + // } + // } + // //Handle AzureEditionDisplay values + // else if (context.PropertyDescriptor != null && + // string.Compare(context.PropertyDescriptor.Name, "AzureEditionDisplay", + // StringComparison.OrdinalIgnoreCase) == 0) + // { + // var designableObject = context.Instance as DesignableObject; + + // if (designableObject != null) + // { + // var prototype = designableObject.ObjectDesigned as DatabasePrototype; + // if (prototype != null) + // { + // foreach ( + // AzureEdition edition in + // AzureSqlDbHelper.GetValidAzureEditionOptions(prototype.ServerVersion)) + // { + // // We don't yet support creating DW with the UI + // if (prototype.Exists || edition != AzureEdition.DataWarehouse) + // { + // standardValues.Add(AzureSqlDbHelper.GetAzureEditionDisplayName(edition)); + // } + // } + // } + // else + // { + // STrace.Assert(false, + // "DesignableObject ObjectDesigned isn't a DatabasePrototype for AzureEditionDisplay StandardValues"); + // } + // } + // else + // { + // STrace.Assert(designableObject != null, + // "Context instance isn't a DesignableObject for AzureEditionDisplay StandardValues"); + // } + // } + // //Handle MaxSize values + // else if (context.PropertyDescriptor != null && + // string.Compare(context.PropertyDescriptor.Name, "MaxSize", StringComparison.OrdinalIgnoreCase) == 0) + // { + // var designableObject = context.Instance as DesignableObject; + // if (designableObject != null) + // { + + // var prototype = designableObject.ObjectDesigned as DatabasePrototypeAzure; + // if (prototype != null) + // { + // KeyValuePair pair; + // if (AzureSqlDbHelper.TryGetDatabaseSizeInfo(prototype.AzureEdition, out pair)) + // { + // standardValues.AddRange(pair.Value); + // } + // } + // } + + // } + + // if (standardValues.Count > 0) + // { + // result = new StandardValuesCollection(standardValues); + // } + + // return result; + // } + + // public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + // { + // //Tells the grid that we'll support the values to display in a drop down + // return true; + // } + + // public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + // { + // //The values are exclusive (populated in a drop-down list instead of combo box) + // return true; + // } + //} + + ///// + ///// Helper class to provide standard values for populating drop down boxes on + ///// database scoped configuration properties displayed in the Properties Grid + ///// + //internal class DatabaseScopedConfigurationOnOffTypes : StringConverter + //{ + // /// + // /// This method returns a list of database scoped configuration on off values + // /// which will be populated as a drop down list. + // /// + // /// + // /// Database scoped configurations which will populate the drop down list. + // public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + // { + // ResourceManager manager = + // new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", + // typeof (DatabasePrototype).GetAssembly()); + // List standardValues = new List(); + // TypeConverter.StandardValuesCollection result = null; + + // if ( + // string.Compare(context.PropertyDescriptor.Name, "LegacyCardinalityEstimationDisplay", + // StringComparison.OrdinalIgnoreCase) == 0 || + // string.Compare(context.PropertyDescriptor.Name, "ParameterSniffingDisplay", + // StringComparison.OrdinalIgnoreCase) == 0 || + // string.Compare(context.PropertyDescriptor.Name, "QueryOptimizerHotfixesDisplay", + // StringComparison.OrdinalIgnoreCase) == 0) + // { + // standardValues.Add(manager.GetString("prototype.db.prop.databasescopedconfig.value.off")); + // standardValues.Add(manager.GetString("prototype.db.prop.databasescopedconfig.value.on")); + // } + // else + // { + // standardValues.Add(manager.GetString("prototype.db.prop.databasescopedconfig.value.off")); + // standardValues.Add(manager.GetString("prototype.db.prop.databasescopedconfig.value.on")); + // standardValues.Add(manager.GetString("prototype.db.prop.databasescopedconfig.value.primary")); + // } + + // if (standardValues.Count > 0) + // { + // result = new TypeConverter.StandardValuesCollection(standardValues); + // } + + // return result; + // } + + // public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + // { + // //Tells the grid that we'll support the values to display in a drop down + // return true; + // } + + // public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + // { + // //The values are exclusive (populated in a drop-down list instead of combo box) + // return true; + // } + //} +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DataContainer.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DataContainer.cs new file mode 100644 index 00000000..721af3f9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DataContainer.cs @@ -0,0 +1,2203 @@ +// +// 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; +using System.Collections.Generic; +using System.ComponentModel; +//using System.Data; +// using System.Drawing; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Security; +using System.Xml; +// using Microsoft.AnalysisServices; +// using Microsoft.SqlServer.Common; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Diagnostics; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Smo; +// using Microsoft.SqlServer.Management.UI.VSIntegration.ObjectExplorer; +using Assembly = System.Reflection.Assembly; +using System.Xml.Linq; +using Microsoft.Data.Tools.DataSets; +//This is used only for non-express sku + + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// CDataContainer + /// + public class CDataContainer : IDisposable + { + #region Nested types + + public enum ServerType + { + SQL, + OLAP, //This type is used only for non-express sku + SQLCE, + UNKNOWN + } + + #endregion + + #region Fields + + private ServerConnection serverConnection; + private Server m_server = null; + + //This member is used for non-express sku only + // private AnalysisServices.Server m_amoServer = null; + private ISandboxLoader sandboxLoader; + + //protected XDocument m_doc = null; + protected XmlDocument m_doc = null; + private XmlDocument originalDocument = null; + private SqlOlapConnectionInfoBase connectionInfo = null; + private SqlConnectionInfoWithConnection sqlCiWithConnection; + private bool ownConnection = true; + private IManagedConnection managedConnection; + protected string serverName; + + //This member is used for non-express sku only + protected string olapServerName; + + protected string sqlceFilename; + + private ServerType serverType = ServerType.UNKNOWN; + + private Hashtable m_hashTable = null; + + private string objectNameKey = "object-name-9524b5c1-e996-4119-a433-b5b947985566"; + private string objectSchemaKey = "object-schema-ccaf2efe-8fa3-4f62-be79-62ef3cbe7390"; + + private SqlSmoObject sqlDialogSubject = null; + + private int sqlServerVersion = 0; + private int sqlServerEffectiveVersion = 0; + + + #endregion + + #region Public properties + + /// + /// gets/sets XmlDocument with parameters + /// + public XmlDocument Document + { + get + { + return this.m_doc; + } + set + { + this.m_doc = value; + + if (value != null) + { + //this.originalDocument = (XmlDocument) value.Clone(); + this.originalDocument = value; + } + else + { + this.originalDocument = null; + } + } + } + + + /// + /// returns the Hashtable that can be used to store generic information + /// + public Hashtable HashTable + { + get + { + if (m_hashTable == null) + { + m_hashTable = new Hashtable(); + } + + return m_hashTable; + } + } + + + /// + /// gets/sets SMO server object + /// + public Server Server + { + get + { + return m_server; + } + set + { + m_server = value; + } + } + + /// + /// gets/sets AMO server object + /// This member is used for non-express sku only + /// + //public AnalysisServices.Server OlapServer + //{ + // get + // { + // return m_amoServer; + // } + // set + // { + // m_amoServer = value; + // } + //} + + public ISandboxLoader SandboxLoader + { + get { return this.sandboxLoader; } + } + + /// + /// connection info that should be used by the dialogs + /// + public SqlOlapConnectionInfoBase ConnectionInfo + { + get + { + //// update the database name in the serverconnection object to set the correct database context when connected to Azure + //var conn = this.connectionInfo as SqlConnectionInfoWithConnection; + + //if (conn != null && conn.ServerConnection.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) + //{ + // if (this.RelevantDatabaseName != null) + // { + // IComparer dbNamesComparer = ServerConnection.ConnectionFactory.GetInstance(conn.ServerConnection).ServerComparer as IComparer; + // if (dbNamesComparer.Compare(this.RelevantDatabaseName, conn.DatabaseName) != 0) + // { + // ServerConnection serverConnection = conn.ServerConnection.GetDatabaseConnection(this.RelevantDatabaseName, true, conn.AccessToken); + // ((SqlConnectionInfoWithConnection)this.connectionInfo).ServerConnection = serverConnection; + // } + // } + //} + + return this.connectionInfo; + } + } + + /// + /// returns SMO server connection object constructed off the connectionInfo. + /// This method cannot work until ConnectionInfo property has been set + /// + public ServerConnection ServerConnection + { + get + { + if (this.serverConnection == null) + { + if (this.serverType != ServerType.SQL) + { + throw new InvalidOperationException(); + } + + if (this.connectionInfo == null) + { + throw new InvalidOperationException(); + } + + + if (this.sqlCiWithConnection != null) + { + this.serverConnection = this.sqlCiWithConnection.ServerConnection; + } + else + { + SqlConnectionInfo sci = this.connectionInfo as SqlConnectionInfo; + this.serverConnection = new ServerConnection(sci); + } + } + + + return this.serverConnection; + } + } + + /// + /// returns SMO server connection object constructed off the connectionInfo. + /// This method cannot work until ConnectionInfo property has been set + /// + public SqlConnectionInfoWithConnection SqlInfoWithConnection + { + get + { + if (this.serverConnection == null) + { + if (this.serverType != ServerType.SQL) + { + throw new InvalidOperationException(); + } + + if (this.connectionInfo == null) + { + throw new InvalidOperationException(); + } + + + if (this.sqlCiWithConnection != null) + { + this.serverConnection = this.sqlCiWithConnection.ServerConnection; + } + else + { + SqlConnectionInfo sci = this.connectionInfo as SqlConnectionInfo; + this.serverConnection = new ServerConnection(sci); + } + } + + return this.sqlCiWithConnection; + } + } + + public string ServerName + { + get + { + return this.serverName; + } + set + { + this.serverName = value; + } + } + + public ServerType ContainerServerType + { + get + { + return this.serverType; + } + set + { + this.serverType = value; + } + } + + public string SqlCeFileName + { + get + { + return this.sqlceFilename; + } + set + { + this.sqlceFilename = value; + } + } + + //This member is used for non-express sku only + public string OlapServerName + { + get + { + return this.olapServerName; + } + set + { + this.olapServerName = value; + } + } + + /// + /// Whether we are creating a new object + /// + public bool IsNewObject + { + get + { + string itemType = this.GetDocumentPropertyString("itemtype"); + return (itemType.Length != 0); + } + } + + /// + /// The URN to the parent of the object we are creating/modifying + /// + public string ParentUrn + { + get + { + string result = String.Empty; + string documentUrn = this.GetDocumentPropertyString("urn"); + + if (this.IsNewObject) + { + result = documentUrn; + } + else + { + Urn urn = new Urn(documentUrn); + result = urn.Parent.ToString(); + } + + return result; + } + } + + /// + /// The URN to the object we are creating/modifying + /// + public string ObjectUrn + { + get + { + string result = String.Empty; + + if (this.IsNewObject) + { + string objectName = this.ObjectName; + string objectSchema = this.ObjectSchema; + + if (0 == objectName.Length) + { + throw new InvalidOperationException("object name is not known, so URN for object can't be formed"); + } + + if (0 == objectSchema.Length) + { + result = String.Format(CultureInfo.InvariantCulture, + "{0}/{1}[@Name='{2}']", + this.ParentUrn, + this.ObjectType, + Urn.EscapeString(objectName)); + } + else + { + result = String.Format(CultureInfo.InvariantCulture, + "{0}/{1}[@Schema='{2}' and @Name='{3}']", + this.ParentUrn, + this.ObjectType, + Urn.EscapeString(objectSchema), + Urn.EscapeString(objectName)); + } + } + else + { + result = this.GetDocumentPropertyString("urn"); + } + + return result; + } + } + + /// + /// The name of the object we are modifying + /// + public string ObjectName + { + get + { + return this.GetDocumentPropertyString(objectNameKey); + } + + set + { + this.SetDocumentPropertyValue(objectNameKey, value); + } + } + + /// + /// The schema of the object we are modifying + /// + public string ObjectSchema + { + get + { + return this.GetDocumentPropertyString(objectSchemaKey); + } + + set + { + this.SetDocumentPropertyValue(objectSchemaKey, value); + } + } + + /// + /// The type of the object we are creating (as it appears in URNs) + /// + public string ObjectType + { + get + { + // note that the itemtype property is only set for new objects + + string result = String.Empty; + string itemtype = this.GetDocumentPropertyString("itemtype"); + + // if this is not a new object + if (0 == itemtype.Length) + { + string documentUrn = this.GetDocumentPropertyString("urn"); + Urn urn = new Urn(documentUrn); + + result = urn.Type; + } + else + { + result = itemtype; + } + + return result; + } + + + } + + /// + /// The SQL SMO object that is the subject of the dialog. + /// + public SqlSmoObject SqlDialogSubject + { + get + { + SqlSmoObject result = null; + + if (this.sqlDialogSubject != null) + { + result = this.sqlDialogSubject; + } + else + { + result = this.Server.GetSmoObject(this.ObjectUrn); + } + + return result; + } + + set + { + this.sqlDialogSubject = value; + } + } + + /// + /// Whether the logged in user is a system administrator + /// + public bool LoggedInUserIsSysadmin + { + get + { + bool result = false; + + if (this.Server != null && this.Server.ConnectionContext != null) + { + result = this.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin); + } + + return result; + } + } + + /// + /// Get the name of the Database that contains (or is) the subject of the dialog. + /// If no there is no relevant database, then an empty string is returned. + /// + public string RelevantDatabaseName + { + get + { + string result = String.Empty; + string urnText = this.GetDocumentPropertyString("urn"); + + if (urnText.Length != 0) + { + Urn urn = new Urn(urnText); + + while ((urn != null) && (urn.Type != "Database")) + { + urn = urn.Parent; + } + + if ((urn != null) && (urn.Type == "Database")) + { + result = urn.GetAttribute("Name"); + } + } + + return result; + } + } + + /// + /// The server major version number + /// + public int SqlServerVersion + { + get + { + if (this.sqlServerVersion == 0) + { + this.sqlServerVersion = 9; + + if ((this.ConnectionInfo != null) && (ServerType.SQL == this.ContainerServerType)) + { + Enumerator enumerator = new Enumerator(); + Urn urn = "Server/Information"; + string[] fields = new string[] { "VersionMajor" }; + DataTable dataTable = enumerator.Process(this.ConnectionInfo, new Request(urn, fields)); + + if (dataTable.Rows.Count != 0) + { + this.sqlServerVersion = (int)dataTable.Rows[0][0]; + } + } + } + + return this.sqlServerVersion; + } + + } + + /// + /// The server version the database is emulating. If database compatibility level is + /// not relevant to the subject, then this just returns the actual server version. + /// + public int EffectiveSqlServerVersion + { + get + { + if (this.sqlServerEffectiveVersion == 0) + { + this.sqlServerEffectiveVersion = 9; + + if ((this.ConnectionInfo != null) && (ServerType.SQL == this.ContainerServerType)) + { + string databaseName = this.RelevantDatabaseName; + + if (databaseName.Length != 0) + { + Enumerator enumerator = new Enumerator(); + Urn urn = String.Format("Server/Database[@Name='{0}']", Urn.EscapeString(databaseName)); + string[] fields = new string[] { "CompatibilityLevel" }; + DataTable dataTable = enumerator.Process(this.ConnectionInfo, new Request(urn, fields)); + + if (dataTable.Rows.Count != 0) + { + + CompatibilityLevel level = (CompatibilityLevel)dataTable.Rows[0][0]; + + switch (level) + { + case CompatibilityLevel.Version60: + case CompatibilityLevel.Version65: + + this.sqlServerEffectiveVersion = 6; + break; + + case CompatibilityLevel.Version70: + + this.sqlServerEffectiveVersion = 7; + break; + + case CompatibilityLevel.Version80: + + this.sqlServerEffectiveVersion = 8; + break; + + case CompatibilityLevel.Version90: + + this.sqlServerEffectiveVersion = 9; + break; + case CompatibilityLevel.Version100: + + this.sqlServerEffectiveVersion = 10; + break; + case CompatibilityLevel.Version110: + + this.sqlServerEffectiveVersion = 11; + break; + case CompatibilityLevel.Version120: + + this.sqlServerEffectiveVersion = 12; + break; + + case CompatibilityLevel.Version130: + this.sqlServerEffectiveVersion = 13; + break; + + case CompatibilityLevel.Version140: + this.sqlServerEffectiveVersion = 14; + break; + + default: + + this.sqlServerEffectiveVersion = 14; + break; + } + } + else + { + this.sqlServerEffectiveVersion = this.SqlServerVersion; + } + } + else + { + this.sqlServerEffectiveVersion = this.SqlServerVersion; + } + } + } + + return this.sqlServerEffectiveVersion; + } + } + + #endregion + + #region Constructors, finalizer + + public CDataContainer() + { + } + + /// + /// contructs the object and initializes its SQL ConnectionInfo and ServerConnection properties + /// using the specified connection info containing live connection. + /// + /// connection info containing live connection + public CDataContainer(object ciObj, bool ownConnection) + { + SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj; + if (ci == null) + { + throw new ArgumentNullException("ci"); + } + ApplyConnectionInfo(ci, ownConnection); + } + + /// + /// contructs the object and initializes its SQL ConnectionInfo and ServerConnection properties + /// using the specified connection info containing live connection. + /// + /// in addition creates a server of the given server type + /// + /// connection info containing live connection + public CDataContainer(ServerType serverType, object ciObj, bool ownConnection) + { + SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj; + if (ci == null) + { + throw new ArgumentNullException("ci"); + } + + this.serverType = serverType; + ApplyConnectionInfo(ci, ownConnection); + + if (serverType == ServerType.SQL) + { + //NOTE: ServerConnection property will constuct the object if needed + m_server = new Server(ServerConnection); + } + else + { + throw new ArgumentException("SRError.UnknownServerType(serverType.ToString()), serverType"); + } + } + + /// + /// Constructor + /// + /// Server type + /// Server name + /// true if connection is trused. If true user name and password are ignored + /// User name for not trusted connections + /// Password for not trusted connections + /// XML string with parameters + public CDataContainer(ServerType serverType, string serverName, bool trusted, string userName, SecureString password, string xmlParameters) + { + this.serverType = serverType; + this.serverName = serverName; + + if (serverType == ServerType.SQL) + { + //does some extra initialization + ApplyConnectionInfo(GetTempSqlConnectionInfoWithConnection(serverName, trusted, userName, password), true); + + //NOTE: ServerConnection property will constuct the object if needed + m_server = new Server(ServerConnection); + } + else + { + throw new ArgumentException("SRError.UnknownServerType(serverType.ToString()), serverType"); + } + + if (xmlParameters != null) + { + this.Document = GenerateXmlDocumentFromString(xmlParameters); + } + + if (ServerType.SQL == serverType) + { + this.InitializeObjectNameAndSchema(); + } + } + + /// + /// + /// + /// Data container + /// XML string with parameters + public CDataContainer(CDataContainer dataContainer, string xmlParameters) + { + Server = dataContainer.Server; + this.serverName = dataContainer.serverName; + this.serverType = dataContainer.serverType; + this.connectionInfo = dataContainer.connectionInfo; + this.ownConnection = dataContainer.ownConnection; + + this.sqlCiWithConnection = dataContainer.connectionInfo as SqlConnectionInfoWithConnection; + if (this.sqlCiWithConnection != null) + { + //we want to be notified if it is closed + this.sqlCiWithConnection.ConnectionClosed += new EventHandler(OnSqlConnectionClosed); + } + + if (xmlParameters != null) + { + XmlDocument doc = GenerateXmlDocumentFromString(xmlParameters); + this.Init(doc); + } + } + + ~CDataContainer() + { + Dispose(false); + } + + #endregion + + #region Public virtual methods + + /// + /// Initialization routine that is a convience fuction for clients with the data in a string + /// + /// The string that contains the xml data + public virtual void Init(string xmlText) + { + XmlDocument xmlDoc = GenerateXmlDocumentFromString(xmlText); + + this.Init(xmlDoc); + } + + /// + /// Overload of basic Init which takes a IServiceProvider and initializes + /// what it can of the container with elements provided by IServcieProvider + /// + /// Today this is only the IManagedProvider if available but this function + /// could be modified to init other things provided by the ServiceProvider + /// + /// + /// + public virtual void Init(XmlDocument doc, IServiceProvider site) + { + + if (site != null) + { + //see if service provider supports INodeInformation interface from the object explorer + try + { + //NOTE: we're trying to forcefully set connection information on the data container. + //If this code doesn't execute, then dc.Init call below will result in CDataContainer + //initializing its ConnectionInfo member with a new object contructed off the parameters + //in the XML doc [server name, user name etc] + IManagedConnection managedConnection = site.GetService(typeof(IManagedConnection)) as IManagedConnection; + if (managedConnection != null) + { + this.SetManagedConnection(managedConnection); + } + } + catch (Exception ex) + { + // keep the exception flowing + throw ex; + } + } + + this.Document = doc; + LoadData(); + + // finish the initialization + this.Init(doc); + } + + + /// + /// main initialization method - the object is unusable until this method is called + /// NOTE: it will ensure the ConnectionInfo and ServerConnetion objects are constructed + /// for the appropriate server types + /// + /// + public virtual void Init(XmlDocument doc) + { + //First, we read the data from XML by calling LoadData + this.Document = doc; + + LoadData(); + + //Second, create the rignt server and connection objects + if (this.serverType == ServerType.SQL) + { + //ensure that we have a valid ConnectionInfo + if (ConnectionInfo == null) + { + throw new InvalidOperationException(); + } + + if (m_server == null) + { + //NOTE: ServerConnection property will constuct the object if needed + m_server = new Server(ServerConnection); + } + } + else if (this.serverType == ServerType.SQLCE) + { + // do nothing; originally we were only distinguishing between two + // types of servers (OLAP/SQL); as a result for SQLCE we were + // executing the same codepath as for OLAP server which was + // resulting in an exception; + } + } + + /// + /// loads data into internal members from the XML document and detects the server type + /// [SQL, OLAP etc] based on the info in the XML doc + /// + public virtual void LoadData() + { + STParameters param; + bool bStatus; + + param = new STParameters(); + + param.SetDocument(m_doc); + + // DEVNOTE: chrisze 02/25/03 + // This is an ugly way to distinguish between different server types + // Maybe we should pass server type as one of the parameters? + // + bStatus = param.GetParam("servername", ref this.serverName); + + if (!bStatus || this.serverName.Length == 0) + { + + { + bStatus = param.GetParam("database", ref this.sqlceFilename); + + if (bStatus && !String.IsNullOrEmpty(this.sqlceFilename)) + { + this.serverType = ServerType.SQLCE; + } + else if (this.sqlCiWithConnection != null) + { + this.serverType = ServerType.SQL; + } + else + { + this.serverType = ServerType.UNKNOWN; + } + } + } + else + { + //OK, let's see if was specified in the parameters. It it was, use + //it to double check that it is SQL + string specifiedServerType = ""; + bStatus = param.GetParam("servertype", ref specifiedServerType); + if (bStatus) + { + if (specifiedServerType != null && "sql" != specifiedServerType.ToLowerInvariant()) + { + this.serverType = ServerType.UNKNOWN;//we know only about 3 types, and 2 of them were excluded by if branch above + } + else + { + this.serverType = ServerType.SQL; + } + } + else + { + this.serverType = ServerType.SQL; + } + } + + // Ensure there is no password in the XML document + string temp = String.Empty; + if (param.GetParam("password", ref temp)) + { + temp = null; + throw new SecurityException(); + } + + if (ServerType.SQL == this.serverType) + { + this.InitializeObjectNameAndSchema(); + } + } + + #endregion + + #region Public methods + + /// + /// we need to store it as context from the OE + /// + /// + internal void SetManagedConnection(IManagedConnection managedConnection) + { + this.managedConnection = managedConnection; + + ApplyConnectionInfo(managedConnection.Connection, true);//it will do some extra initialization + } + + /// + /// Get the named property value from the XML document + /// + /// The name of the property to get + /// The property value + public object GetDocumentPropertyValue(string propertyName) + { + object result = null; + STParameters param = new STParameters(this.Document); + + param.GetBaseParam(propertyName, ref result); + + return result; + } + + /// + /// Get the named property value from the XML document + /// + /// The name of the property to get + /// The property value + public string GetDocumentPropertyString(string propertyName) + { + object result = GetDocumentPropertyValue(propertyName); + if (result == null) + { + result = String.Empty; + } + + return (string)result; + } + + /// + /// Set the named property value in the XML document + /// + /// The name of the property to set + /// The property value + public void SetDocumentPropertyValue(string propertyName, string propertyValue) + { + STParameters param = new STParameters(this.Document); + + param.SetParam(propertyName, propertyValue); + } + + #endregion + + #region internal methods + + /// + /// Reset the data container to its state from just after it was last initialized or reset + /// + internal void Reset() + { + if (this.originalDocument != null) + { + this.Init(this.originalDocument); + } + + if (this.m_hashTable != null) + { + this.m_hashTable = new Hashtable(); + } + } + + + #endregion + + #region Private helpers + + /// + /// Get the font size specified in the STParameters structure + /// + /// The structure from which to extract font data + //private float GetFontSize(STParameters param) + //{ + // float result = 8.0f; + // string fontSize = String.Empty; + + // if (param.GetParam("fontsize", ref fontSize) && (0 != fontSize.Length)) + // { + // result = Convert.ToSingle(fontSize, CultureInfo.InvariantCulture); + // } + + // return result; + //} + + /// + /// Get the font style specified in the STParameters structure + /// + /// The structure from which to extract font data + //private FontStyle GetFontStyle(STParameters param) + //{ + // FontStyle style = FontStyle.Regular; + // string fontStyle = String.Empty; + + // if (param.GetParam("fontstyle", ref fontStyle) && (0 != fontStyle.Length)) + // { + // bool styleIsInitialized = false; + // string fontStyleUpper = fontStyle.ToUpperInvariant(); + + // if (-1 != fontStyleUpper.IndexOf("BOLD",StringComparison.Ordinal)) + // { + // style = FontStyle.Bold; + // styleIsInitialized = true; + // } + + // if (-1 != fontStyleUpper.IndexOf("ITALIC",StringComparison.Ordinal)) + // { + // if (styleIsInitialized) + // { + // style |= FontStyle.Italic; + // } + // else + // { + // style = FontStyle.Italic; + // styleIsInitialized = true; + // } + // } + + // if (-1 != fontStyleUpper.IndexOf("REGULAR",StringComparison.Ordinal)) + // { + // if (styleIsInitialized) + // { + // style |= FontStyle.Regular; + // } + // else + // { + // style = FontStyle.Regular; + // styleIsInitialized = true; + // } + // } + + // if (-1 != fontStyleUpper.IndexOf("STRIKEOUT",StringComparison.Ordinal)) + // { + // if (styleIsInitialized) + // { + // style |= FontStyle.Strikeout; + // } + // else + // { + // style = FontStyle.Strikeout; + // styleIsInitialized = true; + // } + // } + + // if (-1 != fontStyleUpper.IndexOf("UNDERLINE",StringComparison.Ordinal)) + // { + // if (styleIsInitialized) + // { + // style |= FontStyle.Underline; + // } + // else + // { + // style = FontStyle.Underline; + // styleIsInitialized = true; + // } + // } + // } + + // return style; + //} + + /// + /// Get the name and schema (if applicable) for the object we are referring to + /// + private void InitializeObjectNameAndSchema() + { + string documentUrn = this.GetDocumentPropertyString("urn"); + + if (documentUrn.Length != 0) + { + Urn urn = new Urn(documentUrn); + string name = urn.GetAttribute("Name"); + string schema = urn.GetAttribute("Schema"); + + if ((name != null) && (name.Length != 0)) + { + this.ObjectName = name; + } + + if ((schema != null) && (schema.Length != 0)) + { + this.ObjectSchema = schema; + } + } + } + + /// + /// returns SqlConnectionInfoWithConnection object constructed using our internal vars + /// + /// + private SqlConnectionInfoWithConnection GetTempSqlConnectionInfoWithConnection( + string serverName, + bool trusted, + string userName, + SecureString password) + { + SqlConnectionInfoWithConnection tempCI = new SqlConnectionInfoWithConnection(serverName); + tempCI.SingleConnection = false; + tempCI.Pooled = false; + //BUGBUG - set the right application name? + if (trusted) + { + tempCI.UseIntegratedSecurity = true; + } + else + { + tempCI.UseIntegratedSecurity = false; + tempCI.UserName = userName; + tempCI.SecurePassword = password; + } + + return tempCI; + } + + + /// + /// our handler of sqlCiWithConnection.ConnectionClosed + /// + /// + /// + private void OnSqlConnectionClosed(object sender, EventArgs e) + { + //nothing - per MRaheem we'll let user deal with this situation + } + + /// + /// stores specified connection info and performs some extra initialization steps + /// that can only be done after we have the connection information + /// + /// + private void ApplyConnectionInfo(SqlOlapConnectionInfoBase ci, bool ownConnection) + { + + this.connectionInfo = ci; + this.ownConnection = ownConnection; + + //cache the cast value. It is OK that it is null for non SQL types + this.sqlCiWithConnection = ci as SqlConnectionInfoWithConnection; + + if (this.sqlCiWithConnection != null) + { + //we want to be notified if it is closed + this.sqlCiWithConnection.ConnectionClosed += new EventHandler(OnSqlConnectionClosed); + } + } + + ///// + ///// returns string that should be specified to AMO Server.Connect method + ///// This member is used for non-express sku only + ///// + ///// + //internal string OlapConnectionString + //{ + // get + // { + // OlapConnectionInfo olapCi = this.ConnectionInfo as OlapConnectionInfo; + // if (olapCi != null) + // { + // return olapCi.ConnectionString; + // } + // else + // { + // STrace.Assert(this.olapServerName != null); + // return string.Format(CultureInfo.InvariantCulture, "Data Source={0}", this.olapServerName); + // } + + // } + //} + + private static bool MustRethrow(Exception exception) + { + bool result = false; + + switch (exception.GetType().Name) + { + case "ExecutionEngineException": + case "OutOfMemoryException": + case "AccessViolationException": + case "BadImageFormatException": + case "InvalidProgramException": + + result = true; + break; + } + + return result; + } + + /// + /// Generates an XmlDocument from a string, avoiding exploits available through + /// DTDs + /// + /// + /// + private XmlDocument GenerateXmlDocumentFromString(string sourceXml) + { + if (sourceXml == null) + { + throw new ArgumentNullException("sourceXml"); + } + if (sourceXml.Length == 0) + { + throw new ArgumentException("sourceXml"); + } + + MemoryStream memoryStream = new MemoryStream(); + StreamWriter streamWriter = new StreamWriter(memoryStream); + + // Writes the xml to the memory stream + streamWriter.Write(sourceXml); + streamWriter.Flush(); + + // Resets the stream to the beginning + memoryStream.Seek(0, SeekOrigin.Begin); + + // Creates the XML reader from the stream + // and moves it to the correct node + XmlReader xmlReader = XmlReader.Create(memoryStream); + xmlReader.MoveToContent(); + + // generate the xml document + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.PreserveWhitespace = true; + xmlDocument.LoadXml(xmlReader.ReadOuterXml()); + + return xmlDocument; + } + + #endregion + + #region ICustomTypeDescriptor + + //AttributeCollection ICustomTypeDescriptor.GetAttributes() + //{ + // return AttributeCollection.Empty; + //} + + //string ICustomTypeDescriptor.GetClassName() + //{ + // return TypeDescriptor.GetClassName(this, true); + //} + + //string ICustomTypeDescriptor.GetComponentName() + //{ + // return TypeDescriptor.GetComponentName(this, true); + //} + + //TypeConverter ICustomTypeDescriptor.GetConverter() + //{ + // return TypeDescriptor.GetConverter(GetType()); + //} + + //EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() + //{ + // return TypeDescriptor.GetDefaultEvent(GetType()); + //} + + //PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() + //{ + // return TypeDescriptor.GetDefaultProperty(GetType()); + //} + + //object ICustomTypeDescriptor.GetEditor(Type editorBaseType) + //{ + // return TypeDescriptor.GetEditor(GetType(), editorBaseType); + //} + + //EventDescriptorCollection ICustomTypeDescriptor.GetEvents() + //{ + // return EventDescriptorCollection.Empty; + //} + + //EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) + //{ + // return EventDescriptorCollection.Empty; + //} + + //PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() + //{ + // return PropertyDescriptorCollection.Empty; + //} + + //PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) + //{ + // PropertyDescriptorCollection propertyDescriptorCollection = new PropertyDescriptorCollection(null); + + // if (this.serverType == ServerType.SQL) + // { + // bool serverIsAvailable = false; + // int queryTimeout = 30; + + // try + // { + // // five seconds should be plenty to determine the network name of the server + // queryTimeout = this.m_server.ConnectionContext.StatementTimeout; + // this.m_server.ConnectionContext.StatementTimeout = 5; + // serverIsAvailable = this.m_server.Information.NetName != null; + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + // } + // finally + // { + // this.m_server.ConnectionContext.StatementTimeout = queryTimeout; + // } + + // PropertyDescriptor propertyDescriptor; + // /////////////////////////////ServerEnvironment//////////////////////////////// + // // Computer Name + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? this.m_server.Information.NetName : SR.ServerIsUnavailable, + // SR.ComputerName, + // SR.ServerEnvironment, + // SR.ComputerNameDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.ComputerName, + // SR.ServerEnvironment, + // SR.ComputerNameDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Platform + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // ((this.m_server.Information.Platform == null) ? + // String.Empty : + // this.m_server.Information.Platform) : + // SR.ServerIsUnavailable, + // SR.Platform, + // SR.ServerEnvironment, + // SR.PlatformDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.Platform, + // SR.ServerEnvironment, + // SR.PlatformDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Operating System + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? this.m_server.Information.OSVersion : SR.ServerIsUnavailable, + // SR.OperatingSystem, + // SR.ServerEnvironment, + // SR.OperatingSystemDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.OperatingSystem, + // SR.ServerEnvironment, + // SR.OperatingSystemDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Processors + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.Information.Processors.ToString() : + // SR.ServerIsUnavailable, + // SR.Processors, + // SR.ServerEnvironment, + // SR.ProcessorsDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.Processors, + // SR.ServerEnvironment, + // SR.ProcessorsDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Operating System Memory + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.Information.PhysicalMemory.ToString() : + // SR.ServerIsUnavailable, + // SR.OperatingSystemMemory, + // SR.ServerEnvironment, + // SR.OperatingSystemMemoryDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.OperatingSystemMemory, + // SR.ServerEnvironment, + // SR.OperatingSystemMemoryDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // /////////////////////////////ProductCategory//////////////////////////////// + // // Product Name + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // (this.m_server.Information.Product + " " + this.m_server.Information.Edition) : + // SR.ServerIsUnavailable, + // SR.ProductName, + // SR.ProductCategory, + // SR.ProductNameDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.ProductName, + // SR.ProductCategory, + // SR.ProductNameDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Product Version + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // (this.m_server.Information.Version + " " + this.m_server.Information.ProductLevel) : + // SR.ServerIsUnavailable, + // SR.ProductVersion, + // SR.ProductCategory, + // SR.ProductVersionDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.ProductVersion, + // SR.ProductCategory, + // SR.ProductVersionDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Server Name + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? this.m_server.Name : SR.ServerIsUnavailable, + // SR.ServerName, + // SR.ProductCategory, + // SR.ServerNameDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.ServerName, + // SR.ProductCategory, + // SR.ServerNameDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Instance Name + // try + // { + // string instanceName = serverIsAvailable ? this.m_server.InstanceName : SR.ServerIsUnavailable; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (instanceName != null) ? instanceName : String.Empty, + // SR.InstanceName, + // SR.ProductCategory, + // SR.InstanceNameDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.InstanceName, + // SR.ProductCategory, + // SR.InstanceNameDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Language + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? this.m_server.Information.Language : SR.ServerIsUnavailable, + // SR.Language, + // SR.ProductCategory, + // SR.LanguageDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.Language, + // SR.ProductCategory, + // SR.LanguageDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Collation + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? this.m_server.Information.Collation : SR.ServerIsUnavailable, + // SR.Collation, + // SR.ProductCategory, + // SR.CollationDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.Collation, + // SR.ProductCategory, + // SR.CollationDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // /////////////////////////////ConnectionCategory//////////////////////////////// + // // Database + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.ConnectionContext.SqlConnectionObject.Database : + // SR.ServerIsUnavailable, + // SR.Database, + // SR.ConnectionCategory, + // SR.DatabaseDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.Database, + // SR.ConnectionCategory, + // SR.DatabaseDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // SPID + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.ConnectionContext.ProcessID.ToString() : + // SR.ServerIsUnavailable, + // SR.Spid, + // SR.ConnectionCategory, + // SR.SpidDescription); + // } + // catch (InvalidCastException) + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // String.Empty, + // SR.Spid, + // SR.ConnectionCategory, + // SR.SpidDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.Spid, + // SR.ConnectionCategory, + // SR.SpidDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Network Protocol + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // (this.m_server.ConnectionContext.NetworkProtocol == NetworkProtocol.NotSpecified ? + // SR.Default : + // this.m_server.ConnectionContext.NetworkProtocol.ToString()) : + // SR.ServerIsUnavailable, + // SR.NetworkProtocol, + // SR.ConnectionCategory, + // SR.NetworkProtocolDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.NetworkProtocol, + // SR.ConnectionCategory, + // SR.NetworkProtocolDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Network Packet Size + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.ConnectionContext.PacketSize.ToString() : + // SR.ServerIsUnavailable, + // SR.NetworkPacketSize, + // SR.ConnectionCategory, + // SR.NetworkPacketSizeDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.NetworkPacketSize, + // SR.ConnectionCategory, + // SR.NetworkPacketSizeDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Connect Timeout + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.ConnectionContext.ConnectTimeout.ToString() : + // SR.ServerIsUnavailable, + // SR.ConnectTimeout, + // SR.ConnectionCategory, + // SR.ConnectTimeoutDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.ConnectTimeout, + // SR.ConnectionCategory, + // SR.ConnectTimeoutDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Statement Timeout + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? + // this.m_server.ConnectionContext.StatementTimeout.ToString() : + // SR.ServerIsUnavailable, + // SR.StatementTimeout, + // SR.ConnectionCategory, + // SR.StatementTimeoutDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.StatementTimeout, + // SR.ConnectionCategory, + // SR.StatementTimeoutDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Encryption + // try + // { + // string encryptAsString = SR.NoString; + + // if (serverIsAvailable) + // { + // if (this.m_server.ConnectionContext.EncryptConnection) + // { + // encryptAsString = SR.YesString; + // } + // } + // else + // { + // encryptAsString = SR.ServerIsUnavailable; + // } + + // propertyDescriptor = new PropertyDescriptorWrapper( + // encryptAsString, + // SR.EncryptedConnection, + // SR.ConnectionCategory, + // SR.EncryptedConnectionDescription); + // } + // catch (InvalidCastException) + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // String.Empty, + // SR.EncryptedConnection, + // SR.ConnectionCategory, + // SR.EncryptedConnectionDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.EncryptedConnection, + // SR.ConnectionCategory, + // SR.EncryptedConnectionDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // /////////////////////////////Authentication//////////////////////////////// + // // Authentication + // try + // { + // string authenticationType; + + // if (!serverIsAvailable) + // { + // authenticationType = SR.ServerIsUnavailable; + // } + // else if (this.m_server.ConnectionContext.LoginSecure) + // { + // authenticationType = SR.WindowsAuthentication; + // } + // else + // { + // authenticationType = SR.SqlServerAuthentication; + // } + + // propertyDescriptor = new PropertyDescriptorWrapper( + // authenticationType, + // SR.AuthenticationMethod, + // SR.AuthenticationCategory, + // SR.AuthenticationMethodDescription); + + // } + // catch (PropertyNotAvailableException e) + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message == null) ? String.Empty : e.Message, + // SR.AuthenticationMethod, + // SR.AuthenticationCategory, + // SR.AuthenticationMethodDescription); + + // } + // catch (Exception exception) + // { + // if (MustRethrow(exception)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (exception.Message != null) ? exception.Message : SR.ServerIsUnavailable, + // SR.EncryptedConnection, + // SR.ConnectionCategory, + // SR.EncryptedConnectionDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // TrueLogin + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper( + // serverIsAvailable ? this.m_server.ConnectionContext.TrueLogin : SR.ServerIsUnavailable, + // SR.UserName, + // SR.AuthenticationCategory, + // SR.UserNameDescription); + // } + // catch (Exception e) + // { + // if (MustRethrow(e)) + // { + // throw; + // } + + // serverIsAvailable = false; + + // propertyDescriptor = new PropertyDescriptorWrapper( + // (e.Message != null) ? e.Message : SR.ServerIsUnavailable, + // SR.UserName, + // SR.AuthenticationCategory, + // SR.UserNameDescription); + // } + + // propertyDescriptorCollection.Add(propertyDescriptor); + // } + // else if (!Utils.IsSsmsMinimalSet()) + // { + // if (this.serverType == ServerType.OLAP) + // { + // PropertyDescriptor propertyDescriptor; + + // /////////////////////////////ServerEnvironment//////////////////////////////// + + // /////////////////////////////ProductCategory//////////////////////////////// + // // Product Version + // propertyDescriptor = new PropertyDescriptorWrapper(this.m_amoServer.Version, SR.ProductVersion, SR.ProductCategory, SR.ProductVersionDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // // Server Name + // propertyDescriptor = new PropertyDescriptorWrapper(this.m_amoServer.Name, SR.ServerName, SR.ProductCategory, SR.ServerNameDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // // Language + // int langAsInt = Convert.ToInt32(this.m_amoServer.ServerProperties[@"Language"].Value, CultureInfo.InvariantCulture); + // if (langAsInt > 0) + // { + // try + // { + // propertyDescriptor = new PropertyDescriptorWrapper(new CultureInfo(langAsInt).ToString(), SR.Language, SR.ProductCategory, SR.LanguageDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // } + // catch //eat it - CultureInfo might not be creatable from this ID + // { } + // } + // // Collation + // propertyDescriptor = new PropertyDescriptorWrapper(this.m_amoServer.ServerProperties[@"CollationName"].Value, SR.Collation, SR.ProductCategory, SR.CollationDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + + // /////////////////////////////ConnectionCategory//////////////////////////////// + // // Database + // propertyDescriptor = new PropertyDescriptorWrapper(this.ConnectionInfo.DatabaseName, SR.Database, SR.ConnectionCategory, SR.DatabaseDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // // Connect Timeout + // propertyDescriptor = new PropertyDescriptorWrapper(this.ConnectionInfo.ConnectionTimeout, SR.ConnectTimeout, SR.ConnectionCategory, SR.ConnectTimeoutDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // // Execution Timeout + // propertyDescriptor = new PropertyDescriptorWrapper(this.ConnectionInfo.QueryTimeout, SR.StatementTimeout, SR.ConnectionCategory, SR.StatementTimeoutDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + + // //whether connection is encrypted or not + // OlapConnectionInfo olapCi = this.ConnectionInfo as OlapConnectionInfo; + // if (olapCi != null) + // { + // string encryptAsString = SR.NoString; + // if (olapCi.EncryptConnection) + // { + // encryptAsString = SR.YesString; + // } + // propertyDescriptor = new PropertyDescriptorWrapper(encryptAsString, SR.EncryptedConnection, SR.ConnectionCategory, SR.EncryptedConnectionDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // } + + // // Authentication + // if (this.ConnectionInfo.UseIntegratedSecurity) + // propertyDescriptor = new PropertyDescriptorWrapper(SR.WindowsAuthentication, SR.AuthenticationMethod, SR.AuthenticationCategory, SR.AuthenticationMethodDescription); + // else + // propertyDescriptor = new PropertyDescriptorWrapper(SR.SqlServerAuthentication, SR.AuthenticationMethod, SR.AuthenticationCategory, SR.AuthenticationMethodDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // // TrueLogin + // propertyDescriptor = new PropertyDescriptorWrapper(this.ConnectionInfo.UserName, SR.UserName, SR.AuthenticationCategory, SR.UserNameDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + // } + // else if (this.serverType == ServerType.SQLCE) + // { + // PropertyDescriptor propertyDescriptor; + + // // Create an instance of SQLCE engine through reflection + // // + // Assembly asm = SfcUtility.LoadSqlCeAssembly( + // "Microsoft.SqlServerCe.Client.dll"); + // Type type = asm.GetType("Microsoft.SqlServerCe.Client.SqlCeEngine", true); + + // // Create SqlCeEngine instance + // // + // ConstructorInfo constructor = type.GetConstructor(new Type[] { }); + // object instance = constructor.Invoke(new object[] { }); + + // // Call GetDatabaseProperties + // // + // Type[] types = { typeof(String) }; + // MethodInfo mi = type.GetMethod("GetDatabaseProperties", types); + + // instance = mi.Invoke(instance, new object[] { SqlCeFileName }); + + // // Call the accessors on the DatabaseProperties class + // // + // type = asm.GetType("Microsoft.SqlServerCe.Client.DatabaseProperties", true); + + // PropertyInfo pi = type.GetProperty("Platform"); + // string platform = (string)pi.GetValue(instance, new object[] { }); + + // pi = type.GetProperty("OSVersion"); + // string osVersion = (string)pi.GetValue(instance, new object[] { }); + + // pi = type.GetProperty("Version"); + // string sqlCeVersion = (string)pi.GetValue(instance, new object[] { }); + + // pi = type.GetProperty("ClrVersion"); + // string clrVersion = (string)pi.GetValue(instance, new object[] { }); + + // // Platform + // propertyDescriptor = new PropertyDescriptorWrapper(platform, SR.Platform, SR.ServerEnvironment, SR.PlatformDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Operating System + // propertyDescriptor = new PropertyDescriptorWrapper(osVersion, SR.OperatingSystem, SR.ServerEnvironment, SR.OperatingSystemDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // CLR version + // propertyDescriptor = new PropertyDescriptorWrapper(clrVersion, SR.ClrVersion, SR.ServerEnvironment, SR.ClrVersionDescription); + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Product Name + // propertyDescriptor = new PropertyDescriptorWrapper(SR.SqlServerCeName, SR.ProductName, SR.ProductCategory, SR.ProductNameDescriptionCE); + // propertyDescriptorCollection.Add(propertyDescriptor); + + // // Product Version + // propertyDescriptor = new PropertyDescriptorWrapper(sqlCeVersion, SR.ProductVersion, SR.ProductCategory, SR.ProductVersionDescriptionCE); + // propertyDescriptorCollection.Add(propertyDescriptor); + // } + // } + // return propertyDescriptorCollection; + //} + + //object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) + //{ + // return this; + //} + + #endregion + + #region IDisposable + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// MUST be called, as we'll be closing SQL connection inside this call + /// + private void Dispose(bool disposing) + { + try + { + //take care of live SQL connection + if (this.sqlCiWithConnection != null) + { + this.sqlCiWithConnection.ConnectionClosed -= new EventHandler(OnSqlConnectionClosed); + + if (disposing) + { + //if we have the managed connection interface, then use it to disconnect. + //Otherwise, Dispose on SqlConnectionInfoWithConnection should disconnect + if (this.managedConnection != null) + { + //in this case we have gotten sqlCiWithConnection as this.managedConnection.Connection + if (this.ownConnection) + { + this.managedConnection.Close(); + } + this.managedConnection = null; + } + else + { + if (this.ownConnection) + { + this.sqlCiWithConnection.Dispose();//internally will decide whether to disconnect or not + } + } + this.sqlCiWithConnection = null; + } + else + { + this.managedConnection = null; + this.sqlCiWithConnection = null; + } + } + else if (this.managedConnection != null) + { + if (disposing && this.ownConnection) + { + this.managedConnection.Close(); + } + this.managedConnection = null; + } + } + catch (Exception exToEat) + { + } + } + + #endregion + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype.cs new file mode 100644 index 00000000..a5f2cc97 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype.cs @@ -0,0 +1,2679 @@ +// +// 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.Specialized; +using System.ComponentModel; +using System.Resources; +// using System.Data; +//using System.Windows.Forms; +//using Microsoft.SqlServer.Management.AzureSqlDbUtils; +using Microsoft.SqlServer.Management.Common; +//using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Diagnostics; +//using Microsoft.NetEnterpriseServers; +using System.Globalization; +using System.Data.SqlClient; +using System.Collections.Generic; +using System.Diagnostics; +//using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; +using AzureEdition = Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper.AzureEdition; +using Microsoft.Data.Tools.DataSets; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database Prototype + /// + /// + /// This exposes properties common to all server versions. Properties specific to + /// versions after 7.0 are in a derived class. + /// + public class DatabasePrototype : IDynamicValues + { + #region data members + + protected class DatabaseData + { + public string name; + public string owner; + public string collation; + + public RecoveryModel recoveryModel; + public DatabaseUserAccess restrictAccess; + public DatabaseStatus databaseState; + public DefaultCursor defaultCursor; + public CompatibilityLevel databaseCompatibilityLevel; + public ContainmentType databaseContainmentType; + public PageVerify pageVerify; + public AzureEdition azureEdition; + public string configuredServiceLevelObjective; + public string currentServiceLevelObjective; + public DbSize maxSize; + + public bool closeCursorOnCommit; + public bool isReadOnly; + public bool autoClose; + public bool autoShrink; + public bool autoCreateStatistics; + public bool autoCreateStatisticsIncremental; + public bool autoUpdateStatistics; + public bool autoUpdateStatisticsAsync; + public bool ansiNullDefault; + public bool ansiNulls; + public bool ansiWarnings; + public bool ansiPadding; + public bool arithabort; + public bool concatNullYieldsNull; + public bool numericRoundAbort; + public bool quotedIdentifier; + public bool recursiveTriggers; + public bool fullTextIndexingEnabled; + public bool dbChaining; + public bool trustworthy; + public bool dateCorrelationOptimization; + public bool brokerEnabled; + public bool parameterization; + public bool varDecimalEnabled; + public bool encryptionEnabled; + public bool honorBrokerPriority; + public int defaultFulltextLanguageLcid; + public int defaultLanguageLcid; + public int twoDigitYearCutoff; + public int targetRecoveryTime; + public bool nestedTriggersEnabled; + public bool transformNoiseWords; + public bool isReadCommittedSnapshotOn; + public bool allowSnapshotIsolation; + public string defaultCollation = string.Empty; + public System.Guid serviceBrokerGuid; + public FilestreamNonTransactedAccessType filestreamNonTransactedAccess = FilestreamNonTransactedAccessType.Off; + public string filestreamDirectoryName = string.Empty; + public DelayedDurability delayedDurability; + + public MirroringSafetyLevel mirrorSafetyLevel = MirroringSafetyLevel.Off; + public string witnessServer = string.Empty; + + public bool isSystemDB; + + public bool queryStoreEnabled; + + public int maxDop; + public int? maxDopForSecondary; + public DatabaseScopedConfigurationOnOff legacyCardinalityEstimation; + public DatabaseScopedConfigurationOnOff legacyCardinalityEstimationForSecondary; + public DatabaseScopedConfigurationOnOff parameterSniffing; + public DatabaseScopedConfigurationOnOff parameterSniffingForSecondary; + public DatabaseScopedConfigurationOnOff queryOptimizerHotfixes; + public DatabaseScopedConfigurationOnOff queryOptimizerHotfixesForSecondary; + + + /// + /// Constructor for new databases using default data + /// + /// + /// This method is only called when the user doesn't have access to the model database + /// + public DatabaseData(CDataContainer context) + { + this.name = String.Empty; + this.owner = String.Empty; + this.restrictAccess = DatabaseUserAccess.Multiple; + this.isReadOnly = false; + this.databaseState = DatabaseStatus.Normal; + this.closeCursorOnCommit = false; + this.defaultCursor = DefaultCursor.Global; + this.autoClose = false; + this.autoShrink = false; + this.autoCreateStatistics = true; + this.autoCreateStatisticsIncremental = false; + this.autoUpdateStatistics = true; + this.autoUpdateStatisticsAsync = false; + this.ansiNullDefault = false; + this.ansiNulls = false; + this.ansiPadding = false; + this.ansiWarnings = false; + this.arithabort = false; + this.concatNullYieldsNull = false; + this.numericRoundAbort = false; + this.quotedIdentifier = false; + this.recursiveTriggers = false; + this.recoveryModel = RecoveryModel.Simple; + this.dbChaining = false; + this.trustworthy = false; + this.dateCorrelationOptimization = false; + this.brokerEnabled = false; + this.parameterization = false; + this.varDecimalEnabled = false; + this.encryptionEnabled = false; + this.honorBrokerPriority = false; + this.filestreamNonTransactedAccess = FilestreamNonTransactedAccessType.Off; + this.filestreamDirectoryName = String.Empty; + this.delayedDurability = DelayedDurability.Disabled; + this.azureEdition = AzureEdition.Standard; + this.configuredServiceLevelObjective = String.Empty; + this.currentServiceLevelObjective = String.Empty; + this.maxSize = new DbSize(0, DbSize.SizeUnits.MB); + this.maxDop = 0; + this.maxDopForSecondary = null; + this.legacyCardinalityEstimation = DatabaseScopedConfigurationOnOff.Off; + this.legacyCardinalityEstimationForSecondary = DatabaseScopedConfigurationOnOff.Primary; + this.parameterSniffing = DatabaseScopedConfigurationOnOff.On; + this.parameterSniffingForSecondary = DatabaseScopedConfigurationOnOff.Primary; + this.queryOptimizerHotfixes = DatabaseScopedConfigurationOnOff.Off; + this.queryOptimizerHotfixesForSecondary = DatabaseScopedConfigurationOnOff.Primary; + + //The following properties are introduced for contained databases. + //In case of plain old databases, these values should reflect the server configuration values. + this.defaultFulltextLanguageLcid = context.Server.Configuration.DefaultFullTextLanguage.ConfigValue; + int defaultLanguagelangid = context.Server.Configuration.DefaultLanguage.ConfigValue; + this.defaultLanguageLcid = 1033; // LanguageUtils.GetLcidFromLangId(context.Server, defaultLanguagelangid); + this.nestedTriggersEnabled = context.Server.Configuration.NestedTriggers.ConfigValue == 1; + this.transformNoiseWords = context.Server.Configuration.TransformNoiseWords.ConfigValue == 1; + this.twoDigitYearCutoff = context.Server.Configuration.TwoDigitYearCutoff.ConfigValue; + + this.targetRecoveryTime = 0; + + + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + + //in katmai var decimal going to be true by default + if (context.Server.Information.Version.Major >= 10) + { + this.varDecimalEnabled = true; + } + + if (7 < context.Server.Information.Version.Major) + { + this.collation = this.defaultCollation = manager.GetString("general_default"); + } + else + { + this.collation = String.Empty; + } + + if (9 <= context.Server.Information.Version.Major) + { + this.pageVerify = PageVerify.Checksum; + } + else + { + this.pageVerify = PageVerify.TornPageDetection; + } + + // Full-text indexing will always be enabled in Katmai + if (context.Server.Information.Version.Major <= 9) + { + this.fullTextIndexingEnabled = false; + } + else + { + this.fullTextIndexingEnabled = true; + } + + switch (context.SqlServerVersion) + { + case 6: + + string errorMessage = manager.GetString("error_60compatibility"); + throw new InvalidOperationException(errorMessage); + + case 7: + + this.databaseCompatibilityLevel = CompatibilityLevel.Version70; + break; + + case 8: + + this.databaseCompatibilityLevel = CompatibilityLevel.Version80; + break; + + case 9: + + this.databaseCompatibilityLevel = CompatibilityLevel.Version90; + break; + + case 10: + + this.databaseCompatibilityLevel = CompatibilityLevel.Version100; + break; + + case 11: + + this.databaseCompatibilityLevel = CompatibilityLevel.Version110; + break; + + case 12: + + this.databaseCompatibilityLevel = CompatibilityLevel.Version120; + break; + + case 13: + this.databaseCompatibilityLevel = CompatibilityLevel.Version130; + break; + + case 14: + this.databaseCompatibilityLevel = CompatibilityLevel.Version140; + break; + + default: + this.databaseCompatibilityLevel = CompatibilityLevel.Version140; + break; + } + + if (context.Server.ServerType == DatabaseEngineType.SqlAzureDatabase) + { //These properties are only available for Azure DBs + this.azureEdition = AzureEdition.Standard; + this.currentServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(this.azureEdition); + this.configuredServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(this.azureEdition); + this.maxSize = AzureSqlDbHelper.GetDatabaseDefaultSize(this.azureEdition); + } + } + + /// + /// Query to get the current and configured SLO for a target DB. Must be ran on the master DB. + /// + private const string dbSloQuery = @" +SELECT so.name as configured_slo_name, so2.name as current_slo_name +FROM dbo.slo_database_objectives do + INNER JOIN dbo.slo_service_objectives so ON do.configured_objective_id = so.objective_id + INNER JOIN dbo.slo_service_objectives so2 ON do.current_objective_id = so2.objective_id +WHERE do.database_id = @DbID +"; + + /// + /// Constructor for existing databases + /// + public DatabaseData(CDataContainer context, string databaseName) + { + // set prototype properties to match the database + Database db = context.Server.Databases[databaseName]; + + if (db == null) + { + context.Server.Databases.Refresh(); + db = context.Server.Databases[databaseName]; + } + + isSystemDB = db.IsSystemObject; + + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + + this.owner = db.Owner; + + // Databases that are restored from other servers might not have valid owners. + // If the logged in user is an administrator and the owner is not valid, show + // the owner as blank. Note that only administrators can successfully change + // execution context to dbo to perform the check for an invalid owner. + if ((9 <= context.SqlServerVersion) && context.LoggedInUserIsSysadmin) + { + try + { + DataSet dsResult = db.ExecuteWithResults( + "select suser_sname((select sid from sys.database_principals where name = N'dbo'));"); + DataTable tableResult = dsResult.Tables[0]; + if (tableResult.Rows.Count > 0 && tableResult.Columns.Count > 0) + { + DataRow rowResult = tableResult.Rows[0]; + DataColumn colResult = tableResult.Columns[0]; + if (string.IsNullOrEmpty(rowResult[colResult].ToString())) + { + this.owner = String.Empty; + } + } + else + { + this.owner = String.Empty; + } + + } + catch (FailedOperationException) + { + // the owner is invalid, set the owner string to String.Empty + this.owner = String.Empty; + } + } + + this.name = databaseName; + this.restrictAccess = db.DatabaseOptions.UserAccess; + + try + { + this.databaseState = db.Status; + } + catch (Exception ex) + { + SqlException sqlException = CUtils.GetSqlException(ex); + if (null != sqlException && true == CUtils.IsPermissionDeniedException(sqlException)) + { + this.databaseState = DatabaseStatus.Inaccessible; + } + else + { + throw ex; + } + } + + this.closeCursorOnCommit = db.DatabaseOptions.CloseCursorsOnCommitEnabled; + this.defaultCursor = (db.IsSupportedProperty("LocalCursorsDefault") && + db.DatabaseOptions.LocalCursorsDefault) + ? DefaultCursor.Local + : DefaultCursor.Global; + if (db.IsSupportedProperty("AutoClose")) + { + this.autoClose = db.DatabaseOptions.AutoClose; + } + this.autoShrink = db.DatabaseOptions.AutoShrink; + this.autoCreateStatistics = db.DatabaseOptions.AutoCreateStatistics; + this.autoUpdateStatistics = db.DatabaseOptions.AutoUpdateStatistics; + this.ansiNullDefault = db.DatabaseOptions.AnsiNullDefault; + this.ansiNulls = db.DatabaseOptions.AnsiNullsEnabled; + this.quotedIdentifier = db.DatabaseOptions.QuotedIdentifiersEnabled; + this.recursiveTriggers = db.DatabaseOptions.RecursiveTriggersEnabled; + if (db.IsSupportedProperty("RecoveryModel")) + { + this.recoveryModel = db.DatabaseOptions.RecoveryModel; + } + + if (Utils.IsSql12OrLater(context.Server.Information.Version.Major)) + { + this.autoCreateStatisticsIncremental = db.DatabaseOptions.AutoCreateStatisticsIncremental; + } + + // SQL Server 2000 options + if (7 < context.Server.Information.Version.Major) + { + if (context.IsNewObject) + { + this.collation = this.defaultCollation = manager.GetString("general_default"); + } + else + { + this.collation = db.Collation; + } + + this.isReadOnly = db.DatabaseOptions.ReadOnly; + if (db.IsSupportedProperty("PageVerify")) + { + this.pageVerify = db.DatabaseOptions.PageVerify; + } + this.ansiWarnings = db.DatabaseOptions.AnsiWarningsEnabled; + this.ansiPadding = db.DatabaseOptions.AnsiPaddingEnabled; + this.arithabort = db.DatabaseOptions.ArithmeticAbortEnabled; + this.numericRoundAbort = db.DatabaseOptions.NumericRoundAbortEnabled; + this.concatNullYieldsNull = db.DatabaseOptions.ConcatenateNullYieldsNull; + } + else + { + this.collation = String.Empty; + this.isReadOnly = false; + this.pageVerify = PageVerify.None; + this.ansiPadding = false; + this.ansiWarnings = false; + this.arithabort = false; + this.numericRoundAbort = false; + this.concatNullYieldsNull = false; + } + + // DB_CHAINING supported in SQL Server 2000 SP3 and later + Version sql2000sp3 = new Version(8, 0, 760); + if (sql2000sp3 <= context.Server.Information.Version) + { + this.dbChaining = db.DatabaseOptions.DatabaseOwnershipChaining; + } + else + { + this.dbChaining = false; + } + + // SQL Server 2005 options + if (8 < context.Server.Version.Major) + { + this.autoUpdateStatisticsAsync = db.DatabaseOptions.AutoUpdateStatisticsAsync; + this.trustworthy = db.DatabaseOptions.Trustworthy; + this.dateCorrelationOptimization = db.DatabaseOptions.DateCorrelationOptimization; + this.parameterization = db.DatabaseOptions.IsParameterizationForced; + this.isReadCommittedSnapshotOn = db.IsReadCommittedSnapshotOn; + if (db.IsSupportedProperty("BrokerEnabled")) + { + this.serviceBrokerGuid = db.ServiceBrokerGuid; + this.brokerEnabled = db.BrokerEnabled; + } + if (db.IsSupportedProperty("SnapshotIsolationState")) + { + this.allowSnapshotIsolation = !(db.SnapshotIsolationState == SnapshotIsolationState.Disabled); + } + } + else + { + this.autoUpdateStatisticsAsync = false; + this.trustworthy = false; + this.dateCorrelationOptimization = false; + this.brokerEnabled = false; + this.parameterization = false; + this.isReadCommittedSnapshotOn = false; + this.allowSnapshotIsolation = false; + } + + this.varDecimalEnabled = + // db.IsVarDecimalStorageFormatSupported && + db.IsVarDecimalStorageFormatEnabled; + + // SQL Server 2008 options + // Both EncryptionEnabled and HonorPriority are added in SQL Server 2008 only and hence one condition is enough + if (db.IsSupportedProperty("EncryptionEnabled")) + { + this.encryptionEnabled = db.EncryptionEnabled; + this.honorBrokerPriority = db.HonorBrokerPriority; + this.varDecimalEnabled = true; + } + else + { + this.encryptionEnabled = false; + this.honorBrokerPriority = false; + } + + if (db.IsSupportedProperty("FilestreamDirectoryName")) + { + this.filestreamDirectoryName = db.FilestreamDirectoryName; + this.filestreamNonTransactedAccess = db.FilestreamNonTransactedAccess; + } + + try + { + if (context.Server.IsSupportedProperty("IsFullTextInstalled") && context.Server.IsFullTextInstalled) + { + // Full-text indexing will always be enabled in Katmai + if (db.IsSupportedProperty("IsFullTextEnabled")) + { + this.fullTextIndexingEnabled = db.IsFullTextEnabled; + } + else + { + this.fullTextIndexingEnabled = true; + } + } + } + catch (Exception ex) + { + SqlException sqlException = CUtils.GetSqlException(ex); + if (null != sqlException && true == CUtils.IsPermissionDeniedException(sqlException)) + { + // assume false + this.fullTextIndexingEnabled = false; + } + else + { + throw; + } + } + + + if ((db.CompatibilityLevel == CompatibilityLevel.Version60) || + (db.CompatibilityLevel == CompatibilityLevel.Version65)) + { + string errorMessage = manager.GetString("error_60compatibility"); + throw new InvalidOperationException(errorMessage); + } + + this.databaseCompatibilityLevel = db.CompatibilityLevel; + + if (db.IsSupportedProperty("ContainmentType")) + { + this.databaseContainmentType = db.ContainmentType; + this.defaultFulltextLanguageLcid = db.DefaultFullTextLanguage.Lcid; + this.defaultLanguageLcid = db.DefaultLanguage.Lcid; + this.nestedTriggersEnabled = db.NestedTriggersEnabled; + this.transformNoiseWords = db.TransformNoiseWords; + this.twoDigitYearCutoff = db.TwoDigitYearCutoff; + } + + // SQL Server 2011 options + // TargetRecoveryTime is added in SQL Server 2011 + if (db.IsSupportedProperty("TargetRecoveryTime")) + { + this.targetRecoveryTime = db.TargetRecoveryTime; + } + + try + { + if (db.IsSupportedProperty("IsMirroringEnabled") && db.IsMirroringEnabled) + { + this.mirrorSafetyLevel = db.MirroringSafetyLevel; + this.witnessServer = db.MirroringWitness; + } + } + catch (Exception ex) + { + SqlException sqlException = CUtils.GetSqlException(ex); + if (null != sqlException && true == CUtils.IsPermissionDeniedException(sqlException)) + { + /// do nothing + } + else + { + throw ex; + } + } + + // SQL Server 2014 options + // DelayedDurability is added in SQL Server 2014 + if (db.IsSupportedProperty("DelayedDurability")) + { + this.delayedDurability = db.DelayedDurability; + } + + //Only fill in the Azure properties when connected to an Azure server + if (context.Server.ServerType == DatabaseEngineType.SqlAzureDatabase) + { + AzureEdition edition; + if (Enum.TryParse(db.AzureEdition, true, out edition)) + { + this.azureEdition = edition; + } + else + { + //Unknown Azure DB Edition so we can't continue + throw new Exception("CreateDatabaseOptionsSR.Error_UnknownAzureEdition(db.AzureEdition)"); + } + + //Size is in MB, but if it's greater than a GB we want to display the size in GB + //We do this to be on par with what the management portal displays + if (db.Size >= 1024) + { + this.maxSize = new DbSize((int)(db.Size / 1024.0), DbSize.SizeUnits.GB); + } + else + { + this.maxSize = new DbSize((int)db.Size, DbSize.SizeUnits.MB); + } + + this.GetServiceLevelObjectiveValues(context); + + } + + // Check if we support database scoped configurations on this server. Since these were all added at the same time, + // only check if MaxDop is supported rather than each individual property. + if (db.IsSupportedProperty("MaxDop")) + { + this.maxDop = db.MaxDop; + this.maxDopForSecondary = db.MaxDopForSecondary; + this.legacyCardinalityEstimation = db.LegacyCardinalityEstimation; + this.legacyCardinalityEstimationForSecondary = db.LegacyCardinalityEstimationForSecondary; + this.parameterSniffing = db.ParameterSniffing; + this.parameterSniffingForSecondary = db.ParameterSniffingForSecondary; + this.queryOptimizerHotfixes = db.QueryOptimizerHotfixes; + this.queryOptimizerHotfixesForSecondary = db.QueryOptimizerHotfixesForSecondary; + } + } + + /// + /// Fetches the values of the current and configured Service Level Objective + /// for the target DB of this object + /// + private void GetServiceLevelObjectiveValues(CDataContainer context) + { + Database db = context.Server.Databases[this.name]; + + //For Azure v12 or later we can use SMO (the property doesn't exist prior to v12) + if (Utils.IsSql12OrLater(context.Server.Information.Version.Major)) + { + //Currently the only way to get the configured service level objective is to use the REST API. + //Since SSMS doesn't currently support that we'll leave it blank for now until support is + //added or T-SQL supports getting the configured SLO + this.configuredServiceLevelObjective = ""; + this.currentServiceLevelObjective = db.AzureServiceObjective; + } + else + { //If it's under v12 we need to query the master DB directly since that has the views containing the necessary information + using (var conn = new SqlConnection(context.Server.ConnectionContext.ConnectionString)) + { + var cmd = new SqlCommand(dbSloQuery, conn); + cmd.Parameters.AddWithValue("@DbID", db.ID); + conn.Open(); + SqlDataReader reader = cmd.ExecuteReader(); + while (reader.Read()) + { + this.configuredServiceLevelObjective = reader["configured_slo_name"].ToString(); + this.currentServiceLevelObjective = reader["current_slo_name"].ToString(); + break; //Got our service level objective so we're done + } + + } + } + } + + /// + /// Creates an instance of DatabaseData - Copy constructor + /// + /// + public DatabaseData(DatabaseData other) + { + this.owner = other.owner; + this.name = other.name; + this.collation = other.collation; + this.recoveryModel = other.recoveryModel; + this.restrictAccess = other.restrictAccess; + this.databaseState = other.databaseState; + this.defaultCursor = other.defaultCursor; + this.databaseCompatibilityLevel = other.databaseCompatibilityLevel; + this.pageVerify = other.pageVerify; + this.closeCursorOnCommit = other.closeCursorOnCommit; + this.isReadOnly = other.isReadOnly; + this.autoClose = other.autoClose; + this.autoShrink = other.autoShrink; + this.autoCreateStatistics = other.autoCreateStatistics; + this.autoCreateStatisticsIncremental = other.autoCreateStatisticsIncremental; + this.autoUpdateStatistics = other.autoUpdateStatistics; + this.autoUpdateStatisticsAsync = other.autoUpdateStatisticsAsync; + this.ansiNullDefault = other.ansiNullDefault; + this.ansiNulls = other.ansiNulls; + this.ansiWarnings = other.ansiWarnings; + this.ansiPadding = other.ansiPadding; + this.arithabort = other.arithabort; + this.concatNullYieldsNull = other.concatNullYieldsNull; + this.numericRoundAbort = other.numericRoundAbort; + this.quotedIdentifier = other.quotedIdentifier; + this.recursiveTriggers = other.recursiveTriggers; + this.fullTextIndexingEnabled = other.fullTextIndexingEnabled; + this.mirrorSafetyLevel = other.mirrorSafetyLevel; + this.witnessServer = other.witnessServer; + this.dbChaining = other.dbChaining; + this.trustworthy = other.trustworthy; + this.dateCorrelationOptimization = other.dateCorrelationOptimization; + this.brokerEnabled = other.brokerEnabled; + this.parameterization = other.parameterization; + this.varDecimalEnabled = other.varDecimalEnabled; + this.encryptionEnabled = other.encryptionEnabled; + this.honorBrokerPriority = other.honorBrokerPriority; + this.serviceBrokerGuid = other.serviceBrokerGuid; + this.databaseContainmentType = other.databaseContainmentType; + this.defaultFulltextLanguageLcid = other.defaultFulltextLanguageLcid; + this.defaultLanguageLcid = other.defaultLanguageLcid; + this.nestedTriggersEnabled = other.nestedTriggersEnabled; + this.transformNoiseWords = other.transformNoiseWords; + this.twoDigitYearCutoff = other.twoDigitYearCutoff; + this.isReadCommittedSnapshotOn = other.isReadCommittedSnapshotOn; + this.allowSnapshotIsolation = other.allowSnapshotIsolation; + this.filestreamNonTransactedAccess = other.filestreamNonTransactedAccess; + this.filestreamDirectoryName = other.filestreamDirectoryName; + this.isSystemDB = other.isSystemDB; + this.targetRecoveryTime = other.targetRecoveryTime; + this.delayedDurability = other.delayedDurability; + this.azureEdition = other.azureEdition; + this.configuredServiceLevelObjective = other.configuredServiceLevelObjective; + this.currentServiceLevelObjective = other.currentServiceLevelObjective; + this.legacyCardinalityEstimation = other.legacyCardinalityEstimation; + this.legacyCardinalityEstimationForSecondary = other.legacyCardinalityEstimationForSecondary; + this.maxDop = other.maxDop; + this.maxDopForSecondary = other.maxDopForSecondary; + this.parameterSniffing = other.parameterSniffing; + this.parameterSniffingForSecondary = other.parameterSniffingForSecondary; + this.queryOptimizerHotfixes = other.queryOptimizerHotfixes; + this.queryOptimizerHotfixesForSecondary = other.queryOptimizerHotfixesForSecondary; + this.maxSize = other.maxSize == null ? null : new DbSize(other.maxSize); + } + + /// + /// Clones this instance of DatabaseData + /// + /// + public DatabaseData Clone() + { + return new DatabaseData(this); + } + + /// + /// Compares 2 instances of DatabaseData + /// + /// + /// + public bool HasSameValueAs(DatabaseData other) + { + bool result = + (this.recoveryModel == other.recoveryModel) && + (this.restrictAccess == other.restrictAccess) && + (this.databaseState == other.databaseState) && + (this.defaultCursor == other.defaultCursor) && + (this.databaseCompatibilityLevel == other.databaseCompatibilityLevel) && + (this.pageVerify == other.pageVerify) && + (this.closeCursorOnCommit == other.closeCursorOnCommit) && + (this.isReadOnly == other.isReadOnly) && + (this.autoClose == other.autoClose) && + (this.autoShrink == other.autoShrink) && + (this.autoCreateStatistics == other.autoCreateStatistics) && + (this.autoCreateStatisticsIncremental == other.autoCreateStatisticsIncremental) && + (this.autoUpdateStatistics == other.autoUpdateStatistics) && + (this.autoUpdateStatisticsAsync == other.autoUpdateStatisticsAsync) && + (this.ansiNullDefault == other.ansiNullDefault) && + (this.ansiNulls == other.ansiNulls) && + (this.ansiWarnings == other.ansiWarnings) && + (this.ansiPadding == other.ansiPadding) && + (this.arithabort == other.arithabort) && + (this.concatNullYieldsNull == other.concatNullYieldsNull) && + (this.numericRoundAbort == other.numericRoundAbort) && + (this.quotedIdentifier == other.quotedIdentifier) && + (this.recursiveTriggers == other.recursiveTriggers) && + (this.fullTextIndexingEnabled == other.fullTextIndexingEnabled) && + (this.owner == other.owner) && + (this.collation == other.collation) && + (this.witnessServer == other.witnessServer) && + (this.mirrorSafetyLevel == other.mirrorSafetyLevel) && + (this.dbChaining == other.dbChaining) && + (this.trustworthy == other.trustworthy) && + (this.dateCorrelationOptimization == other.dateCorrelationOptimization) && + (this.brokerEnabled == other.brokerEnabled) && + (this.parameterization == other.parameterization) && + (this.varDecimalEnabled == other.varDecimalEnabled) && + (this.encryptionEnabled == other.encryptionEnabled) && + (this.honorBrokerPriority == other.honorBrokerPriority) && + (this.databaseContainmentType == other.databaseContainmentType) && + (this.defaultFulltextLanguageLcid == other.defaultFulltextLanguageLcid) && + (this.defaultLanguageLcid == other.defaultLanguageLcid) && + (this.nestedTriggersEnabled == other.nestedTriggersEnabled) && + (this.transformNoiseWords == other.transformNoiseWords) && + (this.twoDigitYearCutoff == other.twoDigitYearCutoff) && + (this.targetRecoveryTime == other.targetRecoveryTime) && + (this.isReadCommittedSnapshotOn == other.isReadCommittedSnapshotOn) && + (this.allowSnapshotIsolation == other.allowSnapshotIsolation) && + (this.filestreamNonTransactedAccess == other.filestreamNonTransactedAccess) && + (this.filestreamDirectoryName.Equals(other.filestreamDirectoryName, StringComparison.OrdinalIgnoreCase)) && + (this.allowSnapshotIsolation == other.allowSnapshotIsolation) && + (this.delayedDurability == other.delayedDurability) && + (this.azureEdition == other.azureEdition) && + (this.configuredServiceLevelObjective == other.configuredServiceLevelObjective) && + (this.currentServiceLevelObjective == other.currentServiceLevelObjective) && + (this.maxDop == other.maxDop) && + (this.maxDopForSecondary == other.maxDopForSecondary) && + (this.legacyCardinalityEstimation == other.legacyCardinalityEstimation) && + (this.legacyCardinalityEstimationForSecondary == other.legacyCardinalityEstimationForSecondary) && + (this.parameterSniffing == other.parameterSniffing) && + (this.parameterSniffingForSecondary == other.parameterSniffingForSecondary) && + (this.queryOptimizerHotfixes == other.queryOptimizerHotfixes) && + (this.queryOptimizerHotfixesForSecondary == other.queryOptimizerHotfixesForSecondary) && + (this.queryStoreEnabled == other.queryStoreEnabled) && + (this.maxSize == other.maxSize); + + return result; + } + } + + // methods in the based class that modify files and filegroups should work directly against these members + // methods that use the collections as part of persistence or scripting should work against the corresponding wrapper properties + // so derived classes can override behavior + private readonly List filegroups; + private readonly List files; + + private readonly List removedFilegroups; + private readonly List removedFiles; + + protected DatabaseData originalState; + protected DatabaseData currentState; + + protected CDataContainer context; + private bool existingDatabase; + private int numberOfLogFiles; + protected ServerVersion serverVersion; + protected Microsoft.SqlServer.Management.Common.DatabaseEngineType databaseEngineType; + private bool isFilestreamEnabled; + + private event EventHandler observableChanged; + private bool allowNotifications = true; + + // constants + private const double kilobytesPerMegabyte = 1024.0d; + + #endregion + + #region properties + + protected DatabaseEngineEdition EditionToCreate { get; set; } + + /// + /// Whether or not the UI should show File Groups + /// + public virtual bool HideFileSettings + { + get { return false; } + } + + public virtual bool AllowScripting + { + get { return true; } + } + + /// + /// The name of the database + /// + [Browsable(false)] + public string Name + { + get + { + return this.currentState.name; + } + set + { + this.currentState.name = value; + this.NotifyObservers(); + } + } + + /// + /// The owner of the database + /// + [Browsable(false)] + public string Owner + { + get + { + return this.currentState.owner; + } + set + { + this.currentState.owner = value; + this.NotifyObservers(); + } + } + + /// + /// The recovery model for the database + /// + [Browsable(false)] + public virtual RecoveryModel RecoveryModel + { + get + { + return this.currentState.recoveryModel; + } + set + { + this.currentState.recoveryModel = value; + this.NotifyObservers(); + } + } + + /// + /// The collation for the database + /// + [Browsable(false)] + public string Collation + { + get + { + return this.currentState.collation; + } + set + { + this.currentState.collation = value; + this.NotifyObservers(); + } + } + + /// + /// Property return true for System DB + /// + [Browsable(false)] + public bool IsSystemDB + { + get { return this.currentState.isSystemDB; } + } + + /// + /// DatabaseUserAccess for the database + /// + [Category("Category_State"), + DisplayNameAttribute("Property_RestrictAccess"), + TypeConverter(typeof(RestrictAccessTypes))] + public string RestrictAccess + { + get + { + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + string result = null; + + switch (this.currentState.restrictAccess) + { + case DatabaseUserAccess.Multiple: + + result = manager.GetString("prototype_db_prop_restrictAccess_value_multiple"); + break; + + case DatabaseUserAccess.Restricted: + + result = manager.GetString("prototype_db_prop_restrictAccess_value_restricted"); + break; + + case DatabaseUserAccess.Single: + + result = manager.GetString("prototype_db_prop_restrictAccess_value_single"); + break; + + } + + return result; + } + set + { + ResourceManager manager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly()); + + if (value == manager.GetString("prototype_db_prop_restrictAccess_value_multiple")) + { + this.currentState.restrictAccess = DatabaseUserAccess.Multiple; + } + else if (value == manager.GetString("prototype_db_prop_restrictAccess_value_restricted")) + { + this.currentState.restrictAccess = DatabaseUserAccess.Restricted; + } + else + { + this.currentState.restrictAccess = DatabaseUserAccess.Single; + } + + this.NotifyObservers(); + } + } + + /// + /// Status of the database as text + /// + [Category("Category_State"), + DisplayNameAttribute("Property_DatabaseState"), + TypeConverter(typeof(DatabaseStatusTypes))] + public string DatabaseStateDisplay + { + get + { + string result = null; + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype90).GetAssembly()); + + if ((this.currentState.databaseState & DatabaseStatus.Normal) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.normal")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Restoring) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.restoring")); + } + + if ((this.currentState.databaseState & DatabaseStatus.RecoveryPending) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.recoveryPending")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Recovering) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.recovering")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Suspect) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.suspect")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Offline) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.offline")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Inaccessible) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.inaccessible")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Standby) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.standby")); + } + + if ((this.currentState.databaseState & DatabaseStatus.Shutdown) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.shutdown")); + } + + if ((this.currentState.databaseState & DatabaseStatus.EmergencyMode) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.emergency")); + } + + if ((this.currentState.databaseState & DatabaseStatus.AutoClosed) != 0) + { + result = this.AppendState(result, manager.GetString("prototype.db.prop.databaseState.value.autoClosed")); + } + + return result; + } + } + + /// + /// Status of the database + /// + [Browsable(false)] + public DatabaseStatus DatabaseState + { + get + { + return this.currentState.databaseState; + } + set + { + this.currentState.databaseState = value; + this.NotifyObservers(); + } + } + + /// + /// Whether cursors should be closed on transaction commit + /// + [Category("Category_Cursor"), + DisplayNameAttribute("Property_CloseCursorOnCommit")] + public bool CloseCursorOnCommit + { + get + { + return this.currentState.closeCursorOnCommit; + } + set + { + this.currentState.closeCursorOnCommit = value; + this.NotifyObservers(); + } + } + + /// + /// The default cursor type (local or global) + /// + [Category("Category_Cursor"), + DisplayNameAttribute("Property_DefaultCursor"), + TypeConverter(typeof(DefaultCursorTypes))] + public string DefaultCursorDisplay + { + get + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype).GetAssembly()); + string result = null; + + switch (this.currentState.defaultCursor) + { + case DefaultCursor.Local: + + result = manager.GetString("prototype.db.prop.defaultCursor.value.local"); + break; + + case DefaultCursor.Global: + + result = manager.GetString("prototype.db.prop.defaultCursor.value.global"); + break; + } + + return result; + } + + set + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype).GetAssembly()); + + if (value == manager.GetString("prototype.db.prop.defaultCursor.value.local")) + { + this.currentState.defaultCursor = DefaultCursor.Local; + } + else + { + this.currentState.defaultCursor = DefaultCursor.Global; + } + + this.NotifyObservers(); + } + } + + /// + /// Filestream Non-Transacted Access setting for database. + /// + [Category("Category_Filestream"), + DisplayNameAttribute("Property_FilestreamNonTransactedAccess")] + public FilestreamNonTransactedAccessType FilestreamNonTransactedAccess + { + get + { + return this.currentState.filestreamNonTransactedAccess; + } + set + { + this.currentState.filestreamNonTransactedAccess = value; + this.NotifyObservers(); + } + } + + /// + /// Filestream share directory name. + /// + [Category("Category_Filestream"), + DisplayNameAttribute("Property_FilestreamDirectoryName")] + public string FilestreamDirectoryName + { + get + { + return this.currentState.filestreamDirectoryName; + } + set + { + this.currentState.filestreamDirectoryName = value; + this.NotifyObservers(); + } + } + + /// + /// Auto-close + /// + [Category("Category_Automatic"), + DisplayNameAttribute("Property_AutoClose")] + public bool AutoClose + { + get + { + return this.currentState.autoClose; + } + set + { + this.currentState.autoClose = value; + this.NotifyObservers(); + } + } + + /// + /// Auto-shrink + /// + [Category("Category_Automatic"), + DisplayNameAttribute("Property_AutoShrink")] + public bool AutoShrink + { + get + { + return this.currentState.autoShrink; + } + set + { + this.currentState.autoShrink = value; + this.NotifyObservers(); + } + } + + /// + /// Auto-create statistics + /// + [Category("Category_Automatic"), + DisplayNameAttribute("Property_AutoCreateStatistics")] + public bool AutoCreateStatistics + { + get + { + return this.currentState.autoCreateStatistics; + } + set + { + this.currentState.autoCreateStatistics = value; + this.NotifyObservers(); + } + } + + /// + /// Auto-update statistics + /// + [Category("Category_Automatic"), + DisplayNameAttribute("Property_AutoCreateStatisticsIncremental")] + public bool AutoCreateStatisticsIncremental + { + get + { + return this.currentState.autoCreateStatisticsIncremental; + } + set + { + this.currentState.autoCreateStatisticsIncremental = value; + this.NotifyObservers(); + } + } + + /// + /// Auto-update statistics + /// + [Category("Category_Automatic"), + DisplayNameAttribute("Property_AutoUpdateStatistics")] + public bool AutoUpdateStatistics + { + get + { + return this.currentState.autoUpdateStatistics; + } + set + { + this.currentState.autoUpdateStatistics = value; + this.NotifyObservers(); + } + } + + /// + /// Use ANSI Null defaults + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_ANSINullsDefault")] + public bool AnsiNullDefault + { + get + { + return this.currentState.ansiNullDefault; + } + set + { + this.currentState.ansiNullDefault = value; + this.NotifyObservers(); + } + } + + /// + /// Use ANSI Nulls + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_ANSINulls")] + public bool AnsiNulls + { + get + { + return this.currentState.ansiNulls; + } + set + { + this.currentState.ansiNulls = value; + this.NotifyObservers(); + } + } + + /// + /// Whether quoted identifiers are enabled + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_QuotedIdentifier")] + public bool QuotedIdentifier + { + get + { + return this.currentState.quotedIdentifier; + } + set + { + this.currentState.quotedIdentifier = value; + this.NotifyObservers(); + } + } + + /// + /// Whether recursive triggers are enabled + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_RecursiveTriggers")] + public bool RecursiveTriggers + { + get + { + return this.currentState.recursiveTriggers; + } + set + { + this.currentState.recursiveTriggers = value; + this.NotifyObservers(); + } + } + + /// + /// Database compatibility level + /// + [Browsable(false)] + public CompatibilityLevel DatabaseCompatibilityLevel + { + get + { + return this.currentState.databaseCompatibilityLevel; + } + set + { + this.currentState.databaseCompatibilityLevel = value; + this.NotifyObservers(); + } + } + + /// + /// Whether full-text indexing is enabled + /// + [Browsable(false)] + public bool FullTextIndexing + { + get + { + return this.currentState.fullTextIndexingEnabled; + } + set + { + this.currentState.fullTextIndexingEnabled = value; + this.NotifyObservers(); + } + } + + /// + /// the set of prototype filegroups associated with this prototype database + /// + [Browsable(false)] + public virtual IList Filegroups + { + get + { + return this.filegroups; + } + } + + /// + /// The set of prototype files associated with this prototype database + /// + [Browsable(false)] + public virtual IList Files + { + get + { + return this.files; + } + } + + /// + /// The default filegroup + /// + [Browsable(false)] + public FilegroupPrototype DefaultFilegroup + { + get + { + FilegroupPrototype result = null; + + foreach (FilegroupPrototype filegroup in Filegroups) + { + if (filegroup.IsDefault && + !(filegroup.IsFileStream || filegroup.IsMemoryOptimized)) + { + result = filegroup; + break; + } + } + + return result; + } + } + + /// + /// The default filestream filegroup + /// + [Browsable(false)] + public FilegroupPrototype DefaultFileStreamFilegroup + { + get + { + FilegroupPrototype result = null; + + foreach (FilegroupPrototype filegroup in Filegroups) + { + if (filegroup.IsDefault && filegroup.IsFileStream) + { + result = filegroup; + break; + } + } + + return result; + } + } + + /// + /// The default memory optimized filegroup + /// + [Browsable(false)] + public FilegroupPrototype DefaultMemoryOptimizedFilegroup + { + get + { + FilegroupPrototype result = null; + + foreach (FilegroupPrototype filegroup in Filegroups) + { + if (filegroup.IsDefault && filegroup.IsMemoryOptimized) + { + result = filegroup; + break; + } + } + + return result; + } + } + + /// + /// Whether the database exists on the server + /// + [Browsable(false)] + public bool Exists + { + get + { + return this.existingDatabase; + } + + set + { + this.existingDatabase = value; + } + } + + /// + /// The name of the database on the server + /// + [Browsable(false)] + public string OriginalName + { + get + { + return this.originalState.name; + } + } + + /// + /// The number of log files defined for the database + /// + [Browsable(false)] + public int NumberOfLogFiles + { + get + { + return this.numberOfLogFiles; + } + set + { + this.numberOfLogFiles = value; + } + } + + + /// + /// Whether the server version supports per-database collation + /// + [Browsable(false)] + public bool IsCollationSupported + { + get + { + // per database collation was a new feature in SQL Server 2000 (version 8) + return (7 < this.serverVersion.Major); + } + } + + // $FUTURE: 6/25/2004-stevetw Mirroring properties should be moved to + // a Yukon-specific subclass + + /// + /// Mirror Safety level + /// + [Browsable(false)] + public MirroringSafetyLevel MirrorSafetyLevel + { + get + { + return this.currentState.mirrorSafetyLevel; + } + set + { + currentState.mirrorSafetyLevel = value; + this.NotifyObservers(); + } + } + + /// + /// Mirror Witness + /// + [Browsable(false)] + public string MirrorWitness + { + get + { + return this.currentState.witnessServer; + } + set + { + currentState.witnessServer = value; + this.NotifyObservers(); + } + } + + // $FUTURE: 6/29/2004-stevetw Make sure version checks use this property, + // not explicit comparisons against the server major version + /// + /// Whether the server is Yukon or later + /// + [Browsable(false)] + protected bool IsYukonOrLater + { + get + { + return (9 <= this.serverVersion.Major); + } + } + + [Browsable(false)] + public ServerVersion ServerVersion + { + get + { + return this.serverVersion; + } + } + + [Browsable(false)] + public Microsoft.SqlServer.Management.Common.DatabaseEngineType DatabaseEngineType + { + get + { + return this.databaseEngineType; + } + } + + /// + /// Whether filestream is enabled or not. + /// + [Browsable(false)] + public bool IsFilestreamEnabled + { + get + { + return this.isFilestreamEnabled; + } + } + + + #endregion + + private StringCollection GetDatabaseDefaultInitFields(Server server) + { + StringCollection databaseDefaultInitFields; + if (context.IsNewObject) + { + databaseDefaultInitFields = server.GetPropertyNames(typeof(Database), server.DatabaseEngineEdition); + } + else + { + string databaseName = context.GetDocumentPropertyString("database"); + databaseDefaultInitFields = server.GetPropertyNames(typeof(Database), this.context.Server.Databases[databaseName].DatabaseEngineEdition); + } + + //AvailabilityGroupName throws exception for Contained Authentication + //and at the same time is not required in the Database Properties UI. + if (databaseDefaultInitFields.Contains("AvailabilityGroupName")) + { + databaseDefaultInitFields.Remove("AvailabilityGroupName"); + } + + return databaseDefaultInitFields; + } + + private StringCollection GetUIDataFileProperties(Server server) + { + //VSTS 404074 + //In LPU Scenario, db properties dialog failing with permission exception since it tries to execute DBCC showfilestats command and failing with + //an exception. In order to improve DB properties dialog performance, a single query gets executed to fetch the information of all properties related to all the files. This resulted in executing DBCC show filestats which an LPU doesnt have privileges to execute. + //DBCC Showfilestats is executed to fetch UsedSpace and Available Space. These properties are not used anywhere in the dialog. + //Removing the UsedSpace and AvailableSpace properties from the default init fields list for the data files will able to launch the dialog + //properly. + + StringCollection dataFileFields = new StringCollection(); + + dataFileFields.AddRange(new string[] { "IsPrimaryFile", "GrowthType", "FileName", "Size", "MaxSize", "Name", "ID", "Urn", "Growth" }); + + if (Utils.IsYukonOrAbove(server)) + { + dataFileFields.AddRange(new string[] { "IsReadOnlyMedia", "IsReadOnly", "IsOffline", "IsSparse" }); + } + + return dataFileFields; + } + + private StringCollection GetUILogFileProperties(Server server) + { + StringCollection logFileFields = new StringCollection(); + + logFileFields.AddRange(new string[] { "UsedSpace", "GrowthType", "FileName", "Size", "MaxSize", "Name", "ID" + , "Urn", "Growth" }); + + if (Utils.IsYukonOrAbove(server)) + { + logFileFields.AddRange(new string[] { "IsReadOnlyMedia", "IsReadOnly", "IsOffline", "IsSparse" }); + } + + return logFileFields; + } + + /// + /// Creates an instance of DatabasePrototype + /// + /// + public DatabasePrototype(CDataContainer context) + { + this.context = context; + this.serverVersion = context.Server.ConnectionContext.ServerVersion; + this.databaseEngineType = context.Server.DatabaseEngineType; + this.isFilestreamEnabled = Utils.FilestreamEnabled(this.context.Server); + this.files = new List(); + this.filegroups = new List(); + this.removedFilegroups = new List(); + this.removedFiles = new List(); + this.numberOfLogFiles = 0; + this.EditionToCreate = DatabaseEngineEdition.Unknown; + + StringCollection databaseDefaultInitFields = this.GetDatabaseDefaultInitFields(this.context.Server); + context.Server.SetDefaultInitFields(typeof(Database), databaseDefaultInitFields); + context.Server.SetDefaultInitFields(typeof(DataFile), this.GetUIDataFileProperties(this.context.Server)); + context.Server.SetDefaultInitFields(typeof(LogFile), this.GetUILogFileProperties(this.context.Server)); + context.Server.SetDefaultInitFields(typeof(FileGroup), true); + + if (!context.IsNewObject) + { + string databaseName = context.GetDocumentPropertyString("database"); + this.LoadDefinition(databaseName); + } + else + { + try + { + try + { + //First try to get the properties, if attempt fails get + //only the minimal set of properties by setting the DefaultInitFields value to false + this.originalState = new DatabaseData(context, "model"); + } + catch (Exception) + { + //Now try again with the optimized(DefaultInitFields set to false) properties + context.Server.SetDefaultInitFields(typeof(Database), false); + this.originalState = new DatabaseData(context, "model"); + //Set the DefaultInitFields to its original value + context.Server.SetDefaultInitFields(typeof(Database), databaseDefaultInitFields); + } + this.originalState.owner = String.Empty; + } + catch (Exception) + { + //Set the DefaultInitFields to its original value + context.Server.SetDefaultInitFields(typeof(Database), databaseDefaultInitFields); + this.originalState = new DatabaseData(context); + } + + // New database should not inherit ReadOnly from model database (Fix TFS 885072) + this.originalState.isReadOnly = false; + this.originalState.name = String.Empty; + this.currentState = this.originalState.Clone(); + this.existingDatabase = false; + this.currentState = this.originalState.Clone(); + //this value should set to false(it is true when it gets here due model db) + this.originalState.isSystemDB = false; + } + } + + public void Initialize() + { + if (!this.Exists && !this.HideFileSettings) + { + this.ResetFilegroups(); + this.ResetFiles(); + } + } + + /// + /// Return the prototype database to default values + /// + public void Clear() + { + this.currentState = this.originalState.Clone(); + + this.ResetFilegroups(); + this.ResetFiles(); + } + + /// + /// Set prototype database state to match the state of the existing database + /// + /// The name of the database whose definition we are loading + public void LoadDefinition(string newName) + { + var sw = new Stopwatch(); + sw.Start(); + this.originalState = new DatabaseData(context, newName); + sw.Stop(); + Trace.TraceInformation("Time to construct DatabaseData: {0}", sw.ElapsedMilliseconds); + this.currentState = this.originalState.Clone(); + this.existingDatabase = true; + + this.LoadFilesAndFilegroups(); + } + + /// + /// Create or Alter the database on the server + /// + /// The control through which UI interactions are to be marshalled + /// The SMO database object that was created or modified + //public virtual Database ApplyChanges(Control marshallingControl) + //{ + // Database db = null; + + // if (this.ChangesExist()) + // { + // bool scripting = (SqlExecutionModes.CaptureSql == this.context.Server.ConnectionContext.SqlExecutionModes); + // bool mustRollback = false; + + // db = this.GetDatabase(); + + // // Other connections will need to be closed if the following is true + // // 1) The database already exists, AND + // // 2) We are not scripting, AND + // // a) read-only state is changing, OR + // // b) user-access is changing, OR + // // c) date correlation optimization is changing + + // // There are also additional properties we don't currently expose that also need + // // to be changed when no one else is connected: + + // // d) emergency, OR + // // e) offline, (moving to offline - obviously not necessary to check when moving from offline) + // // f) read committed snapshot + + // if (this.Exists && !scripting && + // ((this.currentState.isReadOnly != this.originalState.isReadOnly) || + // (this.currentState.filestreamDirectoryName != this.originalState.filestreamDirectoryName) || + // (this.currentState.filestreamNonTransactedAccess != this.originalState.filestreamNonTransactedAccess) || + // (this.currentState.restrictAccess != this.originalState.restrictAccess) || + // (this.currentState.dateCorrelationOptimization != this.originalState.dateCorrelationOptimization) || + // (this.currentState.isReadCommittedSnapshotOn != this.originalState.isReadCommittedSnapshotOn))) + // { + + // // If the user lacks permissions to enumerate other connections (e.g. the user is not SA) + // // assume there is a connection to close. This occasionally results in unnecessary + // // prompts, but the database alter does succeed this way. If we assume no other connections, + // // then we get errors when other connections do exist. + // int numberOfOpenConnections = 1; + + // try + // { + // numberOfOpenConnections = db.ActiveConnections; + // } + // catch (Exception ex) + // { + // // do nothing - the user doesn't have permission to check whether there are active connections + // STrace.LogExCatch(ex); + // } + + // if (0 < numberOfOpenConnections) + // { + // DialogResult result = (DialogResult)marshallingControl.Invoke(new SimplePrompt(this.PromptToCloseConnections)); + // if (result == DialogResult.No) + // { + // throw new OperationCanceledException(); + // } + + // mustRollback = true; + // } + // } + + // // create/alter filegroups + // foreach (FilegroupPrototype filegroup in Filegroups) + // { + // filegroup.ApplyChanges(db); + // } + + // // create/alter files + // foreach (DatabaseFilePrototype file in Files) + // { + // file.ApplyChanges(db); + // } + + // // set the database properties + // this.SaveProperties(db); + + // // alter the database to match the properties + // if (!this.Exists) + // { + // // this is to prevent silent creation of db behind users back + // // eg. the alter statements to set properties fail when filestream directory name is invalid bug #635273 + // // but create database statement already succeeded + + + // // if filestream directory name has been set by user validate it + // if (!string.IsNullOrEmpty(this.FilestreamDirectoryName)) + // { + // // check is filestream directory name is valid + // if (!FileNameHelper.IsValidFilename(this.FilestreamDirectoryName)) + // { + // string message = String.Format(System.Globalization.CultureInfo.InvariantCulture, + // Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR.Error_InvalidDirectoryName, + // this.FilestreamDirectoryName); + // throw new ArgumentException(message); + // } + + // int rowCount = 0; + // try + // { + + // //if filestream directory name already exists in this instance + // string sqlFilestreamQuery = string.Format(SmoApplication.DefaultCulture, "SELECT * from sys.database_filestream_options WHERE directory_name = {0}", + // SqlSmoObject.MakeSqlString(this.FilestreamDirectoryName)); + // DataSet filestreamResults = this.context.ServerConnection.ExecuteWithResults(sqlFilestreamQuery); + // rowCount = filestreamResults.Tables[0].Rows.Count; + // } + // catch + // { + // // lets not do anything if there is an exception while validating + // // this is will prevent bugs in validation logic from preventing creation of valid databases + // // if database settings are invalid create database tsql statement will fail anyways + // } + // if (rowCount != 0) + // { + // string message = String.Format(System.Globalization.CultureInfo.InvariantCulture, + // Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR.Error_ExistingDirectoryName, + // this.FilestreamDirectoryName, this.Name); + // throw new ArgumentException(message); + + // } + // } + + // db.Create(); + // } + // else + // { + // TerminationClause termination = + // mustRollback ? + // TerminationClause.RollbackTransactionsImmediately : + // TerminationClause.FailOnOpenTransactions; + + // db.Alter(termination); + // } + + // // FIXED-SQLBUDefectTracking-20006074-2005/07/11-stevetw + // // have to explicitly set the default filegroup after the database has been created + // // Also bug 97696 + // foreach (FilegroupPrototype filegroup in Filegroups) + // { + // if (filegroup.IsDefault && !(filegroup.Exists && db.FileGroups[filegroup.Name].IsDefault)) + // { + // if ((filegroup.IsFileStream || filegroup.IsMemoryOptimized) && + // Utils.IsKatmaiOrLater(db.ServerVersion.Major)) + // { + // db.SetDefaultFileStreamFileGroup(filegroup.Name); + // } + // else + // { + // db.SetDefaultFileGroup(filegroup.Name); + // } + // } + // } + + // FilegroupPrototype fg = null; + // // drop should happen after alter so that if we delete default filegroup it makes another default before deleting. + // // drop removed files and filegroups for existing databases + // if (this.Exists) + // { + // foreach (FilegroupPrototype filegroup in this.removedFilegroups) + // { + // // In case all filegroups are removed from filestream . memory optimized one default will remain and that has to be the last. + // if ((filegroup.IsFileStream || filegroup.IsMemoryOptimized) && + // db.FileGroups[filegroup.Name].IsDefault) + // { + // fg = filegroup; + // } + // else + // { + // filegroup.ApplyChanges(db); + // } + // } + + // if (fg != null) + // { + // fg.ApplyChanges(db); + // } + + // foreach (DatabaseFilePrototype file in this.removedFiles) + // { + // file.ApplyChanges(db); + // } + // } + + // // SnapshotIsolation and Owner cannot be set during scripting time for a newly creating database + // // and even in capture mode. Hence this check has been made + // if (db.State == SqlSmoState.Existing) + // { + // if (this.originalState.allowSnapshotIsolation != this.currentState.allowSnapshotIsolation) + // { + // db.SetSnapshotIsolation(this.currentState.allowSnapshotIsolation); + // } + + // // Set the database owner. Note that setting owner is an "immediate" operation that + // // has to happen after the database is created. There is a SMO limitation where SMO + // // throws an exception if immediate operations such as SetOwner() are attempted on + // // an object that doesn't exist on the server. + + // if ((this.Owner.Length != 0) && + // (this.currentState.owner != this.originalState.owner)) + // { + // // + // // bug 20000092 says the error message is confusing if this fails, so + // // wrap this and throw a nicer error on failure. + // // + // try + // { + // db.SetOwner(this.Owner, false); + // } + // catch (Exception ex) + // { + // SqlException sqlException = CUtils.GetSqlException(ex); + + // if ((null != sqlException) && CUtils.IsPermissionDeniedException(sqlException)) + // { + // STrace.LogExCatch(ex); + // throw new Exception(SRError.SetOwnerFailed(this.Owner), ex); + // } + // else + // { + // throw; + // } + // } + // } + // } + // } + + // return db; + //} + + /// + /// Purge Query Store Data. + /// + public void PurgeQueryStoreData() + { + Database db = this.GetDatabase(); + + // db.QueryStoreOptions.PurgeQueryStoreData(); + } + + /// + /// Add a filegroup prototype to the set of filegroup prototypes + /// + /// The filegroup prototype to add + public void Add(FilegroupPrototype filegroup) + { + if ((filegroup != null) && !filegroups.Contains(filegroup)) + { + // add the filegroup to the set + if (0 == String.Compare("PRIMARY", filegroup.Name, StringComparison.OrdinalIgnoreCase)) + { + filegroups.Insert(0, filegroup); + } + else + { + filegroups.Add(filegroup); + } + + // if the new filegroup is the default filegroup, update the other filegroups + if (filegroup.IsDefault) + { + SetNewDefaultFileGroup(filegroup); + } + + // subscribe to default changed events on the filegroup + filegroup.OnFileGroupDefaultChangedHandler += new FileGroupDefaultChangedEventHandler(OnFileGroupDefaultChanged); + this.NotifyObservers(); + } + } + + /// + /// Add a file prototype to the set of file prototypes + /// + /// The file prototype to add + public void Add(DatabaseFilePrototype file) + { + if ((file != null) && !this.files.Contains(file)) + { + if (file.IsPrimaryFile) + { + this.files.Insert(0, file); + } + else + { + this.files.Add(file); + } + + if (FileType.Log == file.DatabaseFileType) + { + ++this.numberOfLogFiles; + } + + this.NotifyObservers(); + } + } + + /// + /// Remove a filegroup prototype from the set of filegroup prototypes + /// + /// The filegroup prototype to removed + public void Remove(FilegroupPrototype filegroup) + { + if ((filegroup != null) && filegroups.Contains(filegroup)) + { + if (!(filegroup.IsFileStream || filegroup.IsMemoryOptimized)) + { + if (filegroup.IsDefault) + { + FilegroupPrototype primary = this.filegroups[0]; + primary.IsDefault = true; + } + + filegroup.NotifyFileGroupDeleted(this.DefaultFilegroup); + } + else + { + if (filegroup.IsDefault) + { + foreach (FilegroupPrototype fgp in filegroups) + { + if ((fgp.IsFileStream || fgp.IsMemoryOptimized) + && fgp != filegroup) + { + fgp.IsDefault = true; + break; + } + } + } + filegroup.NotifyFileGroupDeleted(this.DefaultFileStreamFilegroup); + } + filegroups.Remove(filegroup); + + if (filegroup.Exists) + { + this.removedFilegroups.Add(filegroup); + filegroup.Removed = true; + } + + this.NotifyObservers(); + } + } + + /// + /// Remove a file prototype from the set of file prototypes + /// + /// The file prototype to remove + public void Remove(DatabaseFilePrototype file) + { + if ((file != null) && files.Contains(file)) + { + if (file.IsPrimaryFile) + { + throw new InvalidOperationException("unexpected removal of the primary data file"); + } + + if ((1 == this.numberOfLogFiles) && (FileType.Log == file.DatabaseFileType)) + { + throw new InvalidOperationException("Unexpected removal of the last log file."); + } + + files.Remove(file); + + if (file.Exists) + { + this.removedFiles.Add(file); + file.Removed = true; + } + + if (FileType.Log == file.DatabaseFileType) + { + --this.numberOfLogFiles; + } + + this.NotifyObservers(); + } + } + + /// + /// Reset files and filegroups to match the existing database + /// + private void LoadFilesAndFilegroups() + { + this.allowNotifications = false; + + this.files.Clear(); + this.filegroups.Clear(); + this.removedFiles.Clear(); + this.removedFilegroups.Clear(); + + //Azure doesn't support files/filegroups so just exit early after clearing the current settings + if (this.context.Server.ServerType == DatabaseEngineType.SqlAzureDatabase) + { + return; + } + + Database database = context.Server.Databases[this.Name]; + + // we can only get the files and filegroups if the db can be opened. + // dbs that are in a restoring state, for example, can't be asked for this info. + //if (!DBPropSheet.IsAccessible(database)) + //{ + // return; + //} + + // Dialog load time optimization: tell SMO to initialize all Filegroups and all Files + // using one single query (instead of 1/query for each File collection in each Filegroup) + + // database.InitChildLevel("FileGroup", null, false); + // database.InitChildLevel("FileGroup/File", null, false); + + foreach (FileGroup filegroup in database.FileGroups) + { + FilegroupPrototype filegroupPrototype = new FilegroupPrototype(this, + filegroup.Name, + filegroup.ReadOnly, + filegroup.IsDefault, + filegroup.FileGroupType, + true); + + this.Add(filegroupPrototype); + + try + { + foreach (DataFile datafile in filegroup.Files) + { + DatabaseFilePrototype file = new DatabaseFilePrototype(this, filegroupPrototype, datafile); + this.Add(file); + } + } + catch (ExecutionFailureException ex) + { + // do nothing + + } + } + + this.numberOfLogFiles = 0; + + // $ISSUE: SQL_BU_Defect_Tracking-290364-6/25/2004-stevetw + // Remove the check for snapshots when SMO supports getting + // snapshot log file size info. + bool isSnapshot = false; + try + { + if (database.IsSupportedProperty("IsDatabaseSnapshot")) + { + isSnapshot = this.IsYukonOrLater && database.IsDatabaseSnapshot; + } + } + catch (Exception ex) + { + SqlException sqlException = CUtils.GetSqlException(ex); + if ((null != sqlException) && CUtils.IsPermissionDeniedException(sqlException)) + { + // do nothing + } + else + { + throw; + } + } + + if (!isSnapshot) + { + try + { + foreach (LogFile logfile in database.LogFiles) + { + DatabaseFilePrototype logfilePrototype = new DatabaseFilePrototype(this, logfile); + this.Add(logfilePrototype); + } + } + catch (ExecutionFailureException ex) + { + // do nothing + } + } + + this.allowNotifications = true; + this.NotifyObservers(); + } + + /// + /// Commit property changes to the database + /// + /// The database whose properties we are changing + protected virtual void SaveProperties(Database db) + { + if (!this.Exists || (db.DatabaseOptions.UserAccess != this.currentState.restrictAccess)) + { + db.DatabaseOptions.UserAccess = this.currentState.restrictAccess; + } + + if (!this.Exists || (db.DatabaseOptions.CloseCursorsOnCommitEnabled != this.CloseCursorOnCommit)) + { + db.DatabaseOptions.CloseCursorsOnCommitEnabled = this.CloseCursorOnCommit; + } + + if (db.IsSupportedProperty("LocalCursorsDefault")) + { + bool localCursorsDefault = (this.currentState.defaultCursor == DefaultCursor.Local); + if (!this.Exists || (db.DatabaseOptions.LocalCursorsDefault != localCursorsDefault)) + { + db.DatabaseOptions.LocalCursorsDefault = localCursorsDefault; + } + } + + if (db.IsSupportedProperty("AutoClose")) + { + if (!this.Exists || (db.DatabaseOptions.AutoClose != this.AutoClose)) + { + db.DatabaseOptions.AutoClose = this.AutoClose; + } + } + + if (!this.Exists || (db.DatabaseOptions.AutoShrink != this.AutoShrink)) + { + db.DatabaseOptions.AutoShrink = this.AutoShrink; + } + + if (!this.Exists || (db.DatabaseOptions.AutoCreateStatistics != this.AutoCreateStatistics)) + { + db.DatabaseOptions.AutoCreateStatistics = this.AutoCreateStatistics; + } + + if (db.IsSupportedProperty("AutoCreateIncrementalStatisticsEnabled") && + (!this.Exists || db.DatabaseOptions.AutoCreateStatisticsIncremental != this.AutoCreateStatisticsIncremental)) + { + db.DatabaseOptions.AutoCreateStatisticsIncremental = this.AutoCreateStatisticsIncremental; + } + + if (!this.Exists || (db.DatabaseOptions.AutoUpdateStatistics != this.AutoUpdateStatistics)) + { + db.DatabaseOptions.AutoUpdateStatistics = this.AutoUpdateStatistics; + } + + if (!this.Exists || (db.DatabaseOptions.AnsiNullDefault != this.AnsiNullDefault)) + { + db.DatabaseOptions.AnsiNullDefault = this.AnsiNullDefault; + } + + if (!this.Exists || (db.DatabaseOptions.AnsiNullsEnabled != this.AnsiNulls)) + { + db.DatabaseOptions.AnsiNullsEnabled = this.AnsiNulls; + } + + if (!this.Exists || (db.DatabaseOptions.QuotedIdentifiersEnabled != this.QuotedIdentifier)) + { + db.DatabaseOptions.QuotedIdentifiersEnabled = this.QuotedIdentifier; + } + + if (!this.Exists || (db.DatabaseOptions.RecursiveTriggersEnabled != this.RecursiveTriggers)) + { + db.DatabaseOptions.RecursiveTriggersEnabled = this.RecursiveTriggers; + } + + if (db.IsSupportedProperty("RecoveryModel")) + { + if (!this.Exists || (db.DatabaseOptions.RecoveryModel != this.RecoveryModel)) + { + db.DatabaseOptions.RecoveryModel = this.RecoveryModel; + } + } + + // user has to be a sysadmin to set full text indexing settings or compatibility level + // Azure SQL DB doesn't have a fixed server role for sysadmin + if (db.Parent.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin) || db.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) + { + // Full-text indexing will always be enabled in Katmai + if (this.serverVersion.Major <= 9 && db.Parent.Information.IsFullTextInstalled && + (!this.Exists || (db.IsFullTextEnabled != this.FullTextIndexing))) + { + db.IsFullTextEnabled = this.FullTextIndexing; + } + + if (!this.Exists || (db.CompatibilityLevel != this.DatabaseCompatibilityLevel)) + { + db.CompatibilityLevel = this.DatabaseCompatibilityLevel; + } + } + + // $FUTURE: 6/25/2004-stevetw Consider moving mirroring property sets + // to a Yukon-specific subclass + if (db.IsSupportedProperty("IsMirroringEnabled")) + { + if (this.Exists && db.IsMirroringEnabled && (db.MirroringSafetyLevel != MirrorSafetyLevel)) + { + db.MirroringSafetyLevel = this.MirrorSafetyLevel; + } + + if (this.Exists && db.IsMirroringEnabled && (string.Compare(db.MirroringWitness, this.MirrorWitness, StringComparison.OrdinalIgnoreCase) != 0)) + { + if (this.MirrorWitness.Length == 0) // we want to remove it + { + db.ChangeMirroringState(MirroringOption.RemoveWitness); + } + else + { + db.MirroringWitness = this.MirrorWitness; + } + } + } + + if (db.IsSupportedProperty("FilestreamDirectoryName")) + { + if ((!this.Exists && !string.IsNullOrEmpty(this.FilestreamDirectoryName)) || + (this.Exists && string.Compare(db.FilestreamDirectoryName, this.FilestreamDirectoryName, StringComparison.OrdinalIgnoreCase) != 0)) + { + db.FilestreamDirectoryName = this.FilestreamDirectoryName; + } + + if ((!this.Exists && this.FilestreamNonTransactedAccess != FilestreamNonTransactedAccessType.Off) || + (this.Exists && db.FilestreamNonTransactedAccess != this.FilestreamNonTransactedAccess)) + { + db.FilestreamNonTransactedAccess = this.FilestreamNonTransactedAccess; + } + } + } + + /// + /// Will calling ApplyChanges do anything? + /// + /// True if there are changes to apply, false otherwise + public bool ChangesExist() + { + bool result = + !this.Exists || + this.FileChangesExist() || + this.FileGroupChangesExist() || + !this.originalState.HasSameValueAs(this.currentState); + + return result; + } + + /// + /// Are there any changes associated with filegroups? + /// + /// True if there are filegroup changes, false otherwise + private bool FileGroupChangesExist() + { + bool result = false; + + if (this.removedFilegroups.Count != 0) + { + result = true; + } + else + { + foreach (FilegroupPrototype fgp in this.filegroups) + { + if (fgp.ChangesExist()) + { + result = true; + break; + } + } + } + + return result; + } + + /// + /// Are there any changes associated with files + /// + /// True if there are file changes, false otherwise + private bool FileChangesExist() + { + bool result = false; + + if (this.removedFiles.Count != 0) + { + result = true; + } + else + { + foreach (DatabaseFilePrototype fp in this.files) + { + if (fp.ChangesExist()) + { + result = true; + break; + } + } + } + + return result; + } + + /// + /// Release current filegroups and create a new default filegroup prototype + /// + private void ResetFilegroups() + { + this.allowNotifications = false; + + // unhook events for all the old filegroups + foreach (FilegroupPrototype oldFilegroup in this.filegroups) + { + oldFilegroup.OnFileGroupDefaultChangedHandler -= new FileGroupDefaultChangedEventHandler(OnFileGroupDefaultChanged); + } + + // create default filegroup + string defaultFilegroupName = "PRIMARY"; + bool isDefault = true; + bool isReadOnly = false; + FilegroupPrototype filegroup = new FilegroupPrototype(this, defaultFilegroupName, isReadOnly, isDefault, FileGroupType.RowsFileGroup, false); + + filegroup.OnFileGroupDefaultChangedHandler += new FileGroupDefaultChangedEventHandler(OnFileGroupDefaultChanged); + + // set filegroup collection to contain default filegroup + filegroups.Clear(); + this.Add(filegroup); + + this.allowNotifications = true; + this.NotifyObservers(); + } + + /// + /// Release current files and create new default prototype files + /// + private void ResetFiles() + { + this.allowNotifications = false; + + // create prototype data and log files + DatabaseFilePrototype dataPrototype = new DatabaseFilePrototype(context, this, FileType.Data); + DatabaseFilePrototype logPrototype = new DatabaseFilePrototype(context, this, FileType.Log, "_log"); + + dataPrototype.IsPrimaryFile = true; + + // add prototype files to the set of files + files.Clear(); + files.Add(dataPrototype); + files.Add(logPrototype); + + this.numberOfLogFiles = 1; + + this.allowNotifications = true; + this.NotifyObservers(); + } + + /// + /// Event handler for changes to file group default-ness + /// + /// The object that changed + /// EventArgs describing the change + private void OnFileGroupDefaultChanged(object sender, BooleanValueChangedEventArgs e) + { + // if the default-ness has changed from non-default to default, + // iterate through the set of filegroups changing all the filegroups + // that are not the sender to non-default. + if (e.NewValue) + { + FilegroupPrototype newDefault = sender as FilegroupPrototype; + + SetNewDefaultFileGroup(newDefault); + } + } + + /// + /// Makes all the filegroups that a not the new default filegroup not default + /// + /// The new default filegroup + private void SetNewDefaultFileGroup(FilegroupPrototype newDefault) + { + if (newDefault != null) + { + foreach (FilegroupPrototype prototype in this.filegroups) + { + //Make isDefault property of all the other filegroups of the same filegrouptype to false. + if ((prototype != newDefault) && prototype.IsDefault && + prototype.FileGroupType == newDefault.FileGroupType) + { + prototype.IsDefault = false; + } + } + } + } + + /// + /// Appends a state flag to a string reprenting a combination of flags + /// + /// The flag combination, such as "OFFLINE | RECOVERING" + /// The flag to append, such as "AUTO_CLOSED" + /// The new combination of flags, such as "OFFLINE | RECOVERING | AUTO_CLOSED" + private string AppendState(string fullState, string stateFlag) + { + string result = null; + + if (fullState == null) + { + result = stateFlag; + } + else + { + result = String.Format( + System.Globalization.CultureInfo.InvariantCulture, + "{0} | {1}", + fullState, + stateFlag); + } + + return result; + } + + // private delegate DialogResult SimplePrompt(); + + /// + /// Show prompt dialog to close other open connections + /// + /// The prompt result + //private DialogResult PromptToCloseConnections() + //{ + // ResourceManager resourceManager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabaseAlreadyExistsException).Assembly); + // ExceptionMessageBox emb = new ExceptionMessageBox(); + // emb.Buttons = ExceptionMessageBoxButtons.YesNo; + // emb.Symbol = ExceptionMessageBoxSymbol.Question; + // emb.Caption = resourceManager.GetString("title.openConnectionsMustBeClosed"); + // emb.Message = new Exception(resourceManager.GetString("warning.openConnectionsMustBeClosed")); + // DialogResult result = emb.Show(null); + + // return result; + //} + + + protected Database GetDatabase() + { + Database result = null; + + // if we think we exist, get the SMO database object + if (this.Exists) + { + result = this.context.Server.Databases[this.originalState.name]; + if (result == null) + { + throw new Exception("Object does not exist"); + } + } + else + { + result = new Database(this.context.Server, this.Name, this.EditionToCreate); + } + + return result; + } + + /// + /// Property to access the observable event. + /// + internal event EventHandler Changed + { + add { this.observableChanged += value; } + remove { this.observableChanged -= value; } + } + + /// + /// Notify all observers that this object has changed. + /// + /// The object that changed + /// Hint for the notification, usually null + internal void NotifyObservers(object sender, EventArgs e) + { + if (this.allowNotifications && (this.observableChanged != null)) + { + this.observableChanged(sender, e); + } + } + + /// + /// Notify all observers that this object has changed. + /// + internal void NotifyObservers() + { + this.NotifyObservers(this, new EventArgs()); + } + + #region IDynamicValues Members + + public virtual TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + + TypeConverter.StandardValuesCollection result = null; + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype).GetAssembly()); + List standardValues = new List(); + + if (context.PropertyDescriptor.Name == "DefaultCursorDisplay") + { + standardValues.Add(manager.GetString("prototype.db.prop.defaultCursor.value.local")); + standardValues.Add(manager.GetString("prototype.db.prop.defaultCursor.value.global")); + } + else if (context.PropertyDescriptor.Name == "RestrictAccess") + { + standardValues.Add(manager.GetString("prototype.db.prop.restrictAccess.value.multiple")); + standardValues.Add(manager.GetString("prototype.db.prop.restrictAccess.value.single")); + standardValues.Add(manager.GetString("prototype.db.prop.restrictAccess.value.restricted")); + } + + if (standardValues.Count > 0) + { + result = new TypeConverter.StandardValuesCollection(standardValues); + } + + return result; + } + + #endregion + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype100.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype100.cs new file mode 100644 index 00000000..05d61eb0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype100.cs @@ -0,0 +1,315 @@ +// +// 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.ComponentModel; +using System.Resources; +// using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Diagnostics; + +// using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database properties for SqlServer 2008 + /// + [TypeConverter(typeof(DynamicValueTypeConverter))] + // [StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototype100 : DatabasePrototype90 + { + /// + /// Whether vardecimal compression is enabled on the server + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_VarDecimalEnabled")] + public bool VarDecimalEnabled + { + get + { + return this.currentState.varDecimalEnabled; + } + //there is no set for user database in katmai. By default it's true. + } + + /// + /// Whether database is encrypted or not + /// + [Category("Category_State"), + DisplayNameAttribute("Property_EncryptionEnabled")] + public bool EncryptionEnabled + { + get + { + return this.currentState.encryptionEnabled; + } + set + { + this.currentState.encryptionEnabled = value; + this.NotifyObservers(); + } + } + + /// + /// Honor Broker Priority + /// + [Category("Category_ServiceBroker"), + DisplayNameAttribute("Property_HonorBrokerPriority")] + public bool HonorBrokerPriority + { + get + { + return this.currentState.honorBrokerPriority; + } + } + + [Category("Category_DatabaseScopedConfigurations")] + [DisplayNameAttribute("Property_MaxDop")] + public int MaxDop + { + get + { + return this.currentState.maxDop; + } + set + { + this.currentState.maxDop = value; + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations")] + [DisplayNameAttribute("Property_MaxDopForSecondary")] + public int? MaxDopForSecondary + { + get + { + return this.currentState.maxDopForSecondary; + } + set + { + this.currentState.maxDopForSecondary = value; + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations"), + DisplayNameAttribute("Property_LegacyCardinalityEstimation")] + //TypeConverter(typeof(DatabaseScopedConfigurationOnOffTypes))] + public string LegacyCardinalityEstimationDisplay + { + get + { + return GetDatabaseScopedConfigDisplayText(this.currentState.legacyCardinalityEstimation); + } + set + { + this.currentState.legacyCardinalityEstimation = SetDatabaseScopedConfigHelper(value, forSecondary: false); + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations"), + DisplayNameAttribute("Property_LegacyCardinalityEstimationForSecondary")] + //TypeConverter(typeof(DatabaseScopedConfigurationOnOffTypes))] + public String LegacyCardinalityEstimationForSecondaryDisplay + { + get + { + return GetDatabaseScopedConfigDisplayText(this.currentState.legacyCardinalityEstimationForSecondary); + } + set + { + this.currentState.legacyCardinalityEstimationForSecondary = SetDatabaseScopedConfigHelper(value, forSecondary: true); + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations"), + DisplayNameAttribute("Property_ParameterSniffing")] + //TypeConverter(typeof(DatabaseScopedConfigurationOnOffTypes))] + public string ParameterSniffingDisplay + { + get + { + return GetDatabaseScopedConfigDisplayText(this.currentState.parameterSniffing); + } + set + { + this.currentState.parameterSniffing = SetDatabaseScopedConfigHelper(value, forSecondary: false); + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations"), + DisplayNameAttribute("Property_ParameterSniffingForSecondary")] + //TypeConverter(typeof(DatabaseScopedConfigurationOnOffTypes))] + public String ParameterSniffingForSecondaryDisplay + { + get + { + return GetDatabaseScopedConfigDisplayText(this.currentState.parameterSniffingForSecondary); + } + set + { + this.currentState.parameterSniffingForSecondary = SetDatabaseScopedConfigHelper(value, forSecondary: true); + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations"), + DisplayNameAttribute("Property_QueryOptimizerHotfixes")] + //TypeConverter(typeof(DatabaseScopedConfigurationOnOffTypes))] + public String QueryOptimizerHotfixesDisplay + { + get + { + return GetDatabaseScopedConfigDisplayText(this.currentState.queryOptimizerHotfixes); + } + set + { + this.currentState.queryOptimizerHotfixes = SetDatabaseScopedConfigHelper(value, forSecondary: false); + this.NotifyObservers(); + } + } + + [Category("Category_DatabaseScopedConfigurations"), + DisplayNameAttribute("Property_QueryOptimizerHotfixesForSecondary")] + //TypeConverter(typeof(DatabaseScopedConfigurationOnOffTypes))] + public String QueryOptimizerHotfixesForSecondaryDisplay + { + get + { + return GetDatabaseScopedConfigDisplayText(this.currentState.queryOptimizerHotfixesForSecondary); + } + set + { + this.currentState.queryOptimizerHotfixesForSecondary = SetDatabaseScopedConfigHelper(value, forSecondary: true); + this.NotifyObservers(); + } + } + + public DatabasePrototype100(CDataContainer context) : base(context) { } + + /// + /// Commit changes to the database + /// + /// The database whose properties we are changing + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + if (!this.Exists || this.originalState.encryptionEnabled != this.currentState.encryptionEnabled) + { + db.EncryptionEnabled = this.currentState.encryptionEnabled; + } + + // Check if we support database scoped configurations in this database. Since these were all added at the same time, + // only check if MaxDop is supported rather than each individual property. + if (db.IsSupportedProperty("MaxDop")) + { + if (!this.Exists || (db.MaxDop != this.MaxDop)) + { + db.MaxDop = this.MaxDop; + } + + if (!this.Exists || (db.MaxDopForSecondary != this.MaxDopForSecondary)) + { + db.MaxDopForSecondary = this.MaxDopForSecondary; + } + + if (!this.Exists || (db.LegacyCardinalityEstimation != this.currentState.legacyCardinalityEstimation)) + { + db.LegacyCardinalityEstimation = this.currentState.legacyCardinalityEstimation; + } + + if (!this.Exists || (db.LegacyCardinalityEstimationForSecondary != this.currentState.legacyCardinalityEstimationForSecondary)) + { + db.LegacyCardinalityEstimationForSecondary = this.currentState.legacyCardinalityEstimationForSecondary; + } + + if (!this.Exists || (db.ParameterSniffing != this.currentState.parameterSniffing)) + { + db.ParameterSniffing = this.currentState.parameterSniffing; + } + + if (!this.Exists || (db.ParameterSniffingForSecondary != this.currentState.parameterSniffingForSecondary)) + { + db.ParameterSniffingForSecondary = this.currentState.parameterSniffingForSecondary; + } + + if (!this.Exists || (db.QueryOptimizerHotfixes != this.currentState.queryOptimizerHotfixes)) + { + db.QueryOptimizerHotfixes = this.currentState.queryOptimizerHotfixes; + } + + if (!this.Exists || (db.QueryOptimizerHotfixesForSecondary != this.currentState.queryOptimizerHotfixesForSecondary)) + { + db.QueryOptimizerHotfixesForSecondary = this.currentState.queryOptimizerHotfixesForSecondary; + } + } + } + + #region Helper Methods + + /// + /// Gets the display text for a database scoped configuration setting. + /// + /// The database scoped configuration setting value. + /// A string from the resource manager representing the value. + private string GetDatabaseScopedConfigDisplayText(DatabaseScopedConfigurationOnOff onOffValue) + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype).GetAssembly()); + string result = null; + + switch (onOffValue) + { + case DatabaseScopedConfigurationOnOff.Off: + result = manager.GetString("prototype.db.prop.databasescopedconfig.value.off"); + break; + + case DatabaseScopedConfigurationOnOff.On: + result = manager.GetString("prototype.db.prop.databasescopedconfig.value.on"); + break; + + case DatabaseScopedConfigurationOnOff.Primary: + result = manager.GetString("prototype.db.prop.databasescopedconfig.value.primary"); + break; + } + + return result; + } + + /// + /// Translates a string to a database scoped configuration enum value for the set method. + /// + /// The display text. + /// Whether this is for a secondary in which case "PRIMARY" is allowable. + /// The database scoped configuration enum value that matches the display text. + private DatabaseScopedConfigurationOnOff SetDatabaseScopedConfigHelper(string displayText, bool forSecondary) + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype).GetAssembly()); + + if (displayText == manager.GetString("prototype.db.prop.databasescopedconfig.value.off")) + { + return DatabaseScopedConfigurationOnOff.Off; + } + else if (displayText == manager.GetString("prototype.db.prop.databasescopedconfig.value.on") || !forSecondary) + { + return DatabaseScopedConfigurationOnOff.On; + } + else + { + return DatabaseScopedConfigurationOnOff.Primary; + } + } + + #endregion + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype110.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype110.cs new file mode 100644 index 00000000..2158aab1 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype110.cs @@ -0,0 +1,232 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +// using System.Drawing.Design; +using Microsoft.SqlServer.Management.Common; +// using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; + +// using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database properties for SqlServer 2011 + /// + [TypeConverter(typeof(DynamicValueTypeConverter))] + // [StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototype110 : DatabasePrototype100 //, ILanguageLcidWithConnectionInfo + { + /// + /// Database compatibility level + /// + [Browsable(false)] + public ContainmentType DatabaseContainmentType + { + get + { + return this.currentState.databaseContainmentType; + } + set + { + this.currentState.databaseContainmentType = value; + this.NotifyObservers(); + } + } + + [Category("Category_ContainedDatabases"), + DisplayNameAttribute("Property_DefaultFullTextLanguageLcid")] + public int DefaultFullTextLanguageLcid + { + get + { + return this.currentState.defaultFulltextLanguageLcid; + } + set + { + this.currentState.defaultFulltextLanguageLcid = value; + this.NotifyObservers(); + } + } + + //[ + //// Editor(typeof(DefaultLanguageEditor), typeof(UITypeEditor)), + //Category("Category_ContainedDatabases"), + //DisplayNameAttribute("Property_DefaultLanguage") + //] + //public LanguageChoice DefaultLanguage + //{ + // get + // { + // return LanguageUtils.GetLanguageChoiceAlias(this.context.Server, + // this.currentState.defaultLanguageLcid); + // } + // set + // { + // this.currentState.defaultLanguageLcid = value.lcid; + // this.NotifyObservers(); + // } + //} + + [Category("Category_ContainedDatabases"), + DisplayNameAttribute("Property_NestedTriggersEnabled")] + public bool NestedTriggersEnabled + { + get + { + return this.currentState.nestedTriggersEnabled; + } + set + { + this.currentState.nestedTriggersEnabled = value; + this.NotifyObservers(); + } + } + + [Category("Category_ContainedDatabases"), + DisplayNameAttribute("Property_TransformNoiseWords")] + public bool TransformNoiseWords + { + get + { + return this.currentState.transformNoiseWords; + } + set + { + this.currentState.transformNoiseWords = value; + this.NotifyObservers(); + } + } + + [Category("Category_ContainedDatabases"), + DisplayNameAttribute("Property_TwoDigitYearCutoff")] + public int TwoDigitYearCutoff + { + get + { + return this.currentState.twoDigitYearCutoff; + } + set + { + this.currentState.twoDigitYearCutoff = value; + this.NotifyObservers(); + } + } + + [Category("Category_Recovery"), + DisplayNameAttribute("Property_TargetRecoveryTime")] + public int TargetRecoveryTime + { + get + { + return this.currentState.targetRecoveryTime; + } + set + { + this.currentState.targetRecoveryTime = value; + this.NotifyObservers(); + } + } + + [Category("Category_Misc"), + DisplayNameAttribute("Property_DelayedDurability")] + public DelayedDurability DelayedDurability + { + get + { + return this.currentState.delayedDurability; + } + set + { + this.currentState.delayedDurability = value; + this.NotifyObservers(); + } + } + + public DatabasePrototype110(CDataContainer context) + : base(context) + { + } + + /// + /// Commit changes to the database + /// + /// The database whose properties we are changing + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + + if (db.IsSupportedProperty("ContainmentType")) + { + if (!this.Exists || (db.ContainmentType != this.DatabaseContainmentType)) + { + db.ContainmentType = this.DatabaseContainmentType; + } + + if (this.DatabaseContainmentType != ContainmentType.None) + { + if (!this.Exists || (db.DefaultFullTextLanguage.Lcid != this.DefaultFullTextLanguageLcid)) + { + db.DefaultFullTextLanguage.Lcid = this.DefaultFullTextLanguageLcid; + } + + //if (!this.Exists || (db.DefaultLanguage.Lcid != this.DefaultLanguage.lcid)) + //{ + // db.DefaultLanguage.Lcid = this.DefaultLanguage.lcid; + //} + + if (!this.Exists || (db.NestedTriggersEnabled != this.NestedTriggersEnabled)) + { + db.NestedTriggersEnabled = this.NestedTriggersEnabled; + } + + if (!this.Exists || (db.TransformNoiseWords != this.TransformNoiseWords)) + { + db.TransformNoiseWords = this.TransformNoiseWords; + } + + if (!this.Exists || (db.TwoDigitYearCutoff != this.TwoDigitYearCutoff)) + { + db.TwoDigitYearCutoff = this.TwoDigitYearCutoff; + } + } + } + + if (db.IsSupportedProperty("TargetRecoveryTime")) + { + if (!this.Exists || (db.TargetRecoveryTime != this.TargetRecoveryTime)) + { + db.TargetRecoveryTime = this.TargetRecoveryTime; + } + } + + if (db.IsSupportedProperty("DelayedDurability")) + { + if (!this.Exists || (db.DelayedDurability != this.DelayedDurability)) + { + db.DelayedDurability = this.DelayedDurability; + } + } + } + + #region ILanguageLcidWithConnectionInfo Members + + //int ILanguageLcidWithConnectionInfo.Lcid + //{ + // get { return this.DefaultLanguage.lcid; } + //} + + //ServerConnection ILanguageLcidWithConnectionInfo.Connection + //{ + // get { return this.context.ServerConnection; } + //} + + #endregion + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype80.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype80.cs new file mode 100644 index 00000000..06f5f3f4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype80.cs @@ -0,0 +1,285 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +using System.Resources; +//using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Diagnostics; +using System.Collections.Generic; + +//using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database Prototype for SqlServer 2000 and later servers + /// + //[TypeConverter(typeof(DynamicValueTypeConverter))] + //[StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototype80 : DatabasePrototype, IDynamicValues + { + /// + /// Whether the database is read-only + /// + [ + Category("Category_State"), + DisplayNameAttribute("Property_ReadOnly") + ] + public bool IsReadOnly + { + get + { + return this.currentState.isReadOnly; + } + set + { + this.currentState.isReadOnly = value; + this.NotifyObservers(); + } + } + + /// + /// Whether torn page detection is enabled + /// + [Category("Category_Recovery"), + DisplayNameAttribute("Property_PageVerify"), + TypeConverter(typeof(PageVerifyTypes80))] + public virtual string PageVerifyDisplay + { + get + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype80).GetAssembly()); + string result = null; + + switch (this.currentState.pageVerify) + { + case PageVerify.Checksum: + + result = manager.GetString("prototype.db.prop.pageVerify.value.checksum"); + break; + + case PageVerify.None: + + result = manager.GetString("prototype.db.prop.pageVerify.value.none"); + break; + + case PageVerify.TornPageDetection: + + result = manager.GetString("prototype.db.prop.pageVerify.value.tornPageDetection"); + break; + } + + return result; + } + + set + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype80).GetAssembly()); + if (value == manager.GetString("prototype.db.prop.pageVerify.value.checksum")) + { + this.currentState.pageVerify = PageVerify.Checksum; + } + else if (value == manager.GetString("prototype.db.prop.pageVerify.value.none")) + { + this.currentState.pageVerify = PageVerify.None; + } + else + { + this.currentState.pageVerify = PageVerify.TornPageDetection; + } + + this.NotifyObservers(); + } + } + + /// + /// ANSI Padding enabled + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_ANSIPadding")] + public bool AnsiPadding + { + get + { + return this.currentState.ansiPadding; + } + set + { + this.currentState.ansiPadding = value; + this.NotifyObservers(); + } + } + + /// + /// Use ANSI warnings + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_ANSIWarnings")] + public bool AnsiWarnings + { + get + { + return this.currentState.ansiWarnings; + } + set + { + this.currentState.ansiWarnings = value; + this.NotifyObservers(); + } + } + + /// + /// Arithabort + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_ArithAbort")] + public bool Arithabort + { + get + { + return this.currentState.arithabort; + } + set + { + this.currentState.arithabort = value; + this.NotifyObservers(); + } + } + + /// + /// Whether contatenating a null string yields a null result + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_ConcatNullYieldsNull")] + public bool ConcatNullYieldsNull + { + get + { + return this.currentState.concatNullYieldsNull; + } + set + { + this.currentState.concatNullYieldsNull = value; + this.NotifyObservers(); + } + } + + /// + /// Numeric Roundabout + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_NumericRoundAbort")] + public bool NumericRoundAbort + { + get + { + return this.currentState.numericRoundAbort; + } + set + { + this.currentState.numericRoundAbort = value; + this.NotifyObservers(); + } + } + + + public DatabasePrototype80(CDataContainer context) : base(context) { } + + /// + /// Commit property changes to the database + /// + /// The database whose properties we are changing + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype).GetAssembly()); + + // never set the real database collation to "" - there is no + // real collation with that name. "" is only valid for new + // databases and just means "don't set the collation". + if ((!this.Exists || (this.Exists && (db.Collation != this.Collation))) && + (this.originalState.defaultCollation != this.Collation)) + { + db.Collation = this.Collation; + } + + if (!this.Exists || (db.DatabaseOptions.AnsiPaddingEnabled != this.AnsiPadding)) + { + db.DatabaseOptions.AnsiPaddingEnabled = this.AnsiPadding; + } + + if (!this.Exists || (db.DatabaseOptions.AnsiWarningsEnabled != this.AnsiWarnings)) + { + db.DatabaseOptions.AnsiWarningsEnabled = this.AnsiWarnings; + } + + if (!this.Exists || (db.DatabaseOptions.ArithmeticAbortEnabled != this.Arithabort)) + { + db.DatabaseOptions.ArithmeticAbortEnabled = this.Arithabort; + } + + if (!this.Exists || (db.DatabaseOptions.ConcatenateNullYieldsNull != this.ConcatNullYieldsNull)) + { + db.DatabaseOptions.ConcatenateNullYieldsNull = this.ConcatNullYieldsNull; + } + + if (db.IsSupportedProperty("PageVerify")) + { + if (!this.Exists || (db.DatabaseOptions.PageVerify != this.currentState.pageVerify)) + { + db.DatabaseOptions.PageVerify = this.currentState.pageVerify; + } + } + + if (!this.Exists || (db.DatabaseOptions.NumericRoundAbortEnabled != this.NumericRoundAbort)) + { + db.DatabaseOptions.NumericRoundAbortEnabled = this.NumericRoundAbort; + } + + if (!this.Exists || (db.DatabaseOptions.ReadOnly != this.IsReadOnly)) + { + db.DatabaseOptions.ReadOnly = this.IsReadOnly; + } + } + + + #region IDynamicValues Members + + public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + TypeConverter.StandardValuesCollection result = null; + + if (context.PropertyDescriptor.Name == "PageVerifyDisplay") + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype80).GetAssembly()); + List standardValues = new List(); + + if (this.IsYukonOrLater) + { + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.checksum")); + } + + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.tornPageDetection")); + standardValues.Add(manager.GetString("prototype.db.prop.pageVerify.value.none")); + + result = new TypeConverter.StandardValuesCollection(standardValues); + } + else + { + result = base.GetStandardValues(context); + } + + return result; + } + + #endregion + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype80SP3.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype80SP3.cs new file mode 100644 index 00000000..49121cf4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype80SP3.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +// using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Sdk.Sfc; + +// using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database Prototype for SqlServer 2000 SP3 and later servers + /// + [TypeConverter(typeof(DynamicValueTypeConverter))] + //[StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototype80SP3 : DatabasePrototype80 + { + /// + /// ANSI Padding enabled + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_DBChaining")] + public bool DbChaining + { + get + { + return this.currentState.dbChaining; + } + } + + + public DatabasePrototype80SP3(CDataContainer context) : base(context) { } + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype90.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype90.cs new file mode 100644 index 00000000..2870ba91 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype90.cs @@ -0,0 +1,289 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +using System.Resources; +// using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Diagnostics; +using System.Collections.Generic; + +// using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database Prototype for SqlServer 2005 and later servers + /// + //[TypeConverter(typeof(DynamicValueTypeConverter))] + //[StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototype90 : DatabasePrototype80SP3, IDynamicValues + { + /// + /// Whether torn page detection is enabled + /// + [Category("Category_Automatic"), + DisplayNameAttribute("Property_AutoUpdateStateAsync")] + public bool AutoUpdateStatisticsAsync + { + get + { + return this.currentState.autoUpdateStatisticsAsync; + } + set + { + this.currentState.autoUpdateStatisticsAsync = value; + this.NotifyObservers(); + } + } + + /// + /// Whether torn page detection is enabled + /// + [Category("Category_Recovery"), + DisplayNameAttribute("Property_PageVerify"), + TypeConverter(typeof(PageVerifyTypes90))] + public override string PageVerifyDisplay + { + get + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype80).GetAssembly()); + string result = null; + + switch (this.currentState.pageVerify) + { + case PageVerify.Checksum: + + result = manager.GetString("prototype.db.prop.pageVerify.value.checksum"); + break; + + case PageVerify.None: + + result = manager.GetString("prototype.db.prop.pageVerify.value.none"); + break; + + case PageVerify.TornPageDetection: + + result = manager.GetString("prototype.db.prop.pageVerify.value.tornPageDetection"); + break; + } + + return result; + } + + set + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype80).GetAssembly()); + if (value == manager.GetString("prototype.db.prop.pageVerify.value.checksum")) + { + this.currentState.pageVerify = PageVerify.Checksum; + } + else if (value == manager.GetString("prototype.db.prop.pageVerify.value.none")) + { + this.currentState.pageVerify = PageVerify.None; + } + else + { + this.currentState.pageVerify = PageVerify.TornPageDetection; + } + + this.NotifyObservers(); + } + } + + /// + /// Use ANSI warnings + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_Trustworthy")] + public bool Trustworthy + { + get + { + return this.currentState.trustworthy; + } + } + + /// + /// Arithabort + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_DateCorrelationOptimization")] + public bool DateCorrelationOptimization + { + get + { + return this.currentState.dateCorrelationOptimization; + } + set + { + this.currentState.dateCorrelationOptimization = value; + this.NotifyObservers(); + } + } + + /// + /// AllowSnapshotIsolation + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_AllowSnapshotIsolation")] + public bool AllowSnapshotIsolation + { + get + { + return this.currentState.allowSnapshotIsolation; + } + set + { + this.currentState.allowSnapshotIsolation = value; + this.NotifyObservers(); + } + } + + + /// + /// IsReadCommittedSnapshotOn + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_IsReadCommittedSnapshotOn")] + public bool IsReadCommittedSnapshotOn + { + get + { + return this.currentState.isReadCommittedSnapshotOn; + } + set + { + this.currentState.isReadCommittedSnapshotOn = value; + this.NotifyObservers(); + } + } + + + /// + /// BrokerEnabled + /// + [Category("Category_ServiceBroker"), + DisplayNameAttribute("Property_BrokerEnabled")] + public bool BrokerEnabled + { + get + { + return this.currentState.brokerEnabled; + } + set + { + this.currentState.brokerEnabled = value; + this.NotifyObservers(); + } + } + + /// + /// Whether contatenating a null string yields a null result + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_Parameterization")] + [TypeConverter(typeof(ParameterizationTypes))] + public string Parameterization + { + get + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype90).GetAssembly()); + string result = this.currentState.parameterization ? + manager.GetString("prototype.db.prop.parameterization.value.forced") : + manager.GetString("prototype.db.prop.parameterization.value.simple"); + + return result; + } + set + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype90).GetAssembly()); + this.currentState.parameterization = (value == manager.GetString("prototype.db.prop.parameterization.value.forced")); + this.NotifyObservers(); + } + } + + /// + /// Service Broker Guid + /// + [Category("Category_ServiceBroker"), + DisplayNameAttribute("Property_ServiceBrokerGUID")] + public System.Guid ServiceBrokerGuid + { + get + { + return this.currentState.serviceBrokerGuid; + } + } + + public DatabasePrototype90(CDataContainer context) : base(context) { } + + + /// + /// Commit property changes to the database + /// + /// The database whose properties we are changing + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + + if (!this.Exists || (db.DatabaseOptions.AutoUpdateStatisticsAsync != this.AutoUpdateStatisticsAsync)) + { + db.DatabaseOptions.AutoUpdateStatisticsAsync = this.AutoUpdateStatisticsAsync; + } + + if (!this.Exists || (db.DatabaseOptions.DateCorrelationOptimization != this.DateCorrelationOptimization)) + { + db.DatabaseOptions.DateCorrelationOptimization = this.DateCorrelationOptimization; + } + + if (!this.Exists || (db.DatabaseOptions.IsParameterizationForced != this.currentState.parameterization)) + { + db.DatabaseOptions.IsParameterizationForced = this.currentState.parameterization; + } + if (db.IsSupportedProperty("BrokerEnabled")) + { + if (!this.Exists || (db.DatabaseOptions.BrokerEnabled != this.currentState.brokerEnabled)) + { + db.DatabaseOptions.BrokerEnabled = this.currentState.brokerEnabled; + } + } + + if (!this.Exists || (db.IsReadCommittedSnapshotOn != this.IsReadCommittedSnapshotOn)) + { + db.IsReadCommittedSnapshotOn = this.IsReadCommittedSnapshotOn; + } + } + + #region IDynamicValues Members + + public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + TypeConverter.StandardValuesCollection result = null; + + if (context.PropertyDescriptor.Name == "Parameterization") + { + ResourceManager manager = new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings", typeof(DatabasePrototype90).GetAssembly()); + List standardValues = new List(); + standardValues.Add(manager.GetString("prototype.db.prop.parameterization.value.forced")); + standardValues.Add(manager.GetString("prototype.db.prop.parameterization.value.simple")); + result = new TypeConverter.StandardValuesCollection(standardValues); + } + else + { + result = base.GetStandardValues(context); + } + + return result; + } + + #endregion + + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype90EnterpriseSP2.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype90EnterpriseSP2.cs new file mode 100644 index 00000000..9d0ed1a4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototype90EnterpriseSP2.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +// using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; + +// using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database Prototype for SqlServer 2005 Enterprise SP2 and later servers + /// + [TypeConverter(typeof(DynamicValueTypeConverter))] + //[StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototype90EnterpriseSP2 : DatabasePrototype90 + { + /// + /// Whether vardecimal compression is enabled on the server + /// + [Category("Category_Misc"), + DisplayNameAttribute("Property_VarDecimalEnabled")] + public bool VarDecimalEnabled + { + get + { + return this.currentState.varDecimalEnabled; + } + + set + { + this.currentState.varDecimalEnabled = value; + this.NotifyObservers(); + } + } + + public DatabasePrototype90EnterpriseSP2(CDataContainer context) : base(context) { } + + + /// + /// Commit property changes to the database + /// + /// The database whose properties we are changing + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + + // changing decimal compression status is very expensive, so + // only set a value for vardecimal compression if its value has changed + if (this.originalState.varDecimalEnabled != this.currentState.varDecimalEnabled) + { + db.IsVarDecimalStorageFormatEnabled = this.currentState.varDecimalEnabled; + } + } + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototypeAzure.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototypeAzure.cs new file mode 100644 index 00000000..90829a76 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabasePrototypeAzure.cs @@ -0,0 +1,393 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +using System.Text; +//using System.Windows.Forms; +//using Microsoft.SqlServer.Management.AzureSqlDbUtils; +//using Microsoft.SqlServer.Management.SqlMgmt; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Diagnostics; +using System.Globalization; +using System.Data.SqlClient; + +//using DisplayNameAttribute = Microsoft.SqlServer.Management.SqlMgmt.DisplayNameAttribute; +using AzureEdition = Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper.AzureEdition; +using Microsoft.SqlServer.Management.Common; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Database properties for SQL Azure DB. + /// Business/Web editions are up to compat level 100 now + /// + [TypeConverter(typeof(DynamicValueTypeConverter))] + //[StringResourceClass(typeof(Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseOptionsSR))] + internal class DatabasePrototypeAzure : DatabasePrototype100 + { + + #region Constants + + public const string Category_Azure = "Category_Azure"; + public const string Property_AzureMaxSize = "Property_AzureMaxSize"; + public const string Property_AzureCurrentServiceLevelObjective = "Property_AzureCurrentServiceLevelObjective"; + public const string Property_AzureConfiguredServiceLevelObjective = "Property_AzureConfiguredServiceLevelObjective"; + public const string Property_AzureEdition = "Property_AzureEdition"; + #endregion Constants + + public DatabasePrototypeAzure(CDataContainer context, DatabaseEngineEdition editionToCreate = DatabaseEngineEdition.SqlDatabase) + : base(context) + { + EditionToCreate = editionToCreate; + } + + #region Properties + + //private DesignableObject wrapper; + + //[Browsable(false)] + //public DesignableObject Wrapper + //{ + // get + // { + // return wrapper; + // } + // set + // { + // this.wrapper = value; + // //Now that we have a new wrapper make sure to update the dynamic visibility for the SLO options + // SetServiceLevelObjectiveOptionVisibility(); + + // } + //} + + [Category(Category_Azure), + DisplayNameAttribute(Property_AzureMaxSize)] + //TypeConverter(typeof(DynamicValuesConverter))] + public string MaxSize + { + get + { + return this.currentState.maxSize == null ? null : this.currentState.maxSize.ToString(); + } + set + { + this.currentState.maxSize = DbSize.ParseDbSize(value); + this.NotifyObservers(); + } + } + + [Category(Category_Azure), + DisplayNameAttribute(Property_AzureCurrentServiceLevelObjective)] + //TypeConverter(typeof(DynamicValuesConverter))] + public string CurrentServiceLevelObjective + { + get + { + return this.currentState.currentServiceLevelObjective; + } + set + { + this.currentState.currentServiceLevelObjective = value; + this.NotifyObservers(); + } + } + + [Category(Category_Azure), + DisplayNameAttribute(Property_AzureConfiguredServiceLevelObjective)] + public string ConfiguredServiceLevelObjective + { + //This value is read only because it's changed by changing the current SLO, + //we just expose this to show if the DB is currently transitioning + get + { + return this.currentState.configuredServiceLevelObjective; + } + } + + [Browsable(false)] + public AzureEdition AzureEdition + { + get + { + return this.currentState.azureEdition; + } + } + + [Category(Category_Azure), + DisplayNameAttribute(Property_AzureEdition)] + // TypeConverter(typeof(DynamicValuesConverter))] + //We have a separate property here so that the AzureEdition enum value is still exposed + //(This property is for the name displayed in the drop down menu, which needs to be a string for casting purposes) + public string AzureEditionDisplay + { + get + { + return AzureSqlDbHelper.GetAzureEditionDisplayName(this.currentState.azureEdition); + } + set + { + AzureEdition edition; + if (AzureSqlDbHelper.TryGetAzureEditionFromDisplayName(value, out edition)) + { + if (edition == this.currentState.azureEdition) + { //No changes, return early since we don't need to do any of the changes below + return; + } + + this.currentState.azureEdition = edition; + // this.SetServiceLevelObjectiveOptionVisibility(); + this.CurrentServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(edition); + this.MaxSize = AzureSqlDbHelper.GetDatabaseDefaultSize(edition).ToString(); + this.NotifyObservers(); + } + } + } + + public override IList Filegroups + { + get { return Enumerable.Empty().ToList(); } + } + + + public override IList Files + { + get { return Enumerable.Empty().ToList(); } + } + + [Browsable(false)] + public override bool HideFileSettings + { + get { return true; } + } + + [Browsable(false)] + public override bool AllowScripting + { + get { return this.ServerVersion.Major > 11 && this.AzureEdition != AzureEdition.DataWarehouse; } + } + + #endregion Properties + + /// + /// Sets the visibility of the SLO options based on the current configured Edition + /// + //private void SetServiceLevelObjectiveOptionVisibility() + //{ + // if (this.wrapper == null) + // { + // return; + // } + + // if (this.AzureEdition == AzureEdition.Business || this.AzureEdition == AzureEdition.Web) + // { //Business and Web editions don't support SLO so hide those options + // this.wrapper.SetupDynamicVisibility(Property_AzureCurrentServiceLevelObjective, false); + // this.wrapper.SetupDynamicVisibility(Property_AzureConfiguredServiceLevelObjective, false); + // } + // else + // { //Reset SLO options to visible in case they were hidden before + // this.wrapper.SetupDynamicVisibility(Property_AzureCurrentServiceLevelObjective, true); + // this.wrapper.SetupDynamicVisibility(Property_AzureConfiguredServiceLevelObjective, true); + // } + //} + + #region DatabasePrototype overrides + + /// + /// Commit changes to the database + /// + /// The control through which UI interactions are to be marshalled + /// The SMO database object that was created or modified + //public override Database ApplyChanges(Control marshallingControl) + //{ + // // For v12 Non-DW DBs lets use SMO + // if (this.ServerVersion.Major >= 12 && this.AzureEdition != AzureEdition.DataWarehouse) + // { + // return base.ApplyChanges(marshallingControl); + // } + + // //Note : We purposely don't call base.ApplyChanges() here since SMO doesn't fully support Azure yet and so will throw + // //an error if we try to modify the Database object directly + // string alterDbPropertiesStatement = DatabasePrototypeAzure.CreateModifyAzureDbOptionsStatement(this.Name, this.AzureEdition, this.MaxSize, this.CurrentServiceLevelObjective); + // if (this.AzureEdition == AzureEdition.DataWarehouse) + // { + // alterDbPropertiesStatement = DatabasePrototypeAzure.CreateModifySqlDwDbOptionsStatement(this.Name, this.MaxSize, this.CurrentServiceLevelObjective); + // } + + // string alterAzureDbRecursiveTriggersEnabledStatement = DatabasePrototypeAzure.CreateAzureDbSetRecursiveTriggersStatement(this.Name, this.RecursiveTriggers); + // string alterAzureDbIsReadOnlyStatement = DatabasePrototypeAzure.CreateAzureDbSetIsReadOnlyStatement(this.Name, this.IsReadOnly); + + // Database db = this.GetDatabase(); + + // //Altering the DB needs to be done on the master DB + // using (var conn = new SqlConnection(this.context.ServerConnection.GetDatabaseConnection("master").ConnectionString)) + // { + // var cmd = new SqlCommand(); + // cmd.Connection = conn; + // conn.Open(); + + // //Only run the alter statements for modifications made. This is mostly to allow the non-Azure specific + // //properties to be updated when a SLO change is in progress, but it also is beneficial to save trips to the + // //server whenever we can (especially when Azure is concerned) + // if (currentState.azureEdition != originalState.azureEdition || + // currentState.currentServiceLevelObjective != originalState.currentServiceLevelObjective || + // currentState.maxSize != originalState.maxSize) + // { + // cmd.CommandText = alterDbPropertiesStatement; + // cmd.ExecuteNonQuery(); + // } + + // if (currentState.recursiveTriggers != originalState.recursiveTriggers) + // { + // cmd.CommandText = alterAzureDbRecursiveTriggersEnabledStatement; + // cmd.ExecuteNonQuery(); + // } + + // if (currentState.isReadOnly != originalState.isReadOnly) + // { + // cmd.CommandText = alterAzureDbIsReadOnlyStatement; + // cmd.ExecuteNonQuery(); + // } + // } + + // //Because we didn't use SMO to do the alter we should refresh the DB object so it picks up the correct properties + // db.Refresh(); + + // // For properties that are supported in Database.Alter(), call SaveProperties, and then alter the DB. + // // + // if (this.AzureEdition != AzureEdition.DataWarehouse) + // { + // this.SaveProperties(db); + // db.Alter(TerminationClause.FailOnOpenTransactions); + // } + // return db; + //} + + #endregion DatabasePrototype overrides + + protected override void SaveProperties(Database db) + { + base.SaveProperties(db); + if (this.ServerVersion.Major >= 12 && this.AzureEdition != AzureEdition.DataWarehouse) + { + if (!this.Exists || (this.originalState.maxSize != this.currentState.maxSize)) + { + db.MaxSizeInBytes = this.currentState.maxSize.SizeInBytes; + } + + if (!this.Exists || (this.originalState.azureEdition != this.currentState.azureEdition)) + { + db.AzureEdition = this.currentState.azureEdition.ToString(); + } + + if (!this.Exists || (this.originalState.currentServiceLevelObjective != this.currentState.currentServiceLevelObjective)) + { + db.AzureServiceObjective = this.currentState.currentServiceLevelObjective; + } + } + + } + + private const string AlterDbStatementFormat = + @"ALTER DATABASE [{0}] {1}"; + + private const string ModifyAzureDbStatementFormat = @"MODIFY (EDITION = '{0}', MAXSIZE={1} {2})"; + private const string ModifySqlDwDbStatementFormat = @"MODIFY (MAXSIZE={0} {1})"; + private const string AzureServiceLevelObjectiveOptionFormat = @"SERVICE_OBJECTIVE = '{0}'"; + private const string SetReadOnlyOption = @"SET READ_ONLY"; + private const string SetReadWriteOption = @"SET READ_WRITE"; + private const string SetRecursiveTriggersOptionFormat = @"SET RECURSIVE_TRIGGERS {0}"; + private const string On = @"ON"; + private const string Off = @"OFF"; + + /// + /// Creates an ALTER DATABASE statement to modify the Read-Only status of the target DB + /// + /// + /// + /// + protected static string CreateAzureDbSetIsReadOnlyStatement(string dbName, bool isReadOnly) + { + return CreateAzureAlterDbStatement(dbName, + string.Format(CultureInfo.InvariantCulture, + isReadOnly ? SetReadOnlyOption : SetReadWriteOption)); + } + + /// + /// Creates an ALTER DATABASE statement to modify the RECURSIVE_TRIGGERS option of the target DB + /// + /// + /// + /// + protected static string CreateAzureDbSetRecursiveTriggersStatement(string dbName, bool recursiveTriggersEnabled) + { + return CreateAzureAlterDbStatement(dbName, + string.Format(CultureInfo.InvariantCulture, + DatabasePrototypeAzure.SetRecursiveTriggersOptionFormat, + recursiveTriggersEnabled ? DatabasePrototypeAzure.On : DatabasePrototypeAzure.Off)); + } + + /// + /// Creates an ALTER DATABASE statement to modify the Azure Database properties (Edition, MaxSize and Service Level Objective) + /// for the target database + /// + /// + /// + /// + /// + /// + protected static string CreateModifyAzureDbOptionsStatement(string dbName, AzureEdition edition, string maxSize, string serviceLevelObjective) + { + //We might not have a SLO since some editions don't support it + string sloOption = string.IsNullOrEmpty(serviceLevelObjective) ? + string.Empty : ", " + string.Format(CultureInfo.InvariantCulture, AzureServiceLevelObjectiveOptionFormat, serviceLevelObjective); + + return CreateAzureAlterDbStatement(dbName, + string.Format(CultureInfo.InvariantCulture, + ModifyAzureDbStatementFormat, + edition, + maxSize, + sloOption)); + } + + /// + /// Creates an ALTER DATABASE statement to modify the Azure DataWarehouse properties (MaxSize and Service Level Objective) + /// for the target database + /// + /// Name of the database + /// MaxSize of the database + /// New SLO of the database + /// Sql Statement to Alter the database. + protected static string CreateModifySqlDwDbOptionsStatement(string dbName, string maxSize, string serviceLevelObjective) + { + //We might not have a SLO since some editions don't support it + string sloOption = string.IsNullOrEmpty(serviceLevelObjective) ? + string.Empty : ", " + string.Format(CultureInfo.InvariantCulture, AzureServiceLevelObjectiveOptionFormat, serviceLevelObjective); + + return CreateAzureAlterDbStatement(dbName, + string.Format(CultureInfo.InvariantCulture, + ModifySqlDwDbStatementFormat, + maxSize, + sloOption)); + } + + /// + /// Creates the ALTER DATABASE statement from the given op + /// + /// + private static string CreateAzureAlterDbStatement(string dbName, string options) + { + return string.Format(CultureInfo.InvariantCulture, AlterDbStatementFormat, + CUtils.EscapeString(CUtils.EscapeString(dbName, ']'), '\''), + options); + } + } +} + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabaseTaskHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabaseTaskHelper.cs new file mode 100644 index 00000000..3f2c4324 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DatabaseTaskHelper.cs @@ -0,0 +1,130 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Xml; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + public class DatabaseTaskHelper + { + private DatabasePrototype prototype; + + private XmlDocument document; + + public CDataContainer DataContainer { get; set; } + + /// + /// Expose database prototype to internal classes + /// + public DatabasePrototype Prototype + { + get + { + return this.prototype; + } + set + { + this.prototype = value; + } + } + + public void CreateDatabase(CDataContainer context) + { + InitializeDataMembers(context); + } + + private void InitializeDataMembers(CDataContainer context) + { + if (context != null) + { + this.DataContainer = context; + this.document = context.Document; + + int majorVersionNumber = context.Server.Information.Version.Major; + Version sql2000sp3 = new Version(8, 0, 760); + Version sql2005sp2 = new Version(9, 0, 3000); + + if (context.Server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) + { + this.prototype = new DatabasePrototypeAzure(context); + } + else if (Utils.IsSql11OrLater(context.Server.Version.Major)) + { + this.prototype = new DatabasePrototype110(context); + } + else if (majorVersionNumber == 10) + { + this.prototype = new DatabasePrototype100(context); + } + else if ((sql2005sp2 <= context.Server.Information.Version) && + (context.Server.Information.EngineEdition == Edition.EnterpriseOrDeveloper)) + { + this.prototype = new DatabasePrototype90EnterpriseSP2(context); + } + else if (8 < majorVersionNumber) + { + this.prototype = new DatabasePrototype90(context); + } + else if (sql2000sp3 <= context.Server.Information.Version) + { + this.prototype = new DatabasePrototype80SP3(context); + } + else if (7 < majorVersionNumber) + { + this.prototype = new DatabasePrototype80(context); + } + else + { + this.prototype = new DatabasePrototype(context); + } + + this.prototype.Initialize(); + + //this.databasesCreated = new ArrayList(); + } + else + { + this.DataContainer = null; + this.document = null; + this.prototype = null; + //this.databasesCreated = null; + } + } + + internal static DatabaseInfo DatabasePrototypeToDatabaseInfo(DatabasePrototype prototype) + { + var databaseInfo = new DatabaseInfo(); + databaseInfo.Options.Add(AdminServicesProviderOptionsHelper.Name, prototype.Name); + databaseInfo.Options.Add(AdminServicesProviderOptionsHelper.Owner, prototype.Owner); + databaseInfo.Options.Add(AdminServicesProviderOptionsHelper.Collation, prototype.Collation); + + for (int i = 0; i < prototype.Filegroups.Count; ++i) + { + var fileGroup = prototype.Filegroups[i]; + string itemPrefix = AdminServicesProviderOptionsHelper.FileGroups + "." + i + "."; + databaseInfo.Options.Add(itemPrefix + AdminServicesProviderOptionsHelper.Name, fileGroup.Name); + + } + + for (int i = 0; i < prototype.Files.Count; ++i) + { + var file = prototype.Files[i]; + string itemPrefix = AdminServicesProviderOptionsHelper.DatabaseFiles + "." + i + "."; + databaseInfo.Options.Add(itemPrefix + AdminServicesProviderOptionsHelper.Name, file.Name); + databaseInfo.Options.Add(itemPrefix + AdminServicesProviderOptionsHelper.PhysicalName, file.PhysicalName); + } + + return databaseInfo; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DbSize.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DbSize.cs new file mode 100644 index 00000000..3a3ca7f8 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/DbSize.cs @@ -0,0 +1,173 @@ +// +// 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 System.Linq; +using System.Text; +using Microsoft.SqlServer.Management.Common; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Helper class to describe the size of an Azure database + /// + public class DbSize + { + public enum SizeUnits + { + MB, + GB, + TB + } + + #region Member Vars + + private readonly int size; + private readonly SizeUnits sizeUnit; + + #endregion + + + public DbSize(int size, SizeUnits sizeUnit) + { + this.size = size; + this.sizeUnit = sizeUnit; + } + + /// + /// Copy constructor + /// + public DbSize(DbSize copy) + { + this.size = copy.Size; + this.sizeUnit = copy.SizeUnit; + } + + /// + /// Size of the DB + /// + public int Size + { + get + { + return size; + } + } + + /// + /// Units that the size is measured in + /// + public SizeUnits SizeUnit + { + get + { + return sizeUnit; + } + } + + /// + /// Returns the number of bytes represented by the DbSize + /// + public double SizeInBytes + { + get + { + var sizeBase = Convert.ToDouble(this.size); + switch (SizeUnit) + { + case SizeUnits.MB: + return sizeBase*1024.0*1024.0; + case SizeUnits.GB: + return sizeBase*1024.0*1024.0*1024.0; + case SizeUnits.TB: + return sizeBase*1024.0*1024.0*1024.0*1024.0; + } + throw new InvalidOperationException("SR.UnknownSizeUnit(SizeUnit.ToString())"); + } + } + #region Object Overrides + /// + /// Displays the size in the format ####UU (e.g. 100GB) + /// + /// + public override string ToString() + { + return size + sizeUnit.ToString(); + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + return this == (DbSize)obj; + } + + public override int GetHashCode() + { + return this.size.GetHashCode() ^ this.sizeUnit.GetHashCode(); + } + + #endregion Object Overrides + + /// + /// Parses a string in the format ####UU into a DbSize object. The number of + /// numeric characters must be parseable into an int and the last two characters + /// mapped to one of the SizeUnits enum values. + /// + /// + /// + public static DbSize ParseDbSize(string dbSizeString) + { + if (dbSizeString == null || dbSizeString.Length < 3) + { //Sanity check + throw new ArgumentException("DbSize string must be at least 3 characters (#UU)"); + } + int size; + //Try and parse all but the last two characters into the size + if (int.TryParse(dbSizeString.Substring(0, dbSizeString.Length - 2), out size)) + { + //Get the unit portion (last two characters) + string unitPortion = dbSizeString.Substring(dbSizeString.Length - 2 ); + SizeUnits unit; + if (Enum.TryParse(unitPortion, true, out unit)) + { + return new DbSize(size, unit); + } + else + { + throw new ArgumentException("DbSize string does not contain a valid unit portion"); + } + } + else + { + throw new ArgumentException("DbSize string does not contain a valid numeric portion"); + } + } + + public static bool operator ==(DbSize x, DbSize y) + { + if(ReferenceEquals(x, y)) + { //Both null or both same instance, are equal + return true; + } + + if((object)x == null || (object)y == null) + { //Only one null, never equal (cast to object to avoid infinite loop of ==) + return false; + } + + return x.size == y.size && x.sizeUnit == y.sizeUnit; + } + + public static bool operator !=(DbSize x, DbSize y) + { + return !(x == y); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/ICloneable.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/ICloneable.cs new file mode 100644 index 00000000..7431ff68 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/ICloneable.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace System +{ + internal interface ICloneable + { + object Clone(); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/IManagedConnection.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/IManagedConnection.cs new file mode 100644 index 00000000..fcb9b5f4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/IManagedConnection.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Diagnostics; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + + /// + /// Provides connection and enumerator context for a node + /// + public interface IManagedConnection : IDisposable + { + /// + /// Connection information. + /// + SqlOlapConnectionInfoBase Connection + { + get; + } + + /// + /// Free any resources for this connection + /// + void Close(); + } + + /// + /// interface used by the objectexplorer. Allows us to "pool" the main connection + /// + internal interface IManagedConnection2 : IManagedConnection + { + } + + /// + /// Implementation of IManagedConnection. Allows the use of a direct or indirect connection + /// in the object explorer that takes care of the connection. + /// + internal class ManagedConnection : IManagedConnection2 + { + #region private members + private bool connectionAddedToActiveConnections = false; + private bool closeOnDispose = false; + private SqlOlapConnectionInfoBase connection; + private bool closed = false; + #endregion + + #region Construction + /// + /// Create a new managed connection + /// + /// connection wish to manage + public ManagedConnection(SqlOlapConnectionInfoBase connection) + : this(connection, false) + { + } + /// + /// create a new managed connection. + /// + /// connection + /// true if we are going to try and reuse the + /// connection if possible + public ManagedConnection(SqlOlapConnectionInfoBase sourceConnection, bool attemptToPool) + { + // parameter check + if (sourceConnection == null) + { + throw new ArgumentNullException("sourceConnection"); + } + + // see if the connection can restrict access (single user mode) + IRestrictedAccess access = sourceConnection as IRestrictedAccess; + // see if it is cloneable + ICloneable cloneable = sourceConnection as ICloneable; + lock (ActiveConnections) + { + // if it's not single user mode then we can see if the object can be cloned + if (access == null || access.SingleConnection == false) + { + // if we are going to attempt to pool, see if the connection is in use + if (attemptToPool && !ActiveConnections.Contains(SharedConnectionUtil.GetConnectionKeyName(sourceConnection))) + { + // add it to the hashtable to indicate use. + ActiveConnections.Add(SharedConnectionUtil.GetConnectionKeyName(sourceConnection), sourceConnection); + this.connection = sourceConnection; + this.closeOnDispose = false; + this.connectionAddedToActiveConnections = true; + } + else if (cloneable != null) + { + this.connection = (SqlOlapConnectionInfoBase)cloneable.Clone(); + this.closeOnDispose = true; + } + else if (sourceConnection is SqlConnectionInfoWithConnection) + { + this.connection = ((SqlConnectionInfoWithConnection)sourceConnection).Copy(); + this.closeOnDispose = true; + } + } + } + // if everything else has failed just use to passed in connection. + if (this.connection == null) + { + this.connection = sourceConnection; + } + + // always set the lock timeout to prevent the shell from not responding + if (this.connection is SqlConnectionInfoWithConnection) + { + // set lock_timeout to 10 seconds + ((SqlConnectionInfoWithConnection)this.connection).ServerConnection.LockTimeout = 10; + } + } + #endregion + + #region IDisposable implementation + public void Dispose() + { + Close(); + } + #endregion + + #region IManagedConnection implementation + /// + /// Connection + /// + public SqlOlapConnectionInfoBase Connection + { + get + { + return this.connection; + } + } + + /// + /// Close the current connection if applicable. + /// + public void Close() + { + if (this.closed) + return; + + if (this.closeOnDispose) + { + IDisposable disp = this.connection as IDisposable; + if (disp != null) + { + disp.Dispose(); + } + } + else + { + // if we are not closing the connection and it is a sql connection then ensure it + // is left in the master database. + SqlConnectionInfoWithConnection sqlConnection = this.connection as SqlConnectionInfoWithConnection; + if (sqlConnection != null && sqlConnection.ServerConnection.DatabaseEngineType == DatabaseEngineType.Standalone) + { + try + { + sqlConnection.ServerConnection.ExecuteNonQuery("use [master]"); + } + // don't error if this fails + catch + { } + } + } + if (this.connectionAddedToActiveConnections) + { + lock (ActiveConnections) + { + ActiveConnections.Remove(SharedConnectionUtil.GetConnectionKeyName(connection)); + } + } + + this.connection = null; + this.closed = true; + } + #endregion + + #region static helpers + /// + /// hashtable we use to keep track of actively used main connections + /// + private static Hashtable activeConnections = null; + private Hashtable ActiveConnections + { + get + { + if (activeConnections == null) + { + activeConnections = new Hashtable(); + } + return activeConnections; + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/ISandboxLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/ISandboxLoader.cs new file mode 100644 index 00000000..85cc0cb9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/ISandboxLoader.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// To access DataModelingSandbox from NavigableItem + /// + public interface ISandboxLoader + { + /// + /// Get sandbox + /// + /// DataModelingSandbox object associated with this NavigableItem + object GetSandbox(); + + /// + /// Refresh sandbox data associated with this NavigableItem + /// + void RefreshSandboxData(); + + /// + /// Delete sandbox from cache + /// + void DeleteSandbox(); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/STParameters.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/STParameters.cs new file mode 100644 index 00000000..bdacd2e9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/STParameters.cs @@ -0,0 +1,488 @@ +// +// 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.Xml; +using System.Collections; +using System.Diagnostics; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// SqlTools Parameters, used to define what goes into starting up a Workbench Form + /// AKA a dbCommander, AKA a "dialog" + /// These parameters are xml snippets + /// + public class STParameters + { + public XmlDocument m_doc; + + /// + /// The data type we are interested in + /// + public enum STType { eNULL, eInt, eLong, eString }; + + /// + /// default constructor + /// + public STParameters() + { + // + // TODO: Add constructor logic here + // + } + + /// + /// Constructor + /// + /// The xml snippet used to control the dbCommander + public STParameters(XmlDocument xmlDoc) + { + m_doc = xmlDoc; + } + + /// + /// Changing the xml snippet we are using + /// + /// the new xml snippet + public void SetDocument(XmlDocument xmlDoc) + { + m_doc = xmlDoc; + } + + /// + /// Access to the xml we are using for dbCommander parameters + /// + /// our current parameters + public XmlDocument GetDocument() + { + return m_doc; + } + + /// + /// Search for an xml tag, and return its value + /// + /// the xml tag name + /// the value of that tag + /// flag that is true if the data was found, false if not + public bool GetBaseParam(string parameterName, ref object value) + { + XmlNodeList nodeList = null; + bool parameterExists; + + if (m_doc == null) + return false; + + parameterExists = false; + nodeList = m_doc.GetElementsByTagName(parameterName); + + if (nodeList.Count > 1) + { + value = null; + } + else if (nodeList.Count != 0) // anything there? + { + try + { + XmlNode node = nodeList.Item(0); + if (null != node) + { + value = node.InnerText as object; + parameterExists = true; + } + } + catch (Exception /*e*/) + { + } + + } + + return parameterExists; + } + + /// + /// Finds an existing xml tag, and sets it to a new value, or if the tag is not found + /// create it and set it's value + /// + /// tag name + /// new value + /// flag that is true if the tag was set, false if not + public bool SetBaseParam(string parameterName, object value) + { + XmlNodeList nodeList; + bool success = false; + + nodeList = m_doc.GetElementsByTagName(parameterName); + + if (nodeList.Count == 1) + { + try + { + nodeList.Item(0).InnerText = (string)value; + success = true; + } + catch (InvalidCastException /*e*/) + { + success = false; + } + + } + + if (nodeList.Count == 0) + { + try + { + XmlElement xmlElement = m_doc.CreateElement(parameterName); + XmlNode root = m_doc.DocumentElement; + + nodeList = m_doc.GetElementsByTagName("params"); + + if (nodeList.Count == 1 && value is string) + { + xmlElement.InnerText = (string)value; + nodeList.Item(0).InsertAfter(xmlElement, nodeList.Item(0).LastChild); + + success = true; + } + } + catch (Exception e) + { + string sz = e.ToString(); + success = false; + } + + } + + return success; + + } + + /// + /// Get back an interger parameter. + /// NOTE: if the tag exists, but it contains non-numeric data, this will throw + /// An exception of type 'System.FormatException' + /// with Additional information: Could not find any parsible digits. + /// + /// xml tag name for the parameter of interest + /// out value of parameter + /// flag that is true if the data was found, false if not + public bool GetParam(string parameterName, out int value) + { + bool parameterExists = false; + + value = 0; + + try + { + object oAux = null; + + if (parameterExists = GetBaseParam(parameterName, ref oAux)) + { + try + { + value = Convert.ToInt32((string)oAux, 10); // data is always a string, it is the value of XmlNode.InnerText + } + catch (FormatException e) + { + Debug.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Non numeric data in tag: {0}", parameterName)); + Debug.WriteLine(e.Message); + throw; + } + } + } + catch (InvalidCastException /*e*/) + { + } + + return parameterExists; + } + + /// + /// Accessor for a boolean parameter + /// NOTE: if the tag exists, but it contains non-numeric data, this will throw + /// An exception of type 'System.FormatException' + /// with Additional information: Could not find any parsible digits. + /// + /// xml tag name for the parameter of interest + /// out value of parameter + /// flag that is true if the data was found, false if not + public bool GetParam(string parameterName, ref bool value) + { + bool parameterExists = false; + + value = false; + + try + { + object oAux = null; + if (parameterExists = GetBaseParam(parameterName, ref oAux)) + { + try + { + value = Convert.ToBoolean((string)oAux, System.Globalization.CultureInfo.InvariantCulture); // data is always a string, it is the value of XmlNode.InnerText + } + catch (FormatException e) + { + Debug.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Non boolean data in tag: {0}", parameterName)); + Debug.WriteLine(e.Message); + throw; + } + } + } + catch (InvalidCastException /*e*/) + { + } + + return parameterExists; + } + + /// + /// Accessor to a string parameter + /// + /// xml tag name for the parameter of interest + /// out value of parameter + /// flag that is true if the data was found, false if not + public bool GetParam(string parameterName, ref string value) + { + bool parameterExists; + + value = ""; + parameterExists = false; + + try + { + object oAux = null; + + if (parameterExists = GetBaseParam(parameterName, ref oAux)) + { + value = (string)oAux; + } + } + catch (InvalidCastException /*e*/) + { + } + + return parameterExists; + + } + + /// + /// Accessor to long parameter (Int64) + /// NOTE: if the tag exists, but it contains non-numeric data, this will throw + /// An exception of type 'System.FormatException' + /// with Additional information: Could not find any parsible digits. + /// + /// xml tag name for the parameter of interest + /// out value of parameter + /// flag that is true if the data was found, false if not + public bool GetParam(string parameterName, out long value) + { + bool parameterExists = false; + + value = 0; + + try + { + object oAux = null; + + if (parameterExists = GetBaseParam(parameterName, ref oAux)) + { + try + { + value = Convert.ToInt64((string)oAux, 10); // data is always a string, it is the value of XmlNode.InnerText + } + catch (FormatException e) + { + Debug.WriteLine(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Non numeric data in tag: {0}", parameterName)); + Debug.WriteLine(e.Message); + throw; + } + } + } + catch (InvalidCastException /*e*/) + { + } + + return parameterExists; + + } + + + /// + /// Set an int (Int32) parameter + /// + /// tag name for parameter + /// integer value + /// true if set was successful, false if not + public bool SetParam(string parameterName, int value) + { + bool success; + + success = SetBaseParam(parameterName, (object)value); + + return success; + + } + + /// + /// Set a string parameter + /// + /// tag name for parameter + /// string value + /// true if set was successful, false if not + public bool SetParam(string parameterName, string value) + { + bool success; + + success = SetBaseParam(parameterName, (object)value); + + return success; + + } + + /// + /// Set a long (Int64) parameter + /// + /// tag name for parameter + /// long value + /// true if set was successful, false if not + public bool SetParam(string parameterName, long value) + { + bool success; + + success = SetBaseParam(parameterName, (object)value); + + return success; + + } + + /// + /// Get a string collection parameter + /// + /// name of collection + /// collection that gets filled up with parameters + /// true if we want to get at inner nodes, false if not + /// true if parameter(s) exist + public bool GetParam(string parameterName, System.Collections.Specialized.StringCollection list, bool getInnerXml) + { + /// necessary for OALP objects path that is in an XML form + if (true == getInnerXml) + { + XmlNodeList nodeList; + bool parameterExists; + long lCount; + + parameterExists = false; + nodeList = m_doc.GetElementsByTagName(parameterName); + + list.Clear(); + + lCount = nodeList.Count; + + if (lCount > 0) + { + parameterExists = true; + + for (long i = 0; i < lCount; i++) + { + list.Add(nodeList.Item((int)i).InnerXml); + } + } + else + { + parameterExists = false; + } + + return parameterExists; + } + else + { + return GetParam(parameterName, list); + } + } + + /// + /// Access to a collection of parameters + /// + /// name of collection + /// list to fill with parameters + /// parameter(s) exist + public bool GetParam(string parameterName, System.Collections.Specialized.StringCollection list) + { + XmlNodeList nodeList; + bool parameterExists; + long lCount; + + parameterExists = false; + nodeList = m_doc.GetElementsByTagName(parameterName); + + list.Clear(); + + lCount = nodeList.Count; + + if (lCount > 0) + { + parameterExists = true; + + for (long i = 0; i < lCount; i++) + { + list.Add(nodeList.Item((int)i).InnerText); + } + } + else + { + parameterExists = false; + } + + return parameterExists; + } + + public bool GetParam(string parameterName, ref ArrayList list) + { + System.Collections.Specialized.StringCollection stringList = new System.Collections.Specialized.StringCollection(); + bool parameterExists = GetParam(parameterName, stringList); + list.Clear(); + if (!parameterExists) + { + return false; + } + else + { + for (int i = 0; i < stringList.Count; i++) + { + list.Add(stringList[i]); + } + return true; + } + } + + /// + /// This function does nothing but return false + /// + /// ignored + /// ignored + /// always false + public bool GetParamType(string parameterName, STType type) + { + bool whatever = false; + + return whatever; + } + + /// + /// This function does nothing but return false + /// + /// ignored + /// ignored + /// always false + public bool SetParamType(string parameterName, STType type) + { + bool whatever = false; + + return whatever; + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/SharedConnectionUtil.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/SharedConnectionUtil.cs new file mode 100644 index 00000000..7385c266 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/SharedConnectionUtil.cs @@ -0,0 +1,108 @@ +// +// 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.SqlServer.Management.AzureCredential; +using Microsoft.SqlServer.Management.Common; +//using Microsoft.SqlServer.Management.Smo.RegSvrEnum; +using SFC = Microsoft.SqlServer.Management.Sdk.Sfc; +//using Microsoft.SqlServer.StorageClient; +//using Microsoft.SqlServer.Management.SqlMgmt; + +/// +/// Summary description for SharedConectionUtil +/// Moved GetConnectionName static call in a public class acessible for both +/// OEXM and OE +/// +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + internal class SharedConnectionUtil + { + public SharedConnectionUtil() + { + } + + /// + /// + /// + /// + /// + public static string GetConnectionKeyName(SqlOlapConnectionInfoBase ci) + { + + //// Note that these strings are not localized. The returned string is used by OE in a + //// hash of connections so it can tell if it already has such a connection open. This + //// string is never seen by the user. For the string seen by the user, see + //// ServerNameHandler.cs. + string displayName = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} (", ci.ServerName); + + if (!string.IsNullOrEmpty(ci.DatabaseName)) + { + displayName += ", " + ci.DatabaseName; + } + + return displayName; + + //switch (ci.ServerType) + //{ + // case ConnectionType.AzureStorage: + // AzureStorageConnectionInfo azureCI = ci as AzureStorageConnectionInfo; + // displayName = "AzureStorage," + azureCI.BlobClient.BaseUri; + // break; + // case ConnectionType.AzureAccount: + // if (ci is CertificateBasedAuthenticationInfo) + // { + // displayName = "AzureSubscription," + (ci as CertificateBasedAuthenticationInfo).SubscriptionId; + // } + // else + // { + // displayName = "AzureSubscription"; + // } + // break; + // case ConnectionType.Sql: + // displayName += "SQLServer"; + // SqlConnectionInfo sqlCi = ci as SqlConnectionInfo; + // if (sqlCi.UseIntegratedSecurity == true) + // { + // displayName += ", trusted"; + // } + // else + // { + // displayName += String.Format(System.Globalization.CultureInfo.InvariantCulture, ", user = {0}", sqlCi.UserName); + // //In Cloud a user can have access to only a few UDBs without access to master DB + // // and hence need to show different OE hierarchy trees for each DB + // //Same is the case with a contained user. + + + // if (ServerInfoCache.GetDatabaseEngineType(ci.ServerName) == DatabaseEngineType.SqlAzureDatabase + // || SFC.ExecuteSql.GetDatabaseEngineType(ci) == DatabaseEngineType.SqlAzureDatabase + // || SFC.ExecuteSql.IsContainedAuthentication(ci)) + // { + // if (!string.IsNullOrEmpty(ci.DatabaseName)) + // { + // displayName += ", " + ci.DatabaseName; + // } + // } + // } + // break; + // case ConnectionType.Olap: + // displayName += "OLAP"; + // break; + // case ConnectionType.SqlCE: + // displayName += "SqlCE"; + // break; + // case ConnectionType.ReportServer: + // displayName += "Rs"; + // displayName += String.Format(System.Globalization.CultureInfo.InvariantCulture, ", connection = {0}", ci.ConnectionString); + // break; + // case ConnectionType.IntegrationServer: + // displayName += "SSIS"; + // break; + //} + //displayName += ")"; + //return displayName; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/Utils.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/Utils.cs new file mode 100644 index 00000000..bb79c39e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Common/Utils.cs @@ -0,0 +1,410 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +//extern alias VSShell2; +//extern alias VSShell2Iop; +using System; +using System.Reflection; +using System.Globalization; +using Microsoft.SqlServer.Management.Diagnostics; +using SMO = Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.Common; +//using VSShell2Iop.Microsoft.VisualStudio.Shell.Interop; +//using VSShell2.Microsoft.VisualStudio.Shell; + +namespace Microsoft.SqlTools.ServiceLayer.Admin +{ + /// + /// Internal reusable helpers + /// + internal class Utils + { + /// + /// only static methods + /// + private Utils() { } + + /// + /// returns all instances of the given custom attribute on a given object + /// + /// + /// + /// + public static Attribute GetCustomAttribute(object objectToGetAttributeFrom, Type customAttribute) + { + //first, see if the object implemented this interface to override standard behavior + System.Reflection.ICustomAttributeProvider attribProvider = objectToGetAttributeFrom as System.Reflection.ICustomAttributeProvider; + if (attribProvider == null) + { + //if not, get it from its type + attribProvider = (System.Reflection.ICustomAttributeProvider)objectToGetAttributeFrom.GetType(); + } + + object[] attribs = attribProvider.GetCustomAttributes(customAttribute, true); + if (attribs != null && attribs.Length > 0) + { + //NOTE: important that we'll always use the first one in collection. + //Our implementation of ICustomAttributeProvider knows about that and + //relies on this behavior + return attribs[0] as Attribute; + } + + return null; + } + + + /// + /// called to create SqlConnectionInfo out of the given CDataContainer object + /// + /// + /// + public static SqlConnectionInfo GetSqlConnectionInfoFromDataContainer(CDataContainer dc) + { + if (dc != null) + { + // we may have been given conneciton information by the object explorer. in which case there is no need + // to build it ourselves. + SqlConnectionInfo result = dc.ConnectionInfo as SqlConnectionInfo; + if (result == null) + { + throw new InvalidOperationException(); + } + + return result; + } + else + { + return null; + } + } + + public static int InitialTreeViewWidth + { + get + { + return 175; + } + } + + /// + /// Try to set the CLR thread name. + /// Will not throw if the name is already set. + /// + /// + public static void TrySetThreadName(String name) + { + try + { + System.Threading.Thread.CurrentThread.Name = name; + } + catch (InvalidOperationException) + { } + } + + public static bool IsKatmaiOrLater(int version) + { + return (10 <= version); + } + + public static bool IsKjOrLater(ServerVersion version) + { + return (version.Major > 10 + || (version.Major == 10 && version.Minor >= 50)); + } + + public static bool IsSql11OrLater(ServerVersion version) + { + return IsSql11OrLater(version.Major); + } + + public static bool IsSql11OrLater(int versionMajor) + { + return (versionMajor >= 11); + } + + public static bool IsSql12OrLater(ServerVersion version) + { + return IsSql12OrLater(version.Major); + } + + public static bool IsSql12OrLater(int versionMajor) + { + return (versionMajor >= 12); + } + + public static bool IsSql13OrLater(ServerVersion version) + { + return IsSql13OrLater(version.Major); + } + + public static bool IsSql13OrLater(int versionMajor) + { + return (versionMajor >= 13); + } + + public static bool IsSql14OrLater(ServerVersion version) + { + return IsSql14OrLater(version.Major); + } + + public static bool IsSql14OrLater(int versionMajor) + { + return (versionMajor >= 14); + } + + /// + /// Check if the version is SQL 2016 SP1 or later. + /// + /// + /// true if the version is SQL 2016 SP1 or later, false otherwise + public static bool IsSql13SP1OrLater(Version version) + { + return (version >= new Version(13, 0, 3510)); + } + + public static bool IsXTPSupportedOnServer(SMO.Server server) + { + bool isXTPSupported = false; + + if (server.ConnectionContext.ExecuteScalar("SELECT SERVERPROPERTY('IsXTPSupported')") != DBNull.Value) + { + isXTPSupported = server.IsXTPSupported; + } + + return isXTPSupported; + } + + /// + /// Returns true if given database has memory optimized filegroup on given server. + /// + /// + /// + /// + public static bool HasMemoryOptimizedFileGroup(SMO.Server server, string dbName) + { + bool hasMemoryOptimizedFileGroup = false; + + if (server.ServerType != DatabaseEngineType.SqlAzureDatabase) + { + string query = string.Format(CultureInfo.InvariantCulture, + "select top 1 1 from [{0}].sys.filegroups where type = 'FX'", + CUtils.EscapeString(dbName, ']')); + if (server.ConnectionContext.ExecuteScalar(query) != null) + { + hasMemoryOptimizedFileGroup = true; + } + } + + return hasMemoryOptimizedFileGroup; + } + + public static bool IsPolybasedInstalledOnServer(SMO.Server server) + { + bool isPolybaseInstalled = false; + + if (server.IsSupportedProperty("IsPolyBaseInstalled")) + { + isPolybaseInstalled = server.IsPolyBaseInstalled; + } + + return isPolybaseInstalled; + } + + /// + /// Returns true if current user has given permission on given server. + /// + /// + /// + /// + public static bool HasPermissionOnServer(SMO.Server server, string permissionName) + { + return Convert.ToBoolean(server.ConnectionContext.ExecuteScalar( + string.Format(CultureInfo.InvariantCulture, + "SELECT HAS_PERMS_BY_NAME(null, null, '{0}');", + permissionName))); + } + + public static bool FilestreamEnabled(SMO.Server svr) + { + bool result = false; + if (svr != null) + { + if (IsKatmaiOrLater(svr.Information.Version.Major) + && svr.ServerType != DatabaseEngineType.SqlAzureDatabase) //Azure doesn't support filestream + { + if (svr.Configuration.FilestreamAccessLevel.RunValue != 0) + { + result = true; + } + } + } + + return result; + } + + /// + /// This function returns true if SSMS is the running application + /// and if it contains only the core relational packages. + /// + /// + //public static bool IsSsmsMinimalSet() + //{ + // IVsShell vsShell = Package.GetGlobalService(typeof(SVsShell)) as IVsShell; + // const string guidSqlStudioPkgString = "04401ff3-8b0f-4d2d-85eb-2a3542867a8b"; + // Guid guidSqlStudioPkg = new Guid(guidSqlStudioPkgString); + + + // //Applications like 'AS Migration wizard' are non-ssms/non-VS shell applications + // if (vsShell == null) + // { + // return false; + // } + // else + // { + // IVsPackage ssmsPackage; + // vsShell.IsPackageLoaded(ref guidSqlStudioPkg, out ssmsPackage); + + // return ((ssmsPackage != null) && + // !AreExtendedFeaturesAvailable()); + // } + //} + + /// + /// This function checks if extended SSMS packages are loaded + /// + /// + /// + //public static bool AreExtendedFeaturesAvailable() + //{ + // return Microsoft.SqlServer.Management.UI.VSIntegration.SsmsInformation.CanShowNonExpressFeatures; + //} + + /// + /// Execute a static method by reflection + /// + /// The short name of the assembly, like Microsoft.SqlServer.Management.RegisteredServersUI.dll + /// The fully qualified name of the class, like Microsoft.SqlServer.Management.RegisteredServers.Utils + /// THe name of the static method to call, like DoSomething + /// params array of arguments to pass to the method + /// + //public static object ReflectionExecuteStatic( + // string assemblyShortName, + // string className, + // string methodName, + // params object[] parameters) + //{ + // STrace.Params( + // SqlMgmtDiag.TName, + // "Utils.ReflectionExecuteStatic(string, string, string, object[])", + // "assemblyShortName='{0}', className='{1}', methodName='{2}'", + // assemblyShortName, + // className, + // methodName); + + // Assembly assembly = Assembly.Load( + // Microsoft.SqlServer.Management.SqlMgmt.AssemblyLoadUtil.GetFullAssemblyName(assemblyShortName)); + + // if (assembly == null) + // { + // STrace.LogExThrow(); + // throw new ArgumentException("Couldn't load assembly by reflection"); + // } + + // Type type = assembly.GetType(className); + // if (type == null) + // { + // STrace.LogExThrow(); + // throw new ArgumentException("Couldn't find class by reflection"); + // } + + // // if we need to call a polymorphic method, use type.GetMethod(string, BindingFlags, null, Type[], null) + // MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + // if (method == null) + // { + // STrace.LogExThrow(); + // throw new ArgumentException("Couldn't find method by reflection"); + // } + + // return method.Invoke(null, parameters); + //} + + public static bool IsYukonOrAbove(SMO.Server server) + { + return server.Version.Major >= 9; + } + + public static bool IsBelowYukon(SMO.Server server) + { + return server.Version.Major < 9; + } + + /// + /// Some calendars, such as the UmAlQuraCalendar, support an upper date range that is earlier than MaxValue. + /// In these cases, trying to access MaxValue in variable assignments or formatting and parsing operations can throw + /// an ArgumentOutOfRangeException. Rather than retrieving the value of DateTime.MaxValue, you can retrieve the value + /// of the specified culture's latest valid date value from the + /// System.Globalization.CultureInfo.DateTimeFormat.Calendar.MaxSupportedDateTime property. + /// http://msdn.microsoft.com/en-us/library/system.datetime.maxvalue(v=VS.90).aspx + /// + /// + //public static DateTime GetMaxCultureDateTime() + //{ + // CultureInfo currentCulture = System.Threading.Thread.CurrentThread.CU; + // return currentCulture.DateTimeFormat.Calendar.MaxSupportedDateTime; + //} + + public static string MakeSqlBracket(string s) + { + return "[" + s.Replace("]", "]]") + "]"; + } + + /// + /// Displays F1 Help link + /// + /// Service provider to display help + /// F1 help link + //public static void DisplayHelp(IServiceProvider serviceProvider, string dialogF1Keyword) + //{ + // if (serviceProvider == null) + // { + // return; + // } + // IHelpService helpService = (IHelpService)serviceProvider.GetService(typeof(IHelpService)); + // if (helpService == null) + // { + // IHelpProvider helpProvider = (IHelpProvider)serviceProvider.GetService(typeof(IHelpProvider)); + // if (helpProvider != null) + // { + // helpProvider.DisplayTopicFromF1Keyword(dialogF1Keyword); + // } + // } + // else + // { + // helpService.DisplayHelp(dialogF1Keyword); + // } + //} + } + + /// + /// Public reusable helpers + /// + public class SqlMgmtUtils + { + /// + /// only static methods + /// + private SqlMgmtUtils() { } + + /// + /// Returns whether the server is in AS Azure + /// + /// + /// + public static bool IsASAzure(string serverName) + { + return !string.IsNullOrEmpty(serverName) && serverName.StartsWith("asazure://", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/CreateDatabaseRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/CreateDatabaseRequest.cs index d223bc88..4b42011c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/CreateDatabaseRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/CreateDatabaseRequest.cs @@ -4,10 +4,6 @@ // using Microsoft.SqlTools.Hosting.Protocol.Contracts; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Microsoft.SqlTools.ServiceLayer.Admin.Contracts { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DatabaseInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DatabaseInfo.cs index 8579f8f8..cd40d0a1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DatabaseInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DatabaseInfo.cs @@ -3,10 +3,24 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.SqlTools.Utility; + namespace Microsoft.SqlTools.ServiceLayer.Admin.Contracts { public class DatabaseInfo { - public string Name { get; set; } + /// + /// Gets or sets the options + /// + public Dictionary Options { get; set; } + + public DatabaseInfo() + { + Options = new Dictionary(); + } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DefaultDatabaseInfoRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DefaultDatabaseInfoRequest.cs new file mode 100644 index 00000000..85eb8d11 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Contracts/DefaultDatabaseInfoRequest.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Admin.Contracts +{ + + public class DefaultDatabaseInfoParams + { + public string OwnerUri { get; set; } + } + + public class DefaultDatabaseInfoResponse + { + public DatabaseInfo DefaultDatabaseInfo { get; set; } + } + + + public class DefaultDatabaseInfoRequest + { + public static readonly + RequestType Type = + RequestType.Create("admin/defaultdatabaseinfo"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionProviderOptionsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionProviderOptionsHelper.cs new file mode 100644 index 00000000..1efc3f64 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionProviderOptionsHelper.cs @@ -0,0 +1,300 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Connection +{ + /// + /// Helper class for providing metadata about connection options + /// + internal class ConnectionProviderOptionsHelper + { + internal static ConnectionProviderOptions BuildConnectionProviderOptions() + { + return new ConnectionProviderOptions + { + Options = new ConnectionOption[] + { + new ConnectionOption + { + Name = "server", + DisplayName = "Server Name", + Description = "Name of the SQL Server instance", + ValueType = ConnectionOption.ValueTypeString, + SpecialValueType = ConnectionOption.SpecialValueServerName, + IsIdentity = true, + IsRequired = true, + GroupName = "Source" + }, + new ConnectionOption + { + Name = "database", + DisplayName = "Database Name", + Description = "The name of the initial catalog or database int the data source", + ValueType = ConnectionOption.ValueTypeString, + SpecialValueType = ConnectionOption.SpecialValueDatabaseName, + IsIdentity = true, + IsRequired = false, + GroupName = "Source" + }, + new ConnectionOption + { + Name = "authenticationType", + DisplayName = "Authentication Type", + Description = "Specifies the method of authenticating with SQL Server", + ValueType = ConnectionOption.ValueTypeCategory, + SpecialValueType = ConnectionOption.SpecialValueAuthType, + CategoryValues = new CategoryValue[] + { new CategoryValue {DisplayName = "SQL Login", Name = "SqlLogin" }, + new CategoryValue {DisplayName = "Integrated Auth", Name= "Integrated" } + }, + IsIdentity = true, + IsRequired = true, + GroupName = "Security" + }, + new ConnectionOption + { + Name = "user", + DisplayName = "User Name", + Description = "Indicates the user ID to be used when connecting to the data source", + ValueType = ConnectionOption.ValueTypeString, + SpecialValueType = ConnectionOption.SpecialValueUserName, + IsIdentity = true, + IsRequired = true, + GroupName = "Security" + }, + new ConnectionOption + { + Name = "password", + DisplayName = "Password", + Description = "Indicates the password to be used when connecting to the data source", + ValueType = ConnectionOption.ValueTypePassword, + SpecialValueType = ConnectionOption.SpecialValuePasswordName, + IsIdentity = true, + IsRequired = true, + GroupName = "Security" + }, + new ConnectionOption + { + Name = "applicationIntent", + DisplayName = "Application Intent", + Description = "Declares the application workload type when connecting to a server", + ValueType = ConnectionOption.ValueTypeCategory, + CategoryValues = new CategoryValue[] { + new CategoryValue { Name = "ReadWrite", DisplayName = "ReadWrite" }, + new CategoryValue {Name = "ReadOnly", DisplayName = "ReadOnly" } + }, + GroupName = "Initialization" + }, + new ConnectionOption + { + Name = "asynchronousProcessing", + DisplayName = "Asynchronous processing enabled", + Description = "When true, enables usage of the Asynchronous functionality in the .Net Framework Data Provider", + ValueType = ConnectionOption.ValueTypeBoolean, + GroupName = "Initialization" + }, + new ConnectionOption + { + Name = "connectTimeout", + DisplayName = "Connect Timeout", + Description = + "The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error", + ValueType = ConnectionOption.ValueTypeNumber, + DefaultValue = "15", + GroupName = "Initialization" + }, + new ConnectionOption + { + Name = "currentLanguage", + DisplayName = "Current Language", + Description = "The SQL Server language record name", + ValueType = ConnectionOption.ValueTypeString, + GroupName = "Initialization" + }, + new ConnectionOption + { + Name = "columnEncryptionSetting", + DisplayName = "Column Encryption Setting", + Description = "Default column encryption setting for all the commands on the connection", + ValueType = ConnectionOption.ValueTypeCategory, + GroupName = "Security", + CategoryValues = new CategoryValue[] { + new CategoryValue { Name = "Disabled" }, + new CategoryValue {Name = "Enabled" } + } + }, + new ConnectionOption + { + Name = "encrypt", + DisplayName = "Encrypt", + Description = + "When true, SQL Server uses SSL encryption for all data sent between the client and server if the servers has a certificate installed", + GroupName = "Security", + ValueType = ConnectionOption.ValueTypeBoolean + }, + new ConnectionOption + { + Name = "persistSecurityInfo", + DisplayName = "Persist Security Info", + Description = "When false, security-sensitive information, such as the password, is not returned as part of the connection", + GroupName = "Security", + ValueType = ConnectionOption.ValueTypeBoolean + }, + new ConnectionOption + { + Name = "trustServerCertificate", + DisplayName = "Trust Server Certificate", + Description = "When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate", + GroupName = "Security", + ValueType = ConnectionOption.ValueTypeBoolean + }, + new ConnectionOption + { + Name = "attachedDBFileName", + DisplayName = "Attached DB File Name", + Description = "The name of the primary file, including the full path name, of an attachable database", + ValueType = ConnectionOption.ValueTypeString, + GroupName = "Source" + }, + new ConnectionOption + { + Name = "contextConnection", + DisplayName = "Context Connection", + Description = "When true, indicates the connection should be from the SQL server context. Available only when running in the SQL Server process", + ValueType = ConnectionOption.ValueTypeBoolean, + GroupName = "Source" + }, + new ConnectionOption + { + Name = "port", + DisplayName = "Port", + ValueType = ConnectionOption.ValueTypeNumber + }, + new ConnectionOption + { + Name = "connectRetryCount", + DisplayName = "Connect Retry Count", + Description = "Number of attempts to restore connection", + ValueType = ConnectionOption.ValueTypeNumber, + DefaultValue = "1", + GroupName = "Connection Resiliency" + }, + new ConnectionOption + { + Name = "connectRetryInterval", + DisplayName = "Connect Retry Interval", + Description = "Delay between attempts to restore connection", + ValueType = ConnectionOption.ValueTypeNumber, + DefaultValue = "10", + GroupName = "Connection Resiliency" + + }, + new ConnectionOption + { + Name = "applicationName", + DisplayName = "Application Name", + Description = "The name of the application", + ValueType = ConnectionOption.ValueTypeString, + GroupName = "Context" + }, + new ConnectionOption + { + Name = "workstationId", + DisplayName = "Workstation Id", + Description = "The name of the workstation connecting to SQL Server", + ValueType = ConnectionOption.ValueTypeString, + GroupName = "Context" + }, + new ConnectionOption + { + Name = "pooling", + DisplayName = "Pooling", + Description = "When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool", + ValueType = ConnectionOption.ValueTypeBoolean, + GroupName = "Pooling" + }, + new ConnectionOption + { + Name = "maxPoolSize", + DisplayName = "Max Pool Size", + Description = "The maximum number of connections allowed in the pool", + ValueType = ConnectionOption.ValueTypeNumber, + GroupName = "Pooling" + }, + new ConnectionOption + { + Name = "minPoolSize", + DisplayName = "Min Pool Size", + Description = "The minimum number of connections allowed in the pool", + ValueType = ConnectionOption.ValueTypeNumber, + GroupName = "Pooling" + }, + new ConnectionOption + { + Name = "loadBalanceTimeout", + DisplayName = "Load Balance Timeout", + Description = "The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed", + ValueType = ConnectionOption.ValueTypeNumber, + GroupName = "Pooling" + }, + new ConnectionOption + { + Name = "replication", + DisplayName = "Replication", + Description = "Used by SQL Server in Replication", + ValueType = ConnectionOption.ValueTypeBoolean, + GroupName = "Replication" + }, + new ConnectionOption + { + Name = "attachDbFilename", + DisplayName = "Attach Db Filename", + ValueType = ConnectionOption.ValueTypeString + }, + new ConnectionOption + { + Name = "failoverPartner", + DisplayName = "Failover Partner", + Description = "the name or network address of the instance of SQL Server that acts as a failover partner", + ValueType = ConnectionOption.ValueTypeString, + GroupName = " Source" + }, + new ConnectionOption + { + Name = "multiSubnetFailover", + DisplayName = "Multi Subnet Failover", + ValueType = ConnectionOption.ValueTypeBoolean + }, + new ConnectionOption + { + Name = "multipleActiveResultSets", + DisplayName = "Multiple Active ResultSets", + Description = "When true, multiple result sets can be returned and read from one connection", + ValueType = ConnectionOption.ValueTypeBoolean, + GroupName = "Advanced" + }, + new ConnectionOption + { + Name = "packetSize", + DisplayName = "Packet Size", + Description = "Size in bytes of the network packets used to communicate with an instance of SQL Server", + ValueType = ConnectionOption.ValueTypeNumber, + GroupName = "Advanced" + }, + new ConnectionOption + { + Name = "typeSystemVersion", + DisplayName = "Type System Version", + Description = "Indicates which server type system then provider will expose through the DataReader", + ValueType = ConnectionOption.ValueTypeString, + GroupName = "Advanced" + } + } + }; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 9c45ec30..42931751 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -1,1076 +1,1076 @@ -// -// 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.Concurrent; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Data.SqlClient; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; -using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; -using Microsoft.SqlTools.ServiceLayer.SqlContext; -using Microsoft.SqlTools.ServiceLayer.Workspace; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlTools.Utility; - -namespace Microsoft.SqlTools.ServiceLayer.Connection -{ - /// - /// Main class for the Connection Management services - /// - public class ConnectionService - { - /// - /// Singleton service instance - /// - private static readonly Lazy instance - = new Lazy(() => new ConnectionService()); - - /// - /// Gets the singleton service instance - /// - public static ConnectionService Instance - { - get - { - return instance.Value; - } - } - - /// - /// The SQL connection factory object - /// - private ISqlConnectionFactory connectionFactory; - - private readonly Dictionary ownerToConnectionMap = new Dictionary(); - - /// - /// A map containing all CancellationTokenSource objects that are associated with a given URI/ConnectionType pair. - /// Entries in this map correspond to DbConnection instances that are in the process of connecting. - /// - private readonly ConcurrentDictionary cancelTupleToCancellationTokenSourceMap = - new ConcurrentDictionary(); - - private readonly object cancellationTokenSourceLock = new object(); - - /// - /// Map from script URIs to ConnectionInfo objects - /// This is internal for testing access only - /// - internal Dictionary OwnerToConnectionMap - { - get - { - return this.ownerToConnectionMap; - } - } - - /// - /// Service host object for sending/receiving requests/events. - /// Internal for testing purposes. - /// - internal IProtocolEndpoint ServiceHost - { - get; - set; - } - - /// - /// Default constructor should be private since it's a singleton class, but we need a constructor - /// for use in unit test mocking. - /// - public ConnectionService() - { - } - - /// - /// Callback for onconnection handler - /// - /// - public delegate Task OnConnectionHandler(ConnectionInfo info); - - /// - /// Callback for ondisconnect handler - /// - public delegate Task OnDisconnectHandler(ConnectionSummary summary, string ownerUri); - - /// - /// List of onconnection handlers - /// - private readonly List onConnectionActivities = new List(); - - /// - /// List of ondisconnect handlers - /// - private readonly List onDisconnectActivities = new List(); - - /// - /// Gets the SQL connection factory instance - /// - public ISqlConnectionFactory ConnectionFactory - { - get - { - if (this.connectionFactory == null) - { - this.connectionFactory = new SqlConnectionFactory(); - } - return this.connectionFactory; - } - - internal set { this.connectionFactory = value; } - } - - /// - /// Test constructor that injects dependency interfaces - /// - /// - public ConnectionService(ISqlConnectionFactory testFactory) - { - this.connectionFactory = testFactory; - } - - // Attempts to link a URI to an actively used connection for this URI - public virtual bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo) - { - return this.ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo); - } - - /// - /// Validates the given ConnectParams object. - /// - /// The params to validate - /// A ConnectionCompleteParams object upon validation error, - /// null upon validation success - public ConnectionCompleteParams ValidateConnectParams(ConnectParams connectionParams) - { - string paramValidationErrorMessage; - if (connectionParams == null) - { - return new ConnectionCompleteParams - { - Messages = SR.ConnectionServiceConnectErrorNullParams - }; - } - if (!connectionParams.IsValid(out paramValidationErrorMessage)) - { - return new ConnectionCompleteParams - { - OwnerUri = connectionParams.OwnerUri, - Messages = paramValidationErrorMessage - }; - } - - // return null upon success - return null; - } - - /// - /// Open a connection with the specified ConnectParams - /// - public virtual async Task Connect(ConnectParams connectionParams) - { - // Validate parameters - ConnectionCompleteParams validationResults = ValidateConnectParams(connectionParams); - if (validationResults != null) - { - return validationResults; - } - - // 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; - bool connectionChanged = false; - if (!ownerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) - { - connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); - } - else if (IsConnectionChanged(connectionParams, connectionInfo)) - { - // We are actively changing the connection information for this connection. We must disconnect - // all active connections, since it represents a full context change - connectionChanged = true; - } - - DisconnectExistingConnectionIfNeeded(connectionParams, connectionInfo, disconnectAll: connectionChanged); - - if (connectionChanged) - { - connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); - } - - // Try to open a connection with the given ConnectParams - ConnectionCompleteParams response = await TryOpenConnection(connectionInfo, connectionParams); - if (response != null) - { - return response; - } - - // If this is the first connection for this URI, add the ConnectionInfo to the map - bool addToMap = connectionChanged || !ownerToConnectionMap.ContainsKey(connectionParams.OwnerUri); - if (addToMap) - { - ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo; - } - - // Return information about the connected SQL Server instance - ConnectionCompleteParams completeParams = GetConnectionCompleteParams(connectionParams.Type, connectionInfo); - // Invoke callback notifications - InvokeOnConnectionActivities(connectionInfo, connectionParams); - - return completeParams; - } - - private bool IsConnectionChanged(ConnectParams connectionParams, ConnectionInfo connectionInfo) - { - if (connectionInfo.HasConnectionType(connectionParams.Type) - && !connectionInfo.ConnectionDetails.IsComparableTo(connectionParams.Connection)) - { - return true; - } - return false; - } - - private bool IsDefaultConnectionType(string connectionType) - { - return string.IsNullOrEmpty(connectionType) || ConnectionType.Default.Equals(connectionType, StringComparison.CurrentCultureIgnoreCase); - } - - private void DisconnectExistingConnectionIfNeeded(ConnectParams connectionParams, ConnectionInfo connectionInfo, bool disconnectAll) - { - // Resolve if it is an existing connection - // Disconnect active connection if the URI is already connected for this connection type - DbConnection existingConnection; - if (connectionInfo.TryGetConnection(connectionParams.Type, out existingConnection)) - { - var disconnectParams = new DisconnectParams() - { - OwnerUri = connectionParams.OwnerUri, - Type = disconnectAll ? null : connectionParams.Type - }; - Disconnect(disconnectParams); - } - } - - /// - /// Creates a ConnectionCompleteParams as a response to a successful connection. - /// Also sets the DatabaseName and IsAzure properties of ConnectionInfo. - /// - /// A ConnectionCompleteParams in response to the successful connection - private ConnectionCompleteParams GetConnectionCompleteParams(string connectionType, ConnectionInfo connectionInfo) - { - ConnectionCompleteParams response = new ConnectionCompleteParams { OwnerUri = connectionInfo.OwnerUri, Type = connectionType }; - - try - { - DbConnection connection; - connectionInfo.TryGetConnection(connectionType, out connection); - - // Update with the actual database name in connectionInfo and result - // Doing this here as we know the connection is open - expect to do this only on connecting - connectionInfo.ConnectionDetails.DatabaseName = connection.Database; - if (!string.IsNullOrEmpty(connectionInfo.ConnectionDetails.ConnectionString)) - { - // If the connection was set up with a connection string, use the connection string to get the details - var connectionString = new SqlConnectionStringBuilder(connection.ConnectionString); - response.ConnectionSummary = new ConnectionSummary - { - ServerName = connectionString.DataSource, - DatabaseName = connectionString.InitialCatalog, - UserName = connectionString.UserID - }; - } - else - { - response.ConnectionSummary = new ConnectionSummary - { - ServerName = connectionInfo.ConnectionDetails.ServerName, - DatabaseName = connectionInfo.ConnectionDetails.DatabaseName, - UserName = connectionInfo.ConnectionDetails.UserName - }; - } - - response.ConnectionId = connectionInfo.ConnectionId.ToString(); - - var reliableConnection = connection as ReliableSqlConnection; - DbConnection underlyingConnection = reliableConnection != null - ? reliableConnection.GetUnderlyingConnection() - : connection; - - ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(underlyingConnection); - response.ServerInfo = new ServerInfo - { - ServerMajorVersion = serverInfo.ServerMajorVersion, - ServerMinorVersion = serverInfo.ServerMinorVersion, - ServerReleaseVersion = serverInfo.ServerReleaseVersion, - EngineEditionId = serverInfo.EngineEditionId, - ServerVersion = serverInfo.ServerVersion, - ServerLevel = serverInfo.ServerLevel, - ServerEdition = serverInfo.ServerEdition, - IsCloud = serverInfo.IsCloud, - AzureVersion = serverInfo.AzureVersion, - OsVersion = serverInfo.OsVersion - }; - connectionInfo.IsAzure = serverInfo.IsCloud; - connectionInfo.MajorVersion = serverInfo.ServerMajorVersion; - connectionInfo.IsSqlDW = (serverInfo.EngineEditionId == (int)DatabaseEngineEdition.SqlDataWarehouse); - } - catch (Exception ex) - { - response.Messages = ex.ToString(); - } - - return response; - } - - /// - /// Tries to create and open a connection with the given ConnectParams. - /// - /// null upon success, a ConnectionCompleteParams detailing the error upon failure - private async Task TryOpenConnection(ConnectionInfo connectionInfo, ConnectParams connectionParams) - { - CancellationTokenSource source = null; - DbConnection connection = null; - CancelTokenKey cancelKey = new CancelTokenKey { OwnerUri = connectionParams.OwnerUri, Type = connectionParams.Type }; - ConnectionCompleteParams response = new ConnectionCompleteParams { OwnerUri = connectionInfo.OwnerUri, Type = connectionParams.Type }; - - try - { - // build the connection string from the input parameters - string connectionString = BuildConnectionString(connectionInfo.ConnectionDetails); - - // create a sql connection instance - connection = connectionInfo.Factory.CreateSqlConnection(connectionString); - connectionInfo.AddConnection(connectionParams.Type, connection); - - // Add a cancellation token source so that the connection OpenAsync() can be cancelled - source = new CancellationTokenSource(); - // Locking here to perform two operations as one atomic operation - lock (cancellationTokenSourceLock) - { - // If the URI is currently connecting from a different request, cancel it before we try to connect - CancellationTokenSource currentSource; - if (cancelTupleToCancellationTokenSourceMap.TryGetValue(cancelKey, out currentSource)) - { - currentSource.Cancel(); - } - cancelTupleToCancellationTokenSourceMap[cancelKey] = source; - } - - // Open the connection - await connection.OpenAsync(source.Token); - } - catch (SqlException ex) - { - response.ErrorNumber = ex.Number; - response.ErrorMessage = ex.Message; - response.Messages = ex.ToString(); - return response; - } - catch (OperationCanceledException) - { - // OpenAsync was cancelled - response.Messages = SR.ConnectionServiceConnectionCanceled; - return response; - } - catch (Exception ex) - { - response.ErrorMessage = ex.Message; - response.Messages = ex.ToString(); - return response; - } - finally - { - // Remove our cancellation token from the map since we're no longer connecting - // Using a lock here to perform two operations as one atomic operation - lock (cancellationTokenSourceLock) - { - // Only remove the token from the map if it is the same one created by this request - CancellationTokenSource sourceValue; - if (cancelTupleToCancellationTokenSourceMap.TryGetValue(cancelKey, out sourceValue) && sourceValue == source) - { - cancelTupleToCancellationTokenSourceMap.TryRemove(cancelKey, out sourceValue); - } - source?.Dispose(); - } - } - - // Return null upon success - return null; - } - - /// - /// Gets the existing connection with the given URI and connection type string. If none exists, - /// creates a new connection. This cannot be used to create a default connection or to create a - /// connection if a default connection does not exist. - /// - /// A DB connection for the connection type requested - public async Task GetOrOpenConnection(string ownerUri, string connectionType) - { - Validate.IsNotNullOrEmptyString(nameof(ownerUri), ownerUri); - Validate.IsNotNullOrEmptyString(nameof(connectionType), connectionType); - - // Try to get the ConnectionInfo, if it exists - ConnectionInfo connectionInfo; - if (!ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo)) - { - throw new ArgumentOutOfRangeException(SR.ConnectionServiceListDbErrorNotConnected(ownerUri)); - } - - // Make sure a default connection exists - DbConnection defaultConnection; - if (!connectionInfo.TryGetConnection(ConnectionType.Default, out defaultConnection)) - { - throw new InvalidOperationException(SR.ConnectionServiceDbErrorDefaultNotConnected(ownerUri)); - } - - // Try to get the DbConnection - DbConnection connection; - if (!connectionInfo.TryGetConnection(connectionType, out connection) && ConnectionType.Default != connectionType) - { - // If the DbConnection does not exist and is not the default connection, create one. - // We can't create the default (initial) connection here because we won't have a ConnectionDetails - // if Connect() has not yet been called. - ConnectParams connectParams = new ConnectParams - { - OwnerUri = ownerUri, - Connection = connectionInfo.ConnectionDetails, - Type = connectionType - }; - await Connect(connectParams); - connectionInfo.TryGetConnection(connectionType, out connection); - } - - return connection; - } - - /// - /// Cancel a connection that is in the process of opening. - /// - public bool CancelConnect(CancelConnectParams cancelParams) - { - // Validate parameters - if (cancelParams == null || string.IsNullOrEmpty(cancelParams.OwnerUri)) - { - return false; - } - - CancelTokenKey cancelKey = new CancelTokenKey - { - OwnerUri = cancelParams.OwnerUri, - Type = cancelParams.Type - }; - - // Cancel any current connection attempts for this URI - CancellationTokenSource source; - if (cancelTupleToCancellationTokenSourceMap.TryGetValue(cancelKey, out source)) - { - try - { - source.Cancel(); - return true; - } - catch - { - return false; - } - } - - return false; - } - - /// - /// Close a connection with the specified connection details. - /// +// +// 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.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Workspace; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Connection +{ + /// + /// Main class for the Connection Management services + /// + public class ConnectionService + { + /// + /// Singleton service instance + /// + private static readonly Lazy instance + = new Lazy(() => new ConnectionService()); + + /// + /// Gets the singleton service instance + /// + public static ConnectionService Instance + { + get + { + return instance.Value; + } + } + + /// + /// The SQL connection factory object + /// + private ISqlConnectionFactory connectionFactory; + + private readonly Dictionary ownerToConnectionMap = new Dictionary(); + + /// + /// A map containing all CancellationTokenSource objects that are associated with a given URI/ConnectionType pair. + /// Entries in this map correspond to DbConnection instances that are in the process of connecting. + /// + private readonly ConcurrentDictionary cancelTupleToCancellationTokenSourceMap = + new ConcurrentDictionary(); + + private readonly object cancellationTokenSourceLock = new object(); + + /// + /// Map from script URIs to ConnectionInfo objects + /// This is internal for testing access only + /// + internal Dictionary OwnerToConnectionMap + { + get + { + return this.ownerToConnectionMap; + } + } + + /// + /// Service host object for sending/receiving requests/events. + /// Internal for testing purposes. + /// + internal IProtocolEndpoint ServiceHost + { + get; + set; + } + + /// + /// Default constructor should be private since it's a singleton class, but we need a constructor + /// for use in unit test mocking. + /// + public ConnectionService() + { + } + + /// + /// Callback for onconnection handler + /// + /// + public delegate Task OnConnectionHandler(ConnectionInfo info); + + /// + /// Callback for ondisconnect handler + /// + public delegate Task OnDisconnectHandler(ConnectionSummary summary, string ownerUri); + + /// + /// List of onconnection handlers + /// + private readonly List onConnectionActivities = new List(); + + /// + /// List of ondisconnect handlers + /// + private readonly List onDisconnectActivities = new List(); + + /// + /// Gets the SQL connection factory instance + /// + public ISqlConnectionFactory ConnectionFactory + { + get + { + if (this.connectionFactory == null) + { + this.connectionFactory = new SqlConnectionFactory(); + } + return this.connectionFactory; + } + + internal set { this.connectionFactory = value; } + } + + /// + /// Test constructor that injects dependency interfaces + /// + /// + public ConnectionService(ISqlConnectionFactory testFactory) + { + this.connectionFactory = testFactory; + } + + // Attempts to link a URI to an actively used connection for this URI + public virtual bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo) + { + return this.ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo); + } + + /// + /// Validates the given ConnectParams object. + /// + /// The params to validate + /// A ConnectionCompleteParams object upon validation error, + /// null upon validation success + public ConnectionCompleteParams ValidateConnectParams(ConnectParams connectionParams) + { + string paramValidationErrorMessage; + if (connectionParams == null) + { + return new ConnectionCompleteParams + { + Messages = SR.ConnectionServiceConnectErrorNullParams + }; + } + if (!connectionParams.IsValid(out paramValidationErrorMessage)) + { + return new ConnectionCompleteParams + { + OwnerUri = connectionParams.OwnerUri, + Messages = paramValidationErrorMessage + }; + } + + // return null upon success + return null; + } + + /// + /// Open a connection with the specified ConnectParams + /// + public virtual async Task Connect(ConnectParams connectionParams) + { + // Validate parameters + ConnectionCompleteParams validationResults = ValidateConnectParams(connectionParams); + if (validationResults != null) + { + return validationResults; + } + + // 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; + bool connectionChanged = false; + if (!ownerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) + { + connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); + } + else if (IsConnectionChanged(connectionParams, connectionInfo)) + { + // We are actively changing the connection information for this connection. We must disconnect + // all active connections, since it represents a full context change + connectionChanged = true; + } + + DisconnectExistingConnectionIfNeeded(connectionParams, connectionInfo, disconnectAll: connectionChanged); + + if (connectionChanged) + { + connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); + } + + // Try to open a connection with the given ConnectParams + ConnectionCompleteParams response = await TryOpenConnection(connectionInfo, connectionParams); + if (response != null) + { + return response; + } + + // If this is the first connection for this URI, add the ConnectionInfo to the map + bool addToMap = connectionChanged || !ownerToConnectionMap.ContainsKey(connectionParams.OwnerUri); + if (addToMap) + { + ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo; + } + + // Return information about the connected SQL Server instance + ConnectionCompleteParams completeParams = GetConnectionCompleteParams(connectionParams.Type, connectionInfo); + // Invoke callback notifications + InvokeOnConnectionActivities(connectionInfo, connectionParams); + + return completeParams; + } + + private bool IsConnectionChanged(ConnectParams connectionParams, ConnectionInfo connectionInfo) + { + if (connectionInfo.HasConnectionType(connectionParams.Type) + && !connectionInfo.ConnectionDetails.IsComparableTo(connectionParams.Connection)) + { + return true; + } + return false; + } + + private bool IsDefaultConnectionType(string connectionType) + { + return string.IsNullOrEmpty(connectionType) || ConnectionType.Default.Equals(connectionType, StringComparison.CurrentCultureIgnoreCase); + } + + private void DisconnectExistingConnectionIfNeeded(ConnectParams connectionParams, ConnectionInfo connectionInfo, bool disconnectAll) + { + // Resolve if it is an existing connection + // Disconnect active connection if the URI is already connected for this connection type + DbConnection existingConnection; + if (connectionInfo.TryGetConnection(connectionParams.Type, out existingConnection)) + { + var disconnectParams = new DisconnectParams() + { + OwnerUri = connectionParams.OwnerUri, + Type = disconnectAll ? null : connectionParams.Type + }; + Disconnect(disconnectParams); + } + } + + /// + /// Creates a ConnectionCompleteParams as a response to a successful connection. + /// Also sets the DatabaseName and IsAzure properties of ConnectionInfo. + /// + /// A ConnectionCompleteParams in response to the successful connection + private ConnectionCompleteParams GetConnectionCompleteParams(string connectionType, ConnectionInfo connectionInfo) + { + ConnectionCompleteParams response = new ConnectionCompleteParams { OwnerUri = connectionInfo.OwnerUri, Type = connectionType }; + + try + { + DbConnection connection; + connectionInfo.TryGetConnection(connectionType, out connection); + + // Update with the actual database name in connectionInfo and result + // Doing this here as we know the connection is open - expect to do this only on connecting + connectionInfo.ConnectionDetails.DatabaseName = connection.Database; + if (!string.IsNullOrEmpty(connectionInfo.ConnectionDetails.ConnectionString)) + { + // If the connection was set up with a connection string, use the connection string to get the details + var connectionString = new SqlConnectionStringBuilder(connection.ConnectionString); + response.ConnectionSummary = new ConnectionSummary + { + ServerName = connectionString.DataSource, + DatabaseName = connectionString.InitialCatalog, + UserName = connectionString.UserID + }; + } + else + { + response.ConnectionSummary = new ConnectionSummary + { + ServerName = connectionInfo.ConnectionDetails.ServerName, + DatabaseName = connectionInfo.ConnectionDetails.DatabaseName, + UserName = connectionInfo.ConnectionDetails.UserName + }; + } + + response.ConnectionId = connectionInfo.ConnectionId.ToString(); + + var reliableConnection = connection as ReliableSqlConnection; + DbConnection underlyingConnection = reliableConnection != null + ? reliableConnection.GetUnderlyingConnection() + : connection; + + ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(underlyingConnection); + response.ServerInfo = new ServerInfo + { + ServerMajorVersion = serverInfo.ServerMajorVersion, + ServerMinorVersion = serverInfo.ServerMinorVersion, + ServerReleaseVersion = serverInfo.ServerReleaseVersion, + EngineEditionId = serverInfo.EngineEditionId, + ServerVersion = serverInfo.ServerVersion, + ServerLevel = serverInfo.ServerLevel, + ServerEdition = serverInfo.ServerEdition, + IsCloud = serverInfo.IsCloud, + AzureVersion = serverInfo.AzureVersion, + OsVersion = serverInfo.OsVersion + }; + connectionInfo.IsAzure = serverInfo.IsCloud; + connectionInfo.MajorVersion = serverInfo.ServerMajorVersion; + connectionInfo.IsSqlDW = (serverInfo.EngineEditionId == (int)DatabaseEngineEdition.SqlDataWarehouse); + } + catch (Exception ex) + { + response.Messages = ex.ToString(); + } + + return response; + } + + /// + /// Tries to create and open a connection with the given ConnectParams. + /// + /// null upon success, a ConnectionCompleteParams detailing the error upon failure + private async Task TryOpenConnection(ConnectionInfo connectionInfo, ConnectParams connectionParams) + { + CancellationTokenSource source = null; + DbConnection connection = null; + CancelTokenKey cancelKey = new CancelTokenKey { OwnerUri = connectionParams.OwnerUri, Type = connectionParams.Type }; + ConnectionCompleteParams response = new ConnectionCompleteParams { OwnerUri = connectionInfo.OwnerUri, Type = connectionParams.Type }; + + try + { + // build the connection string from the input parameters + string connectionString = BuildConnectionString(connectionInfo.ConnectionDetails); + + // create a sql connection instance + connection = connectionInfo.Factory.CreateSqlConnection(connectionString); + connectionInfo.AddConnection(connectionParams.Type, connection); + + // Add a cancellation token source so that the connection OpenAsync() can be cancelled + source = new CancellationTokenSource(); + // Locking here to perform two operations as one atomic operation + lock (cancellationTokenSourceLock) + { + // If the URI is currently connecting from a different request, cancel it before we try to connect + CancellationTokenSource currentSource; + if (cancelTupleToCancellationTokenSourceMap.TryGetValue(cancelKey, out currentSource)) + { + currentSource.Cancel(); + } + cancelTupleToCancellationTokenSourceMap[cancelKey] = source; + } + + // Open the connection + await connection.OpenAsync(source.Token); + } + catch (SqlException ex) + { + response.ErrorNumber = ex.Number; + response.ErrorMessage = ex.Message; + response.Messages = ex.ToString(); + return response; + } + catch (OperationCanceledException) + { + // OpenAsync was cancelled + response.Messages = SR.ConnectionServiceConnectionCanceled; + return response; + } + catch (Exception ex) + { + response.ErrorMessage = ex.Message; + response.Messages = ex.ToString(); + return response; + } + finally + { + // Remove our cancellation token from the map since we're no longer connecting + // Using a lock here to perform two operations as one atomic operation + lock (cancellationTokenSourceLock) + { + // Only remove the token from the map if it is the same one created by this request + CancellationTokenSource sourceValue; + if (cancelTupleToCancellationTokenSourceMap.TryGetValue(cancelKey, out sourceValue) && sourceValue == source) + { + cancelTupleToCancellationTokenSourceMap.TryRemove(cancelKey, out sourceValue); + } + source?.Dispose(); + } + } + + // Return null upon success + return null; + } + + /// + /// Gets the existing connection with the given URI and connection type string. If none exists, + /// creates a new connection. This cannot be used to create a default connection or to create a + /// connection if a default connection does not exist. + /// + /// A DB connection for the connection type requested + public async Task GetOrOpenConnection(string ownerUri, string connectionType) + { + Validate.IsNotNullOrEmptyString(nameof(ownerUri), ownerUri); + Validate.IsNotNullOrEmptyString(nameof(connectionType), connectionType); + + // Try to get the ConnectionInfo, if it exists + ConnectionInfo connectionInfo; + if (!ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo)) + { + throw new ArgumentOutOfRangeException(SR.ConnectionServiceListDbErrorNotConnected(ownerUri)); + } + + // Make sure a default connection exists + DbConnection defaultConnection; + if (!connectionInfo.TryGetConnection(ConnectionType.Default, out defaultConnection)) + { + throw new InvalidOperationException(SR.ConnectionServiceDbErrorDefaultNotConnected(ownerUri)); + } + + // Try to get the DbConnection + DbConnection connection; + if (!connectionInfo.TryGetConnection(connectionType, out connection) && ConnectionType.Default != connectionType) + { + // If the DbConnection does not exist and is not the default connection, create one. + // We can't create the default (initial) connection here because we won't have a ConnectionDetails + // if Connect() has not yet been called. + ConnectParams connectParams = new ConnectParams + { + OwnerUri = ownerUri, + Connection = connectionInfo.ConnectionDetails, + Type = connectionType + }; + await Connect(connectParams); + connectionInfo.TryGetConnection(connectionType, out connection); + } + + return connection; + } + + /// + /// Cancel a connection that is in the process of opening. + /// + public bool CancelConnect(CancelConnectParams cancelParams) + { + // Validate parameters + if (cancelParams == null || string.IsNullOrEmpty(cancelParams.OwnerUri)) + { + return false; + } + + CancelTokenKey cancelKey = new CancelTokenKey + { + OwnerUri = cancelParams.OwnerUri, + Type = cancelParams.Type + }; + + // Cancel any current connection attempts for this URI + CancellationTokenSource source; + if (cancelTupleToCancellationTokenSourceMap.TryGetValue(cancelKey, out source)) + { + try + { + source.Cancel(); + return true; + } + catch + { + return false; + } + } + + return false; + } + + /// + /// Close a connection with the specified connection details. + /// public virtual bool Disconnect(DisconnectParams disconnectParams) - { - // Validate parameters - if (disconnectParams == null || string.IsNullOrEmpty(disconnectParams.OwnerUri)) - { - return false; - } - - // Cancel if we are in the middle of connecting - if (CancelConnections(disconnectParams.OwnerUri, disconnectParams.Type)) - { - return false; - } - - // Lookup the ConnectionInfo owned by the URI - ConnectionInfo info; - if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info)) - { - return false; - } - - // Call Close() on the connections we want to disconnect - // If no connections were located, return false - if (!CloseConnections(info, disconnectParams.Type)) - { - return false; - } - - // Remove the disconnected connections from the ConnectionInfo map - if (disconnectParams.Type == null) - { - info.RemoveAllConnections(); - } - else - { - info.RemoveConnection(disconnectParams.Type); - } - - // If the ConnectionInfo has no more connections, remove the ConnectionInfo - if (info.CountConnections == 0) - { - ownerToConnectionMap.Remove(disconnectParams.OwnerUri); - } - - // Handle Telemetry disconnect events if we are disconnecting the default connection - if (disconnectParams.Type == null || disconnectParams.Type == ConnectionType.Default) - { - HandleDisconnectTelemetry(info); - InvokeOnDisconnectionActivities(info); - } - - // Return true upon success - return true; - } - - /// - /// Cancel connections associated with the given ownerUri. - /// If connectionType is not null, cancel the connection with the given connectionType - /// If connectionType is null, cancel all pending connections associated with ownerUri. - /// - /// true if a single pending connection associated with the non-null connectionType was - /// found and cancelled, false otherwise - private bool CancelConnections(string ownerUri, string connectionType) - { - // Cancel the connection of the given type - if (connectionType != null) - { - // If we are trying to disconnect a specific connection and it was just cancelled, - // this will return true - return CancelConnect(new CancelConnectParams() { OwnerUri = ownerUri, Type = connectionType }); - } - - // Cancel all pending connections - foreach (var entry in cancelTupleToCancellationTokenSourceMap) - { - string entryConnectionUri = entry.Key.OwnerUri; - string entryConnectionType = entry.Key.Type; - if (ownerUri.Equals(entryConnectionUri)) - { - CancelConnect(new CancelConnectParams() { OwnerUri = ownerUri, Type = entryConnectionType }); - } - } - - return false; - } - - /// - /// Closes DbConnections associated with the given ConnectionInfo. - /// If connectionType is not null, closes the DbConnection with the type given by connectionType. - /// If connectionType is null, closes all DbConnections. - /// - /// true if connections were found and attempted to be closed, - /// false if no connections were found - private bool CloseConnections(ConnectionInfo connectionInfo, string connectionType) - { - ICollection connectionsToDisconnect = new List(); - if (connectionType == null) - { - connectionsToDisconnect = connectionInfo.AllConnections; - } - else - { - // Make sure there is an existing connection of this type - DbConnection connection; - if (!connectionInfo.TryGetConnection(connectionType, out connection)) - { - return false; - } - connectionsToDisconnect.Add(connection); - } - - if (connectionsToDisconnect.Count == 0) - { - return false; - } - - foreach (DbConnection connection in connectionsToDisconnect) - { - try - { - connection.Close(); - } - catch (Exception) - { - // Ignore - } - } - - return true; - } - - /// - /// List all databases on the server specified - /// - public ListDatabasesResponse ListDatabases(ListDatabasesParams listDatabasesParams) - { - // Verify parameters - var owner = listDatabasesParams.OwnerUri; - if (string.IsNullOrEmpty(owner)) - { - throw new ArgumentException(SR.ConnectionServiceListDbErrorNullOwnerUri); - } - - // Use the existing connection as a base for the search - ConnectionInfo info; - if (!TryFindConnection(owner, out info)) - { - throw new Exception(SR.ConnectionServiceListDbErrorNotConnected(owner)); - } - ConnectionDetails connectionDetails = info.ConnectionDetails.Clone(); - - // Connect to master and query sys.databases - connectionDetails.DatabaseName = "master"; - var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails)); - connection.Open(); - - List results = new List(); - var systemDatabases = new[] {"master", "model", "msdb", "tempdb"}; - using (DbCommand command = connection.CreateCommand()) - { - command.CommandText = "SELECT name FROM sys.databases ORDER BY name ASC"; - command.CommandTimeout = 15; - command.CommandType = CommandType.Text; - - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - results.Add(reader[0].ToString()); - } - } - } - - // Put system databases at the top of the list - results = - results.Where(s => systemDatabases.Any(s.Equals)).Concat( - results.Where(s => systemDatabases.All(x => !s.Equals(x)))).ToList(); - - connection.Close(); - - ListDatabasesResponse response = new ListDatabasesResponse(); - response.DatabaseNames = results.ToArray(); - - return response; - } - - public void InitializeService(IProtocolEndpoint serviceHost) - { - this.ServiceHost = serviceHost; - - // Register request and event handlers with the Service Host - serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest); - serviceHost.SetRequestHandler(CancelConnectRequest.Type, HandleCancelConnectRequest); - serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest); - serviceHost.SetRequestHandler(ListDatabasesRequest.Type, HandleListDatabasesRequest); - - // Register the configuration update handler - WorkspaceService.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); - } - - /// - /// Add a new method to be called when the onconnection request is submitted - /// - /// - public void RegisterOnConnectionTask(OnConnectionHandler activity) - { - onConnectionActivities.Add(activity); - } - - /// - /// Add a new method to be called when the ondisconnect request is submitted - /// - public void RegisterOnDisconnectTask(OnDisconnectHandler activity) - { - onDisconnectActivities.Add(activity); - } - - /// - /// Handle new connection requests - /// - /// - /// - /// - protected async Task HandleConnectRequest( - ConnectParams connectParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleConnectRequest"); - - try - { - RunConnectRequestHandlerTask(connectParams); - await requestContext.SendResult(true); - } - catch - { - await requestContext.SendResult(false); - } - } - - private void RunConnectRequestHandlerTask(ConnectParams connectParams) - { - // create a task to connect asynchronously so that other requests are not blocked in the meantime - Task.Run(async () => - { - try - { - // result is null if the ConnectParams was successfully validated - ConnectionCompleteParams result = ValidateConnectParams(connectParams); - if (result != null) - { - await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); - return; - } - - // open connection based on request details - result = await Connect(connectParams); - await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); - } - catch (Exception ex) - { - ConnectionCompleteParams result = new ConnectionCompleteParams() - { - Messages = ex.ToString() - }; - await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); - } - }); - } - - /// - /// Handle cancel connect requests - /// - protected async Task HandleCancelConnectRequest( - CancelConnectParams cancelParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleCancelConnectRequest"); - - try - { - bool result = CancelConnect(cancelParams); - await requestContext.SendResult(result); - } - catch(Exception ex) - { - await requestContext.SendError(ex.ToString()); - } - } - - /// - /// Handle disconnect requests - /// - protected async Task HandleDisconnectRequest( - DisconnectParams disconnectParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleDisconnectRequest"); - - try - { - bool result = Instance.Disconnect(disconnectParams); - await requestContext.SendResult(result); - } - catch(Exception ex) - { - await requestContext.SendError(ex.ToString()); - } - - } - - /// - /// Handle requests to list databases on the current server - /// - protected async Task HandleListDatabasesRequest( - ListDatabasesParams listDatabasesParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "ListDatabasesRequest"); - - try - { - ListDatabasesResponse result = Instance.ListDatabases(listDatabasesParams); - await requestContext.SendResult(result); - } - catch(Exception ex) - { - await requestContext.SendError(ex.ToString()); - } - } - - public Task HandleDidChangeConfigurationNotification( - SqlToolsSettings newSettings, - SqlToolsSettings oldSettings, - EventContext eventContext) - { - return Task.FromResult(true); - } - - /// - /// Build a connection string from a connection details instance - /// - /// - public static string BuildConnectionString(ConnectionDetails connectionDetails) - { - SqlConnectionStringBuilder connectionBuilder; - - // If connectionDetails has a connection string already, just validate and return it - if (!string.IsNullOrEmpty(connectionDetails.ConnectionString)) - { - connectionBuilder = new SqlConnectionStringBuilder(connectionDetails.ConnectionString); - return connectionBuilder.ToString(); - } - - connectionBuilder = new SqlConnectionStringBuilder - { - ["Data Source"] = connectionDetails.ServerName, - ["User Id"] = connectionDetails.UserName, - ["Password"] = connectionDetails.Password - }; - - // Check for any optional parameters - if (!string.IsNullOrEmpty(connectionDetails.DatabaseName)) - { - connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName; - } - if (!string.IsNullOrEmpty(connectionDetails.AuthenticationType)) - { - switch(connectionDetails.AuthenticationType) - { - case "Integrated": - connectionBuilder.IntegratedSecurity = true; - break; - case "SqlLogin": - break; - default: - throw new ArgumentException(SR.ConnectionServiceConnStringInvalidAuthType(connectionDetails.AuthenticationType)); - } - } - if (connectionDetails.Encrypt.HasValue) - { - connectionBuilder.Encrypt = connectionDetails.Encrypt.Value; - } - if (connectionDetails.TrustServerCertificate.HasValue) - { - connectionBuilder.TrustServerCertificate = connectionDetails.TrustServerCertificate.Value; - } - if (connectionDetails.PersistSecurityInfo.HasValue) - { - connectionBuilder.PersistSecurityInfo = connectionDetails.PersistSecurityInfo.Value; - } - if (connectionDetails.ConnectTimeout.HasValue) - { - connectionBuilder.ConnectTimeout = connectionDetails.ConnectTimeout.Value; - } - if (connectionDetails.ConnectRetryCount.HasValue) - { - connectionBuilder.ConnectRetryCount = connectionDetails.ConnectRetryCount.Value; - } - if (connectionDetails.ConnectRetryInterval.HasValue) - { - connectionBuilder.ConnectRetryInterval = connectionDetails.ConnectRetryInterval.Value; - } - if (!string.IsNullOrEmpty(connectionDetails.ApplicationName)) - { - connectionBuilder.ApplicationName = connectionDetails.ApplicationName; - } - if (!string.IsNullOrEmpty(connectionDetails.WorkstationId)) - { - connectionBuilder.WorkstationID = connectionDetails.WorkstationId; - } - if (!string.IsNullOrEmpty(connectionDetails.ApplicationIntent)) - { - ApplicationIntent intent; - switch (connectionDetails.ApplicationIntent) - { - case "ReadOnly": - intent = ApplicationIntent.ReadOnly; - break; - case "ReadWrite": - intent = ApplicationIntent.ReadWrite; - break; - default: - throw new ArgumentException(SR.ConnectionServiceConnStringInvalidIntent(connectionDetails.ApplicationIntent)); - } - connectionBuilder.ApplicationIntent = intent; - } - if (!string.IsNullOrEmpty(connectionDetails.CurrentLanguage)) - { - connectionBuilder.CurrentLanguage = connectionDetails.CurrentLanguage; - } - if (connectionDetails.Pooling.HasValue) - { - connectionBuilder.Pooling = connectionDetails.Pooling.Value; - } - if (connectionDetails.MaxPoolSize.HasValue) - { - connectionBuilder.MaxPoolSize = connectionDetails.MaxPoolSize.Value; - } - if (connectionDetails.MinPoolSize.HasValue) - { - connectionBuilder.MinPoolSize = connectionDetails.MinPoolSize.Value; - } - if (connectionDetails.LoadBalanceTimeout.HasValue) - { - connectionBuilder.LoadBalanceTimeout = connectionDetails.LoadBalanceTimeout.Value; - } - if (connectionDetails.Replication.HasValue) - { - connectionBuilder.Replication = connectionDetails.Replication.Value; - } - if (!string.IsNullOrEmpty(connectionDetails.AttachDbFilename)) - { - connectionBuilder.AttachDBFilename = connectionDetails.AttachDbFilename; - } - if (!string.IsNullOrEmpty(connectionDetails.FailoverPartner)) - { - connectionBuilder.FailoverPartner = connectionDetails.FailoverPartner; - } - if (connectionDetails.MultiSubnetFailover.HasValue) - { - connectionBuilder.MultiSubnetFailover = connectionDetails.MultiSubnetFailover.Value; - } - if (connectionDetails.MultipleActiveResultSets.HasValue) - { - connectionBuilder.MultipleActiveResultSets = connectionDetails.MultipleActiveResultSets.Value; - } - if (connectionDetails.PacketSize.HasValue) - { - connectionBuilder.PacketSize = connectionDetails.PacketSize.Value; - } - if (!string.IsNullOrEmpty(connectionDetails.TypeSystemVersion)) - { - connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion; - } - - return connectionBuilder.ToString(); - } - - /// - /// Change the database context of a connection. - /// - /// URI of the owner of the connection - /// Name of the database to change the connection to - public void ChangeConnectionDatabaseContext(string ownerUri, string newDatabaseName) - { - ConnectionInfo info; - if (TryFindConnection(ownerUri, out info)) - { - try - { - foreach (DbConnection connection in info.AllConnections) - { - if (connection.State == ConnectionState.Open) - { - connection.ChangeDatabase(newDatabaseName); - } - } - - info.ConnectionDetails.DatabaseName = newDatabaseName; - - // Fire a connection changed event - ConnectionChangedParams parameters = new ConnectionChangedParams(); - ConnectionSummary summary = info.ConnectionDetails; - parameters.Connection = summary.Clone(); - parameters.OwnerUri = ownerUri; - ServiceHost.SendEvent(ConnectionChangedNotification.Type, parameters); - } - catch (Exception e) - { - Logger.Write( - LogLevel.Error, - string.Format( - "Exception caught while trying to change database context to [{0}] for OwnerUri [{1}]. Exception:{2}", - newDatabaseName, - ownerUri, - e.ToString()) - ); - } - } - } - - /// - /// Invokes the initial on-connect activities if the provided ConnectParams represents the default - /// connection. - /// - private void InvokeOnConnectionActivities(ConnectionInfo connectionInfo, ConnectParams connectParams) - { - if (connectParams.Type != ConnectionType.Default) - { - return; - } - - foreach (var activity in this.onConnectionActivities) - { - // not awaiting here to allow handlers to run in the background - activity(connectionInfo); - } - } - - /// - /// Invokes the final on-disconnect activities if the provided DisconnectParams represents the default - /// connection or is null - representing that all connections are being disconnected. - /// - private void InvokeOnDisconnectionActivities(ConnectionInfo connectionInfo) - { - foreach (var activity in this.onDisconnectActivities) - { - activity(connectionInfo.ConnectionDetails, connectionInfo.OwnerUri); - } - } - - /// - /// Handles the Telemetry events that occur upon disconnect. - /// - /// - private void HandleDisconnectTelemetry(ConnectionInfo connectionInfo) - { - if (ServiceHost != null) - { - try - { - // Send a telemetry notification for intellisense performance metrics - ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() - { - Params = new TelemetryProperties - { - Properties = new Dictionary - { - { TelemetryPropertyNames.IsAzure, connectionInfo.IsAzure.ToOneOrZeroString() } - }, - EventName = TelemetryEventNames.IntellisenseQuantile, - Measures = connectionInfo.IntellisenseMetrics.Quantile - } - }); - } - catch (Exception ex) - { - Logger.Write(LogLevel.Verbose, "Could not send Connection telemetry event " + ex.ToString()); - } - } - } - } -} + { + // Validate parameters + if (disconnectParams == null || string.IsNullOrEmpty(disconnectParams.OwnerUri)) + { + return false; + } + + // Cancel if we are in the middle of connecting + if (CancelConnections(disconnectParams.OwnerUri, disconnectParams.Type)) + { + return false; + } + + // Lookup the ConnectionInfo owned by the URI + ConnectionInfo info; + if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info)) + { + return false; + } + + // Call Close() on the connections we want to disconnect + // If no connections were located, return false + if (!CloseConnections(info, disconnectParams.Type)) + { + return false; + } + + // Remove the disconnected connections from the ConnectionInfo map + if (disconnectParams.Type == null) + { + info.RemoveAllConnections(); + } + else + { + info.RemoveConnection(disconnectParams.Type); + } + + // If the ConnectionInfo has no more connections, remove the ConnectionInfo + if (info.CountConnections == 0) + { + ownerToConnectionMap.Remove(disconnectParams.OwnerUri); + } + + // Handle Telemetry disconnect events if we are disconnecting the default connection + if (disconnectParams.Type == null || disconnectParams.Type == ConnectionType.Default) + { + HandleDisconnectTelemetry(info); + InvokeOnDisconnectionActivities(info); + } + + // Return true upon success + return true; + } + + /// + /// Cancel connections associated with the given ownerUri. + /// If connectionType is not null, cancel the connection with the given connectionType + /// If connectionType is null, cancel all pending connections associated with ownerUri. + /// + /// true if a single pending connection associated with the non-null connectionType was + /// found and cancelled, false otherwise + private bool CancelConnections(string ownerUri, string connectionType) + { + // Cancel the connection of the given type + if (connectionType != null) + { + // If we are trying to disconnect a specific connection and it was just cancelled, + // this will return true + return CancelConnect(new CancelConnectParams() { OwnerUri = ownerUri, Type = connectionType }); + } + + // Cancel all pending connections + foreach (var entry in cancelTupleToCancellationTokenSourceMap) + { + string entryConnectionUri = entry.Key.OwnerUri; + string entryConnectionType = entry.Key.Type; + if (ownerUri.Equals(entryConnectionUri)) + { + CancelConnect(new CancelConnectParams() { OwnerUri = ownerUri, Type = entryConnectionType }); + } + } + + return false; + } + + /// + /// Closes DbConnections associated with the given ConnectionInfo. + /// If connectionType is not null, closes the DbConnection with the type given by connectionType. + /// If connectionType is null, closes all DbConnections. + /// + /// true if connections were found and attempted to be closed, + /// false if no connections were found + private bool CloseConnections(ConnectionInfo connectionInfo, string connectionType) + { + ICollection connectionsToDisconnect = new List(); + if (connectionType == null) + { + connectionsToDisconnect = connectionInfo.AllConnections; + } + else + { + // Make sure there is an existing connection of this type + DbConnection connection; + if (!connectionInfo.TryGetConnection(connectionType, out connection)) + { + return false; + } + connectionsToDisconnect.Add(connection); + } + + if (connectionsToDisconnect.Count == 0) + { + return false; + } + + foreach (DbConnection connection in connectionsToDisconnect) + { + try + { + connection.Close(); + } + catch (Exception) + { + // Ignore + } + } + + return true; + } + + /// + /// List all databases on the server specified + /// + public ListDatabasesResponse ListDatabases(ListDatabasesParams listDatabasesParams) + { + // Verify parameters + var owner = listDatabasesParams.OwnerUri; + if (string.IsNullOrEmpty(owner)) + { + throw new ArgumentException(SR.ConnectionServiceListDbErrorNullOwnerUri); + } + + // Use the existing connection as a base for the search + ConnectionInfo info; + if (!TryFindConnection(owner, out info)) + { + throw new Exception(SR.ConnectionServiceListDbErrorNotConnected(owner)); + } + ConnectionDetails connectionDetails = info.ConnectionDetails.Clone(); + + // Connect to master and query sys.databases + connectionDetails.DatabaseName = "master"; + var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails)); + connection.Open(); + + List results = new List(); + var systemDatabases = new[] {"master", "model", "msdb", "tempdb"}; + using (DbCommand command = connection.CreateCommand()) + { + command.CommandText = "SELECT name FROM sys.databases ORDER BY name ASC"; + command.CommandTimeout = 15; + command.CommandType = CommandType.Text; + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + results.Add(reader[0].ToString()); + } + } + } + + // Put system databases at the top of the list + results = + results.Where(s => systemDatabases.Any(s.Equals)).Concat( + results.Where(s => systemDatabases.All(x => !s.Equals(x)))).ToList(); + + connection.Close(); + + ListDatabasesResponse response = new ListDatabasesResponse(); + response.DatabaseNames = results.ToArray(); + + return response; + } + + public void InitializeService(IProtocolEndpoint serviceHost) + { + this.ServiceHost = serviceHost; + + // Register request and event handlers with the Service Host + serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest); + serviceHost.SetRequestHandler(CancelConnectRequest.Type, HandleCancelConnectRequest); + serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest); + serviceHost.SetRequestHandler(ListDatabasesRequest.Type, HandleListDatabasesRequest); + + // Register the configuration update handler + WorkspaceService.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); + } + + /// + /// Add a new method to be called when the onconnection request is submitted + /// + /// + public void RegisterOnConnectionTask(OnConnectionHandler activity) + { + onConnectionActivities.Add(activity); + } + + /// + /// Add a new method to be called when the ondisconnect request is submitted + /// + public void RegisterOnDisconnectTask(OnDisconnectHandler activity) + { + onDisconnectActivities.Add(activity); + } + + /// + /// Handle new connection requests + /// + /// + /// + /// + protected async Task HandleConnectRequest( + ConnectParams connectParams, + RequestContext requestContext) + { + Logger.Write(LogLevel.Verbose, "HandleConnectRequest"); + + try + { + RunConnectRequestHandlerTask(connectParams); + await requestContext.SendResult(true); + } + catch + { + await requestContext.SendResult(false); + } + } + + private void RunConnectRequestHandlerTask(ConnectParams connectParams) + { + // create a task to connect asynchronously so that other requests are not blocked in the meantime + Task.Run(async () => + { + try + { + // result is null if the ConnectParams was successfully validated + ConnectionCompleteParams result = ValidateConnectParams(connectParams); + if (result != null) + { + await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); + return; + } + + // open connection based on request details + result = await Connect(connectParams); + await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); + } + catch (Exception ex) + { + ConnectionCompleteParams result = new ConnectionCompleteParams() + { + Messages = ex.ToString() + }; + await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); + } + }); + } + + /// + /// Handle cancel connect requests + /// + protected async Task HandleCancelConnectRequest( + CancelConnectParams cancelParams, + RequestContext requestContext) + { + Logger.Write(LogLevel.Verbose, "HandleCancelConnectRequest"); + + try + { + bool result = CancelConnect(cancelParams); + await requestContext.SendResult(result); + } + catch(Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + } + + /// + /// Handle disconnect requests + /// + protected async Task HandleDisconnectRequest( + DisconnectParams disconnectParams, + RequestContext requestContext) + { + Logger.Write(LogLevel.Verbose, "HandleDisconnectRequest"); + + try + { + bool result = Instance.Disconnect(disconnectParams); + await requestContext.SendResult(result); + } + catch(Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + + } + + /// + /// Handle requests to list databases on the current server + /// + protected async Task HandleListDatabasesRequest( + ListDatabasesParams listDatabasesParams, + RequestContext requestContext) + { + Logger.Write(LogLevel.Verbose, "ListDatabasesRequest"); + + try + { + ListDatabasesResponse result = Instance.ListDatabases(listDatabasesParams); + await requestContext.SendResult(result); + } + catch(Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + } + + public Task HandleDidChangeConfigurationNotification( + SqlToolsSettings newSettings, + SqlToolsSettings oldSettings, + EventContext eventContext) + { + return Task.FromResult(true); + } + + /// + /// Build a connection string from a connection details instance + /// + /// + public static string BuildConnectionString(ConnectionDetails connectionDetails) + { + SqlConnectionStringBuilder connectionBuilder; + + // If connectionDetails has a connection string already, just validate and return it + if (!string.IsNullOrEmpty(connectionDetails.ConnectionString)) + { + connectionBuilder = new SqlConnectionStringBuilder(connectionDetails.ConnectionString); + return connectionBuilder.ToString(); + } + + connectionBuilder = new SqlConnectionStringBuilder + { + ["Data Source"] = connectionDetails.ServerName, + ["User Id"] = connectionDetails.UserName, + ["Password"] = connectionDetails.Password + }; + + // Check for any optional parameters + if (!string.IsNullOrEmpty(connectionDetails.DatabaseName)) + { + connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName; + } + if (!string.IsNullOrEmpty(connectionDetails.AuthenticationType)) + { + switch(connectionDetails.AuthenticationType) + { + case "Integrated": + connectionBuilder.IntegratedSecurity = true; + break; + case "SqlLogin": + break; + default: + throw new ArgumentException(SR.ConnectionServiceConnStringInvalidAuthType(connectionDetails.AuthenticationType)); + } + } + if (connectionDetails.Encrypt.HasValue) + { + connectionBuilder.Encrypt = connectionDetails.Encrypt.Value; + } + if (connectionDetails.TrustServerCertificate.HasValue) + { + connectionBuilder.TrustServerCertificate = connectionDetails.TrustServerCertificate.Value; + } + if (connectionDetails.PersistSecurityInfo.HasValue) + { + connectionBuilder.PersistSecurityInfo = connectionDetails.PersistSecurityInfo.Value; + } + if (connectionDetails.ConnectTimeout.HasValue) + { + connectionBuilder.ConnectTimeout = connectionDetails.ConnectTimeout.Value; + } + if (connectionDetails.ConnectRetryCount.HasValue) + { + connectionBuilder.ConnectRetryCount = connectionDetails.ConnectRetryCount.Value; + } + if (connectionDetails.ConnectRetryInterval.HasValue) + { + connectionBuilder.ConnectRetryInterval = connectionDetails.ConnectRetryInterval.Value; + } + if (!string.IsNullOrEmpty(connectionDetails.ApplicationName)) + { + connectionBuilder.ApplicationName = connectionDetails.ApplicationName; + } + if (!string.IsNullOrEmpty(connectionDetails.WorkstationId)) + { + connectionBuilder.WorkstationID = connectionDetails.WorkstationId; + } + if (!string.IsNullOrEmpty(connectionDetails.ApplicationIntent)) + { + ApplicationIntent intent; + switch (connectionDetails.ApplicationIntent) + { + case "ReadOnly": + intent = ApplicationIntent.ReadOnly; + break; + case "ReadWrite": + intent = ApplicationIntent.ReadWrite; + break; + default: + throw new ArgumentException(SR.ConnectionServiceConnStringInvalidIntent(connectionDetails.ApplicationIntent)); + } + connectionBuilder.ApplicationIntent = intent; + } + if (!string.IsNullOrEmpty(connectionDetails.CurrentLanguage)) + { + connectionBuilder.CurrentLanguage = connectionDetails.CurrentLanguage; + } + if (connectionDetails.Pooling.HasValue) + { + connectionBuilder.Pooling = connectionDetails.Pooling.Value; + } + if (connectionDetails.MaxPoolSize.HasValue) + { + connectionBuilder.MaxPoolSize = connectionDetails.MaxPoolSize.Value; + } + if (connectionDetails.MinPoolSize.HasValue) + { + connectionBuilder.MinPoolSize = connectionDetails.MinPoolSize.Value; + } + if (connectionDetails.LoadBalanceTimeout.HasValue) + { + connectionBuilder.LoadBalanceTimeout = connectionDetails.LoadBalanceTimeout.Value; + } + if (connectionDetails.Replication.HasValue) + { + connectionBuilder.Replication = connectionDetails.Replication.Value; + } + if (!string.IsNullOrEmpty(connectionDetails.AttachDbFilename)) + { + connectionBuilder.AttachDBFilename = connectionDetails.AttachDbFilename; + } + if (!string.IsNullOrEmpty(connectionDetails.FailoverPartner)) + { + connectionBuilder.FailoverPartner = connectionDetails.FailoverPartner; + } + if (connectionDetails.MultiSubnetFailover.HasValue) + { + connectionBuilder.MultiSubnetFailover = connectionDetails.MultiSubnetFailover.Value; + } + if (connectionDetails.MultipleActiveResultSets.HasValue) + { + connectionBuilder.MultipleActiveResultSets = connectionDetails.MultipleActiveResultSets.Value; + } + if (connectionDetails.PacketSize.HasValue) + { + connectionBuilder.PacketSize = connectionDetails.PacketSize.Value; + } + if (!string.IsNullOrEmpty(connectionDetails.TypeSystemVersion)) + { + connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion; + } + + return connectionBuilder.ToString(); + } + + /// + /// Change the database context of a connection. + /// + /// URI of the owner of the connection + /// Name of the database to change the connection to + public void ChangeConnectionDatabaseContext(string ownerUri, string newDatabaseName) + { + ConnectionInfo info; + if (TryFindConnection(ownerUri, out info)) + { + try + { + foreach (DbConnection connection in info.AllConnections) + { + if (connection.State == ConnectionState.Open) + { + connection.ChangeDatabase(newDatabaseName); + } + } + + info.ConnectionDetails.DatabaseName = newDatabaseName; + + // Fire a connection changed event + ConnectionChangedParams parameters = new ConnectionChangedParams(); + ConnectionSummary summary = info.ConnectionDetails; + parameters.Connection = summary.Clone(); + parameters.OwnerUri = ownerUri; + ServiceHost.SendEvent(ConnectionChangedNotification.Type, parameters); + } + catch (Exception e) + { + Logger.Write( + LogLevel.Error, + string.Format( + "Exception caught while trying to change database context to [{0}] for OwnerUri [{1}]. Exception:{2}", + newDatabaseName, + ownerUri, + e.ToString()) + ); + } + } + } + + /// + /// Invokes the initial on-connect activities if the provided ConnectParams represents the default + /// connection. + /// + private void InvokeOnConnectionActivities(ConnectionInfo connectionInfo, ConnectParams connectParams) + { + if (connectParams.Type != ConnectionType.Default) + { + return; + } + + foreach (var activity in this.onConnectionActivities) + { + // not awaiting here to allow handlers to run in the background + activity(connectionInfo); + } + } + + /// + /// Invokes the final on-disconnect activities if the provided DisconnectParams represents the default + /// connection or is null - representing that all connections are being disconnected. + /// + private void InvokeOnDisconnectionActivities(ConnectionInfo connectionInfo) + { + foreach (var activity in this.onDisconnectActivities) + { + activity(connectionInfo.ConnectionDetails, connectionInfo.OwnerUri); + } + } + + /// + /// Handles the Telemetry events that occur upon disconnect. + /// + /// + private void HandleDisconnectTelemetry(ConnectionInfo connectionInfo) + { + if (ServiceHost != null) + { + try + { + // Send a telemetry notification for intellisense performance metrics + ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() + { + Params = new TelemetryProperties + { + Properties = new Dictionary + { + { TelemetryPropertyNames.IsAzure, connectionInfo.IsAzure.ToOneOrZeroString() } + }, + EventName = TelemetryEventNames.IntellisenseQuantile, + Measures = connectionInfo.IntellisenseMetrics.Quantile + } + }); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Verbose, "Could not send Connection telemetry event " + ex.ToString()); + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 6535c287..be3c966b 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -2357,6 +2357,894 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string unavailable + { + get + { + return Keys.GetString(Keys.unavailable); + } + } + + public static string filegroup_dialog_defaultFilegroup + { + get + { + return Keys.GetString(Keys.filegroup_dialog_defaultFilegroup); + } + } + + public static string filegroup_dialog_title + { + get + { + return Keys.GetString(Keys.filegroup_dialog_title); + } + } + + public static string filegroups_default + { + get + { + return Keys.GetString(Keys.filegroups_default); + } + } + + public static string filegroups_files + { + get + { + return Keys.GetString(Keys.filegroups_files); + } + } + + public static string filegroups_name + { + get + { + return Keys.GetString(Keys.filegroups_name); + } + } + + public static string filegroups_readonly + { + get + { + return Keys.GetString(Keys.filegroups_readonly); + } + } + + public static string general_autogrowth + { + get + { + return Keys.GetString(Keys.general_autogrowth); + } + } + + public static string general_builderText + { + get + { + return Keys.GetString(Keys.general_builderText); + } + } + + public static string general_default + { + get + { + return Keys.GetString(Keys.general_default); + } + } + + public static string general_fileGroup + { + get + { + return Keys.GetString(Keys.general_fileGroup); + } + } + + public static string general_fileName + { + get + { + return Keys.GetString(Keys.general_fileName); + } + } + + public static string general_fileType + { + get + { + return Keys.GetString(Keys.general_fileType); + } + } + + public static string general_initialSize + { + get + { + return Keys.GetString(Keys.general_initialSize); + } + } + + public static string general_newFilegroup + { + get + { + return Keys.GetString(Keys.general_newFilegroup); + } + } + + public static string general_path + { + get + { + return Keys.GetString(Keys.general_path); + } + } + + public static string general_physicalFileName + { + get + { + return Keys.GetString(Keys.general_physicalFileName); + } + } + + public static string general_rawDevice + { + get + { + return Keys.GetString(Keys.general_rawDevice); + } + } + + public static string general_recoveryModel_bulkLogged + { + get + { + return Keys.GetString(Keys.general_recoveryModel_bulkLogged); + } + } + + public static string general_recoveryModel_full + { + get + { + return Keys.GetString(Keys.general_recoveryModel_full); + } + } + + public static string general_recoveryModel_simple + { + get + { + return Keys.GetString(Keys.general_recoveryModel_simple); + } + } + + public static string general_titleSearchOwner + { + get + { + return Keys.GetString(Keys.general_titleSearchOwner); + } + } + + public static string prototype_autogrowth_disabled + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_disabled); + } + } + + public static string prototype_autogrowth_restrictedGrowthByMB + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_restrictedGrowthByMB); + } + } + + public static string prototype_autogrowth_restrictedGrowthByPercent + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_restrictedGrowthByPercent); + } + } + + public static string prototype_autogrowth_unrestrictedGrowthByMB + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_unrestrictedGrowthByMB); + } + } + + public static string prototype_autogrowth_unrestrictedGrowthByPercent + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_unrestrictedGrowthByPercent); + } + } + + public static string prototype_autogrowth_unlimitedfilestream + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_unlimitedfilestream); + } + } + + public static string prototype_autogrowth_limitedfilestream + { + get + { + return Keys.GetString(Keys.prototype_autogrowth_limitedfilestream); + } + } + + public static string prototype_db_category_automatic + { + get + { + return Keys.GetString(Keys.prototype_db_category_automatic); + } + } + + public static string prototype_db_category_servicebroker + { + get + { + return Keys.GetString(Keys.prototype_db_category_servicebroker); + } + } + + public static string prototype_db_category_collation + { + get + { + return Keys.GetString(Keys.prototype_db_category_collation); + } + } + + public static string prototype_db_category_cursor + { + get + { + return Keys.GetString(Keys.prototype_db_category_cursor); + } + } + + public static string prototype_db_category_misc + { + get + { + return Keys.GetString(Keys.prototype_db_category_misc); + } + } + + public static string prototype_db_category_recovery + { + get + { + return Keys.GetString(Keys.prototype_db_category_recovery); + } + } + + public static string prototype_db_category_state + { + get + { + return Keys.GetString(Keys.prototype_db_category_state); + } + } + + public static string prototype_db_prop_ansiNullDefault + { + get + { + return Keys.GetString(Keys.prototype_db_prop_ansiNullDefault); + } + } + + public static string prototype_db_prop_ansiNulls + { + get + { + return Keys.GetString(Keys.prototype_db_prop_ansiNulls); + } + } + + public static string prototype_db_prop_ansiPadding + { + get + { + return Keys.GetString(Keys.prototype_db_prop_ansiPadding); + } + } + + public static string prototype_db_prop_ansiWarnings + { + get + { + return Keys.GetString(Keys.prototype_db_prop_ansiWarnings); + } + } + + public static string prototype_db_prop_arithabort + { + get + { + return Keys.GetString(Keys.prototype_db_prop_arithabort); + } + } + + public static string prototype_db_prop_autoClose + { + get + { + return Keys.GetString(Keys.prototype_db_prop_autoClose); + } + } + + public static string prototype_db_prop_autoCreateStatistics + { + get + { + return Keys.GetString(Keys.prototype_db_prop_autoCreateStatistics); + } + } + + public static string prototype_db_prop_autoShrink + { + get + { + return Keys.GetString(Keys.prototype_db_prop_autoShrink); + } + } + + public static string prototype_db_prop_autoUpdateStatistics + { + get + { + return Keys.GetString(Keys.prototype_db_prop_autoUpdateStatistics); + } + } + + public static string prototype_db_prop_autoUpdateStatisticsAsync + { + get + { + return Keys.GetString(Keys.prototype_db_prop_autoUpdateStatisticsAsync); + } + } + + public static string prototype_db_prop_caseSensitive + { + get + { + return Keys.GetString(Keys.prototype_db_prop_caseSensitive); + } + } + + public static string prototype_db_prop_closeCursorOnCommit + { + get + { + return Keys.GetString(Keys.prototype_db_prop_closeCursorOnCommit); + } + } + + public static string prototype_db_prop_collation + { + get + { + return Keys.GetString(Keys.prototype_db_prop_collation); + } + } + + public static string prototype_db_prop_concatNullYieldsNull + { + get + { + return Keys.GetString(Keys.prototype_db_prop_concatNullYieldsNull); + } + } + + public static string prototype_db_prop_databaseCompatibilityLevel + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseCompatibilityLevel); + } + } + + public static string prototype_db_prop_databaseState + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState); + } + } + + public static string prototype_db_prop_defaultCursor + { + get + { + return Keys.GetString(Keys.prototype_db_prop_defaultCursor); + } + } + + public static string prototype_db_prop_fullTextIndexing + { + get + { + return Keys.GetString(Keys.prototype_db_prop_fullTextIndexing); + } + } + + public static string prototype_db_prop_numericRoundAbort + { + get + { + return Keys.GetString(Keys.prototype_db_prop_numericRoundAbort); + } + } + + public static string prototype_db_prop_pageVerify + { + get + { + return Keys.GetString(Keys.prototype_db_prop_pageVerify); + } + } + + public static string prototype_db_prop_quotedIdentifier + { + get + { + return Keys.GetString(Keys.prototype_db_prop_quotedIdentifier); + } + } + + public static string prototype_db_prop_readOnly + { + get + { + return Keys.GetString(Keys.prototype_db_prop_readOnly); + } + } + + public static string prototype_db_prop_recursiveTriggers + { + get + { + return Keys.GetString(Keys.prototype_db_prop_recursiveTriggers); + } + } + + public static string prototype_db_prop_restrictAccess + { + get + { + return Keys.GetString(Keys.prototype_db_prop_restrictAccess); + } + } + + public static string prototype_db_prop_selectIntoBulkCopy + { + get + { + return Keys.GetString(Keys.prototype_db_prop_selectIntoBulkCopy); + } + } + + public static string prototype_db_prop_honorBrokerPriority + { + get + { + return Keys.GetString(Keys.prototype_db_prop_honorBrokerPriority); + } + } + + public static string prototype_db_prop_serviceBrokerGuid + { + get + { + return Keys.GetString(Keys.prototype_db_prop_serviceBrokerGuid); + } + } + + public static string prototype_db_prop_brokerEnabled + { + get + { + return Keys.GetString(Keys.prototype_db_prop_brokerEnabled); + } + } + + public static string prototype_db_prop_truncateLogOnCheckpoint + { + get + { + return Keys.GetString(Keys.prototype_db_prop_truncateLogOnCheckpoint); + } + } + + public static string prototype_db_prop_dbChaining + { + get + { + return Keys.GetString(Keys.prototype_db_prop_dbChaining); + } + } + + public static string prototype_db_prop_trustworthy + { + get + { + return Keys.GetString(Keys.prototype_db_prop_trustworthy); + } + } + + public static string prototype_db_prop_dateCorrelationOptimization + { + get + { + return Keys.GetString(Keys.prototype_db_prop_dateCorrelationOptimization); + } + } + + public static string prototype_db_prop_parameterization_value_forced + { + get + { + return Keys.GetString(Keys.prototype_db_prop_parameterization_value_forced); + } + } + + public static string prototype_db_prop_parameterization_value_simple + { + get + { + return Keys.GetString(Keys.prototype_db_prop_parameterization_value_simple); + } + } + + public static string prototype_file_dataFile + { + get + { + return Keys.GetString(Keys.prototype_file_dataFile); + } + } + + public static string prototype_file_logFile + { + get + { + return Keys.GetString(Keys.prototype_file_logFile); + } + } + + public static string prototype_file_filestreamFile + { + get + { + return Keys.GetString(Keys.prototype_file_filestreamFile); + } + } + + public static string prototype_file_noFileGroup + { + get + { + return Keys.GetString(Keys.prototype_file_noFileGroup); + } + } + + public static string prototype_file_defaultpathstring + { + get + { + return Keys.GetString(Keys.prototype_file_defaultpathstring); + } + } + + public static string title_openConnectionsMustBeClosed + { + get + { + return Keys.GetString(Keys.title_openConnectionsMustBeClosed); + } + } + + public static string warning_openConnectionsMustBeClosed + { + get + { + return Keys.GetString(Keys.warning_openConnectionsMustBeClosed); + } + } + + public static string prototype_db_prop_databaseState_value_autoClosed + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_autoClosed); + } + } + + public static string prototype_db_prop_databaseState_value_emergency + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_emergency); + } + } + + public static string prototype_db_prop_databaseState_value_inaccessible + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_inaccessible); + } + } + + public static string prototype_db_prop_databaseState_value_normal + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_normal); + } + } + + public static string prototype_db_prop_databaseState_value_offline + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_offline); + } + } + + public static string prototype_db_prop_databaseState_value_recovering + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_recovering); + } + } + + public static string prototype_db_prop_databaseState_value_recoveryPending + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_recoveryPending); + } + } + + public static string prototype_db_prop_databaseState_value_restoring + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_restoring); + } + } + + public static string prototype_db_prop_databaseState_value_shutdown + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_shutdown); + } + } + + public static string prototype_db_prop_databaseState_value_standby + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_standby); + } + } + + public static string prototype_db_prop_databaseState_value_suspect + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databaseState_value_suspect); + } + } + + public static string prototype_db_prop_defaultCursor_value_global + { + get + { + return Keys.GetString(Keys.prototype_db_prop_defaultCursor_value_global); + } + } + + public static string prototype_db_prop_defaultCursor_value_local + { + get + { + return Keys.GetString(Keys.prototype_db_prop_defaultCursor_value_local); + } + } + + public static string prototype_db_prop_restrictAccess_value_multiple + { + get + { + return Keys.GetString(Keys.prototype_db_prop_restrictAccess_value_multiple); + } + } + + public static string prototype_db_prop_restrictAccess_value_restricted + { + get + { + return Keys.GetString(Keys.prototype_db_prop_restrictAccess_value_restricted); + } + } + + public static string prototype_db_prop_restrictAccess_value_single + { + get + { + return Keys.GetString(Keys.prototype_db_prop_restrictAccess_value_single); + } + } + + public static string prototype_db_prop_pageVerify_value_checksum + { + get + { + return Keys.GetString(Keys.prototype_db_prop_pageVerify_value_checksum); + } + } + + public static string prototype_db_prop_pageVerify_value_none + { + get + { + return Keys.GetString(Keys.prototype_db_prop_pageVerify_value_none); + } + } + + public static string prototype_db_prop_pageVerify_value_tornPageDetection + { + get + { + return Keys.GetString(Keys.prototype_db_prop_pageVerify_value_tornPageDetection); + } + } + + public static string prototype_db_prop_varDecimalEnabled + { + get + { + return Keys.GetString(Keys.prototype_db_prop_varDecimalEnabled); + } + } + + public static string compatibilityLevel_katmai + { + get + { + return Keys.GetString(Keys.compatibilityLevel_katmai); + } + } + + public static string prototype_db_prop_encryptionEnabled + { + get + { + return Keys.GetString(Keys.prototype_db_prop_encryptionEnabled); + } + } + + public static string prototype_db_prop_databasescopedconfig_value_off + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databasescopedconfig_value_off); + } + } + + public static string prototype_db_prop_databasescopedconfig_value_on + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databasescopedconfig_value_on); + } + } + + public static string prototype_db_prop_databasescopedconfig_value_primary + { + get + { + return Keys.GetString(Keys.prototype_db_prop_databasescopedconfig_value_primary); + } + } + + public static string error_db_prop_invalidleadingColumns + { + get + { + return Keys.GetString(Keys.error_db_prop_invalidleadingColumns); + } + } + + public static string compatibilityLevel_denali + { + get + { + return Keys.GetString(Keys.compatibilityLevel_denali); + } + } + + public static string compatibilityLevel_sql14 + { + get + { + return Keys.GetString(Keys.compatibilityLevel_sql14); + } + } + + public static string compatibilityLevel_sql15 + { + get + { + return Keys.GetString(Keys.compatibilityLevel_sql15); + } + } + + public static string compatibilityLevel_sqlvNext + { + get + { + return Keys.GetString(Keys.compatibilityLevel_sqlvNext); + } + } + + public static string general_containmentType_None + { + get + { + return Keys.GetString(Keys.general_containmentType_None); + } + } + + public static string general_containmentType_Partial + { + get + { + return Keys.GetString(Keys.general_containmentType_Partial); + } + } + + public static string filegroups_filestreamFiles + { + get + { + return Keys.GetString(Keys.filegroups_filestreamFiles); + } + } + + public static string prototype_file_noApplicableFileGroup + { + get + { + return Keys.GetString(Keys.prototype_file_noApplicableFileGroup); + } + } + public static string ConnectionServiceListDbErrorNotConnected(string uri) { return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); @@ -3350,6 +4238,339 @@ namespace Microsoft.SqlTools.ServiceLayer public const string ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = "ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid"; + public const string unavailable = "unavailable"; + + + public const string filegroup_dialog_defaultFilegroup = "filegroup_dialog_defaultFilegroup"; + + + public const string filegroup_dialog_title = "filegroup_dialog_title"; + + + public const string filegroups_default = "filegroups_default"; + + + public const string filegroups_files = "filegroups_files"; + + + public const string filegroups_name = "filegroups_name"; + + + public const string filegroups_readonly = "filegroups_readonly"; + + + public const string general_autogrowth = "general_autogrowth"; + + + public const string general_builderText = "general_builderText"; + + + public const string general_default = "general_default"; + + + public const string general_fileGroup = "general_fileGroup"; + + + public const string general_fileName = "general_fileName"; + + + public const string general_fileType = "general_fileType"; + + + public const string general_initialSize = "general_initialSize"; + + + public const string general_newFilegroup = "general_newFilegroup"; + + + public const string general_path = "general_path"; + + + public const string general_physicalFileName = "general_physicalFileName"; + + + public const string general_rawDevice = "general_rawDevice"; + + + public const string general_recoveryModel_bulkLogged = "general_recoveryModel_bulkLogged"; + + + public const string general_recoveryModel_full = "general_recoveryModel_full"; + + + public const string general_recoveryModel_simple = "general_recoveryModel_simple"; + + + public const string general_titleSearchOwner = "general_titleSearchOwner"; + + + public const string prototype_autogrowth_disabled = "prototype_autogrowth_disabled"; + + + public const string prototype_autogrowth_restrictedGrowthByMB = "prototype_autogrowth_restrictedGrowthByMB"; + + + public const string prototype_autogrowth_restrictedGrowthByPercent = "prototype_autogrowth_restrictedGrowthByPercent"; + + + public const string prototype_autogrowth_unrestrictedGrowthByMB = "prototype_autogrowth_unrestrictedGrowthByMB"; + + + public const string prototype_autogrowth_unrestrictedGrowthByPercent = "prototype_autogrowth_unrestrictedGrowthByPercent"; + + + public const string prototype_autogrowth_unlimitedfilestream = "prototype_autogrowth_unlimitedfilestream"; + + + public const string prototype_autogrowth_limitedfilestream = "prototype_autogrowth_limitedfilestream"; + + + public const string prototype_db_category_automatic = "prototype_db_category_automatic"; + + + public const string prototype_db_category_servicebroker = "prototype_db_category_servicebroker"; + + + public const string prototype_db_category_collation = "prototype_db_category_collation"; + + + public const string prototype_db_category_cursor = "prototype_db_category_cursor"; + + + public const string prototype_db_category_misc = "prototype_db_category_misc"; + + + public const string prototype_db_category_recovery = "prototype_db_category_recovery"; + + + public const string prototype_db_category_state = "prototype_db_category_state"; + + + public const string prototype_db_prop_ansiNullDefault = "prototype_db_prop_ansiNullDefault"; + + + public const string prototype_db_prop_ansiNulls = "prototype_db_prop_ansiNulls"; + + + public const string prototype_db_prop_ansiPadding = "prototype_db_prop_ansiPadding"; + + + public const string prototype_db_prop_ansiWarnings = "prototype_db_prop_ansiWarnings"; + + + public const string prototype_db_prop_arithabort = "prototype_db_prop_arithabort"; + + + public const string prototype_db_prop_autoClose = "prototype_db_prop_autoClose"; + + + public const string prototype_db_prop_autoCreateStatistics = "prototype_db_prop_autoCreateStatistics"; + + + public const string prototype_db_prop_autoShrink = "prototype_db_prop_autoShrink"; + + + public const string prototype_db_prop_autoUpdateStatistics = "prototype_db_prop_autoUpdateStatistics"; + + + public const string prototype_db_prop_autoUpdateStatisticsAsync = "prototype_db_prop_autoUpdateStatisticsAsync"; + + + public const string prototype_db_prop_caseSensitive = "prototype_db_prop_caseSensitive"; + + + public const string prototype_db_prop_closeCursorOnCommit = "prototype_db_prop_closeCursorOnCommit"; + + + public const string prototype_db_prop_collation = "prototype_db_prop_collation"; + + + public const string prototype_db_prop_concatNullYieldsNull = "prototype_db_prop_concatNullYieldsNull"; + + + public const string prototype_db_prop_databaseCompatibilityLevel = "prototype_db_prop_databaseCompatibilityLevel"; + + + public const string prototype_db_prop_databaseState = "prototype_db_prop_databaseState"; + + + public const string prototype_db_prop_defaultCursor = "prototype_db_prop_defaultCursor"; + + + public const string prototype_db_prop_fullTextIndexing = "prototype_db_prop_fullTextIndexing"; + + + public const string prototype_db_prop_numericRoundAbort = "prototype_db_prop_numericRoundAbort"; + + + public const string prototype_db_prop_pageVerify = "prototype_db_prop_pageVerify"; + + + public const string prototype_db_prop_quotedIdentifier = "prototype_db_prop_quotedIdentifier"; + + + public const string prototype_db_prop_readOnly = "prototype_db_prop_readOnly"; + + + public const string prototype_db_prop_recursiveTriggers = "prototype_db_prop_recursiveTriggers"; + + + public const string prototype_db_prop_restrictAccess = "prototype_db_prop_restrictAccess"; + + + public const string prototype_db_prop_selectIntoBulkCopy = "prototype_db_prop_selectIntoBulkCopy"; + + + public const string prototype_db_prop_honorBrokerPriority = "prototype_db_prop_honorBrokerPriority"; + + + public const string prototype_db_prop_serviceBrokerGuid = "prototype_db_prop_serviceBrokerGuid"; + + + public const string prototype_db_prop_brokerEnabled = "prototype_db_prop_brokerEnabled"; + + + public const string prototype_db_prop_truncateLogOnCheckpoint = "prototype_db_prop_truncateLogOnCheckpoint"; + + + public const string prototype_db_prop_dbChaining = "prototype_db_prop_dbChaining"; + + + public const string prototype_db_prop_trustworthy = "prototype_db_prop_trustworthy"; + + + public const string prototype_db_prop_dateCorrelationOptimization = "prototype_db_prop_dateCorrelationOptimization"; + + + public const string prototype_db_prop_parameterization_value_forced = "prototype_db_prop_parameterization_value_forced"; + + + public const string prototype_db_prop_parameterization_value_simple = "prototype_db_prop_parameterization_value_simple"; + + + public const string prototype_file_dataFile = "prototype_file_dataFile"; + + + public const string prototype_file_logFile = "prototype_file_logFile"; + + + public const string prototype_file_filestreamFile = "prototype_file_filestreamFile"; + + + public const string prototype_file_noFileGroup = "prototype_file_noFileGroup"; + + + public const string prototype_file_defaultpathstring = "prototype_file_defaultpathstring"; + + + public const string title_openConnectionsMustBeClosed = "title_openConnectionsMustBeClosed"; + + + public const string warning_openConnectionsMustBeClosed = "warning_openConnectionsMustBeClosed"; + + + public const string prototype_db_prop_databaseState_value_autoClosed = "prototype_db_prop_databaseState_value_autoClosed"; + + + public const string prototype_db_prop_databaseState_value_emergency = "prototype_db_prop_databaseState_value_emergency"; + + + public const string prototype_db_prop_databaseState_value_inaccessible = "prototype_db_prop_databaseState_value_inaccessible"; + + + public const string prototype_db_prop_databaseState_value_normal = "prototype_db_prop_databaseState_value_normal"; + + + public const string prototype_db_prop_databaseState_value_offline = "prototype_db_prop_databaseState_value_offline"; + + + public const string prototype_db_prop_databaseState_value_recovering = "prototype_db_prop_databaseState_value_recovering"; + + + public const string prototype_db_prop_databaseState_value_recoveryPending = "prototype_db_prop_databaseState_value_recoveryPending"; + + + public const string prototype_db_prop_databaseState_value_restoring = "prototype_db_prop_databaseState_value_restoring"; + + + public const string prototype_db_prop_databaseState_value_shutdown = "prototype_db_prop_databaseState_value_shutdown"; + + + public const string prototype_db_prop_databaseState_value_standby = "prototype_db_prop_databaseState_value_standby"; + + + public const string prototype_db_prop_databaseState_value_suspect = "prototype_db_prop_databaseState_value_suspect"; + + + public const string prototype_db_prop_defaultCursor_value_global = "prototype_db_prop_defaultCursor_value_global"; + + + public const string prototype_db_prop_defaultCursor_value_local = "prototype_db_prop_defaultCursor_value_local"; + + + public const string prototype_db_prop_restrictAccess_value_multiple = "prototype_db_prop_restrictAccess_value_multiple"; + + + public const string prototype_db_prop_restrictAccess_value_restricted = "prototype_db_prop_restrictAccess_value_restricted"; + + + public const string prototype_db_prop_restrictAccess_value_single = "prototype_db_prop_restrictAccess_value_single"; + + + public const string prototype_db_prop_pageVerify_value_checksum = "prototype_db_prop_pageVerify_value_checksum"; + + + public const string prototype_db_prop_pageVerify_value_none = "prototype_db_prop_pageVerify_value_none"; + + + public const string prototype_db_prop_pageVerify_value_tornPageDetection = "prototype_db_prop_pageVerify_value_tornPageDetection"; + + + public const string prototype_db_prop_varDecimalEnabled = "prototype_db_prop_varDecimalEnabled"; + + + public const string compatibilityLevel_katmai = "compatibilityLevel_katmai"; + + + public const string prototype_db_prop_encryptionEnabled = "prototype_db_prop_encryptionEnabled"; + + + public const string prototype_db_prop_databasescopedconfig_value_off = "prototype_db_prop_databasescopedconfig_value_off"; + + + public const string prototype_db_prop_databasescopedconfig_value_on = "prototype_db_prop_databasescopedconfig_value_on"; + + + public const string prototype_db_prop_databasescopedconfig_value_primary = "prototype_db_prop_databasescopedconfig_value_primary"; + + + public const string error_db_prop_invalidleadingColumns = "error_db_prop_invalidleadingColumns"; + + + public const string compatibilityLevel_denali = "compatibilityLevel_denali"; + + + public const string compatibilityLevel_sql14 = "compatibilityLevel_sql14"; + + + public const string compatibilityLevel_sql15 = "compatibilityLevel_sql15"; + + + public const string compatibilityLevel_sqlvNext = "compatibilityLevel_sqlvNext"; + + + public const string general_containmentType_None = "general_containmentType_None"; + + + public const string general_containmentType_Partial = "general_containmentType_Partial"; + + + public const string filegroups_filestreamFiles = "filegroups_filestreamFiles"; + + + public const string prototype_file_noApplicableFileGroup = "prototype_file_noApplicableFileGroup"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index dcccd874..43a5b0da 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -1351,4 +1351,448 @@ Error parsing ScriptingListObjectsCompleteParams.ConnectionString property. + + Unavailable + + + + Current default filegroup: {0} + + + + New Filegroup for {0} + + + + Default + + + + Files + + + + Name + + + + Read-Only + + + + Autogrowth / Maxsize + + + + ... + + + + <default> + + + + Filegroup + + + + Logical Name + + + + File Type + + + + Initial Size (MB) + + + + <new filegroup> + + + + Path + + + + File Name + + + + <raw device> + + + + Bulk-logged + + + + Full + + + + Simple + + + + Select Database Owner + + + + None + + + + By {0} MB, Limited to {1} MB + + + + By {0} percent, Limited to {1} MB + + + + By {0} MB, Unlimited + + + + By {0} percent, Unlimited + + + + Unlimited + + + + Limited to {0} MB + + + + Automatic + + + + Service Broker + + + + Collation + + + + Cursor + + + + Miscellaneous + + + + Recovery + + + + State + + + + ANSI NULL Default + + + + ANSI NULLS Enabled + + + + ANSI Padding Enabled + + + + ANSI Warnings Enabled + + + + Arithmetic Abort Enabled + + + + Auto Close + + + + Auto Create Statistics + + + + Auto Shrink + + + + Auto Update Statistics + + + + Auto Update Statistics Asynchronously + + + + Case Sensitive + + + + Close Cursor on Commit Enabled + + + + Collation + + + + Concatenate Null Yields Null + + + + Database Compatibility Level + + + + Database State + + + + Default Cursor + + + + Full-Text Indexing Enabled + + + + Numeric Round-Abort + + + + Page Verify + + + + Quoted Identifiers Enabled + + + + Database Read-Only + + + + Recursive Triggers Enabled + + + + Restrict Access + + + + Select Into/Bulk Copy + + + + Honor Broker Priority + + + + Service Broker Identifier + + + + Broker Enabled + + + + Truncate Log on Checkpoint + + + + Cross-database Ownership Chaining Enabled + + + + Trustworthy + + + + Date Correlation Optimization Enabledprototype_db_prop_parameterization = Parameterization + + + + Forced + + + + Simple + + + + ROWS Data + + + + LOG + + + + FILESTREAM Data + + + + Not Applicable + + + + <default path> + + + + Open Connections + + + + To change the database properties, SQL Server must close all other connections to the database_ Are you sure you want to change the properties and close all other connections? + + + + AUTO_CLOSED + + + + EMERGENCY + + + + INACCESSIBLE + + + + NORMAL + + + + OFFLINE + + + + RECOVERING + + + + RECOVERY PENDING + + + + RESTORING + + + + SHUTDOWN + + + + STANDBY + + + + SUSPECT + + + + GLOBAL + + + + LOCAL + + + + MULTI_USER + + + + RESTRICTED_USER + + + + SINGLE_USER + + + + CHECKSUM + + + + NONE + + + + TORN_PAGE_DETECTION + + + + VarDecimal Storage Format Enabled + + + + SQL Server 2008 (100) + + + + Encryption Enabled + + + + OFF + + + + ON + + + + PRIMARY + + + + For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns + + + + SQL Server 2012 (110) + + + + SQL Server 2014 (120) + + + + SQL Server 2016 (130) + + + + SQL Server vNext (140) + + + + None + + + + Partial + + + + FILESTREAM Files + + + + No Applicable Filegroup + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 1b904a2d..51743cd3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -675,4 +675,126 @@ ScriptingParams_ConnectionString_Property_Invalid = Error parsing ScriptingParam ScriptingParams_FilePath_Property_Invalid = Invalid directory specified by the ScriptingParams.FilePath property. -ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = Error parsing ScriptingListObjectsCompleteParams.ConnectionString property. \ No newline at end of file +ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = Error parsing ScriptingListObjectsCompleteParams.ConnectionString property. + + + + +############################################################################ +# Admin Service + +unavailable = Unavailable +filegroup_dialog_defaultFilegroup = Current default filegroup: {0} +filegroup_dialog_title = New Filegroup for {0} +filegroups_default = Default +filegroups_files = Files +filegroups_name = Name +filegroups_readonly = Read-Only +general_autogrowth = Autogrowth / Maxsize +general_builderText = ... +general_default = <default> +general_fileGroup = Filegroup +general_fileName = Logical Name +general_fileType = File Type +general_initialSize = Initial Size (MB) +general_newFilegroup = <new filegroup> +general_path = Path +general_physicalFileName = File Name +general_rawDevice = <raw device> +general_recoveryModel_bulkLogged = Bulk-logged +general_recoveryModel_full = Full +general_recoveryModel_simple = Simple +general_titleSearchOwner = Select Database Owner + +prototype_autogrowth_disabled = None +prototype_autogrowth_restrictedGrowthByMB = By {0} MB, Limited to {1} MB +prototype_autogrowth_restrictedGrowthByPercent = By {0} percent, Limited to {1} MB +prototype_autogrowth_unrestrictedGrowthByMB = By {0} MB, Unlimited +prototype_autogrowth_unrestrictedGrowthByPercent = By {0} percent, Unlimited +prototype_autogrowth_unlimitedfilestream = Unlimited +prototype_autogrowth_limitedfilestream = Limited to {0} MB +prototype_db_category_automatic = Automatic +prototype_db_category_servicebroker = Service Broker +prototype_db_category_collation = Collation +prototype_db_category_cursor = Cursor +prototype_db_category_misc = Miscellaneous +prototype_db_category_recovery = Recovery +prototype_db_category_state = State +prototype_db_prop_ansiNullDefault = ANSI NULL Default +prototype_db_prop_ansiNulls = ANSI NULLS Enabled +prototype_db_prop_ansiPadding = ANSI Padding Enabled +prototype_db_prop_ansiWarnings = ANSI Warnings Enabled +prototype_db_prop_arithabort = Arithmetic Abort Enabled +prototype_db_prop_autoClose = Auto Close +prototype_db_prop_autoCreateStatistics = Auto Create Statistics +prototype_db_prop_autoShrink = Auto Shrink +prototype_db_prop_autoUpdateStatistics = Auto Update Statistics +prototype_db_prop_autoUpdateStatisticsAsync = Auto Update Statistics Asynchronously +prototype_db_prop_caseSensitive = Case Sensitive +prototype_db_prop_closeCursorOnCommit = Close Cursor on Commit Enabled +prototype_db_prop_collation = Collation +prototype_db_prop_concatNullYieldsNull = Concatenate Null Yields Null +prototype_db_prop_databaseCompatibilityLevel = Database Compatibility Level +prototype_db_prop_databaseState = Database State +prototype_db_prop_defaultCursor = Default Cursor +prototype_db_prop_fullTextIndexing = Full-Text Indexing Enabled +prototype_db_prop_numericRoundAbort = Numeric Round-Abort +prototype_db_prop_pageVerify = Page Verify +prototype_db_prop_quotedIdentifier = Quoted Identifiers Enabled +prototype_db_prop_readOnly = Database Read-Only +prototype_db_prop_recursiveTriggers = Recursive Triggers Enabled +prototype_db_prop_restrictAccess = Restrict Access +prototype_db_prop_selectIntoBulkCopy = Select Into/Bulk Copy +prototype_db_prop_honorBrokerPriority = Honor Broker Priority +prototype_db_prop_serviceBrokerGuid = Service Broker Identifier +prototype_db_prop_brokerEnabled = Broker Enabled +prototype_db_prop_truncateLogOnCheckpoint = Truncate Log on Checkpoint +prototype_db_prop_dbChaining = Cross-database Ownership Chaining Enabled +prototype_db_prop_trustworthy = Trustworthy +prototype_db_prop_dateCorrelationOptimization = Date Correlation Optimization Enabledprototype_db_prop_parameterization = Parameterization +prototype_db_prop_parameterization_value_forced = Forced +prototype_db_prop_parameterization_value_simple = Simple +prototype_file_dataFile = ROWS Data +prototype_file_logFile = LOG +prototype_file_filestreamFile = FILESTREAM Data +prototype_file_noFileGroup = Not Applicable +prototype_file_defaultpathstring = <default path> +title_openConnectionsMustBeClosed = Open Connections +warning_openConnectionsMustBeClosed=To change the database properties, SQL Server must close all other connections to the database_ Are you sure you want to change the properties and close all other connections? +prototype_db_prop_databaseState_value_autoClosed = AUTO_CLOSED +prototype_db_prop_databaseState_value_emergency = EMERGENCY +prototype_db_prop_databaseState_value_inaccessible = INACCESSIBLE +prototype_db_prop_databaseState_value_normal = NORMAL +prototype_db_prop_databaseState_value_offline = OFFLINE +prototype_db_prop_databaseState_value_recovering = RECOVERING +prototype_db_prop_databaseState_value_recoveryPending = RECOVERY PENDING +prototype_db_prop_databaseState_value_restoring = RESTORING +prototype_db_prop_databaseState_value_shutdown=SHUTDOWN +prototype_db_prop_databaseState_value_standby = STANDBY +prototype_db_prop_databaseState_value_suspect = SUSPECT +prototype_db_prop_defaultCursor_value_global = GLOBAL +prototype_db_prop_defaultCursor_value_local = LOCAL +prototype_db_prop_restrictAccess_value_multiple = MULTI_USER +prototype_db_prop_restrictAccess_value_restricted = RESTRICTED_USER +prototype_db_prop_restrictAccess_value_single = SINGLE_USER +prototype_db_prop_pageVerify_value_checksum = CHECKSUM +prototype_db_prop_pageVerify_value_none = NONE +prototype_db_prop_pageVerify_value_tornPageDetection = TORN_PAGE_DETECTION +prototype_db_prop_varDecimalEnabled = VarDecimal Storage Format Enabled +compatibilityLevel_katmai = SQL Server 2008 (100) +prototype_db_prop_encryptionEnabled = Encryption Enabled +prototype_db_prop_databasescopedconfig_value_off = OFF +prototype_db_prop_databasescopedconfig_value_on = ON +prototype_db_prop_databasescopedconfig_value_primary = PRIMARY +error_db_prop_invalidleadingColumns = For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns +compatibilityLevel_denali = SQL Server 2012 (110) +compatibilityLevel_sql14 = SQL Server 2014 (120) +compatibilityLevel_sql15 = SQL Server 2016 (130) +compatibilityLevel_sqlvNext = SQL Server vNext (140) +general_containmentType_None = None +general_containmentType_Partial = Partial +filegroups_filestreamFiles = FILESTREAM Files +prototype_file_noApplicableFileGroup = No Applicable Filegroup + + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 6c85dc7e..6836f9ad 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -1541,6 +1541,561 @@ System-Versioned + + Unavailable + Unavailable + + + + Current default filegroup: {0} + Current default filegroup: {0} + + + + New Filegroup for {0} + New Filegroup for {0} + + + + Default + Default + + + + Files + Files + + + + Name + Name + + + + Read-Only + Read-Only + + + + Autogrowth / Maxsize + Autogrowth / Maxsize + + + + ... + ... + + + + <default> + <default> + + + + Filegroup + Filegroup + + + + Logical Name + Logical Name + + + + File Type + File Type + + + + Initial Size (MB) + Initial Size (MB) + + + + <new filegroup> + <new filegroup> + + + + Path + Path + + + + File Name + File Name + + + + <raw device> + <raw device> + + + + Bulk-logged + Bulk-logged + + + + Full + Full + + + + Simple + Simple + + + + Select Database Owner + Select Database Owner + + + + None + None + + + + By {0} MB, Limited to {1} MB + By {0} MB, Limited to {1} MB + + + + By {0} percent, Limited to {1} MB + By {0} percent, Limited to {1} MB + + + + By {0} MB, Unlimited + By {0} MB, Unlimited + + + + By {0} percent, Unlimited + By {0} percent, Unlimited + + + + Unlimited + Unlimited + + + + Limited to {0} MB + Limited to {0} MB + + + + Automatic + Automatic + + + + Service Broker + Service Broker + + + + Collation + Collation + + + + Cursor + Cursor + + + + Miscellaneous + Miscellaneous + + + + Recovery + Recovery + + + + State + State + + + + ANSI NULL Default + ANSI NULL Default + + + + ANSI NULLS Enabled + ANSI NULLS Enabled + + + + ANSI Padding Enabled + ANSI Padding Enabled + + + + ANSI Warnings Enabled + ANSI Warnings Enabled + + + + Arithmetic Abort Enabled + Arithmetic Abort Enabled + + + + Auto Close + Auto Close + + + + Auto Create Statistics + Auto Create Statistics + + + + Auto Shrink + Auto Shrink + + + + Auto Update Statistics + Auto Update Statistics + + + + Auto Update Statistics Asynchronously + Auto Update Statistics Asynchronously + + + + Case Sensitive + Case Sensitive + + + + Close Cursor on Commit Enabled + Close Cursor on Commit Enabled + + + + Collation + Collation + + + + Concatenate Null Yields Null + Concatenate Null Yields Null + + + + Database Compatibility Level + Database Compatibility Level + + + + Database State + Database State + + + + Default Cursor + Default Cursor + + + + Full-Text Indexing Enabled + Full-Text Indexing Enabled + + + + Numeric Round-Abort + Numeric Round-Abort + + + + Page Verify + Page Verify + + + + Quoted Identifiers Enabled + Quoted Identifiers Enabled + + + + Database Read-Only + Database Read-Only + + + + Recursive Triggers Enabled + Recursive Triggers Enabled + + + + Restrict Access + Restrict Access + + + + Select Into/Bulk Copy + Select Into/Bulk Copy + + + + Honor Broker Priority + Honor Broker Priority + + + + Service Broker Identifier + Service Broker Identifier + + + + Broker Enabled + Broker Enabled + + + + Truncate Log on Checkpoint + Truncate Log on Checkpoint + + + + Cross-database Ownership Chaining Enabled + Cross-database Ownership Chaining Enabled + + + + Trustworthy + Trustworthy + + + + Date Correlation Optimization Enabledprototype_db_prop_parameterization = Parameterization + Date Correlation Optimization Enabledprototype_db_prop_parameterization = Parameterization + + + + Forced + Forced + + + + Simple + Simple + + + + ROWS Data + ROWS Data + + + + LOG + LOG + + + + FILESTREAM Data + FILESTREAM Data + + + + Not Applicable + Not Applicable + + + + <default path> + <default path> + + + + Open Connections + Open Connections + + + + To change the database properties, SQL Server must close all other connections to the database_ Are you sure you want to change the properties and close all other connections? + To change the database properties, SQL Server must close all other connections to the database_ Are you sure you want to change the properties and close all other connections? + + + + AUTO_CLOSED + AUTO_CLOSED + + + + EMERGENCY + EMERGENCY + + + + INACCESSIBLE + INACCESSIBLE + + + + NORMAL + NORMAL + + + + OFFLINE + OFFLINE + + + + RECOVERING + RECOVERING + + + + RECOVERY PENDING + RECOVERY PENDING + + + + RESTORING + RESTORING + + + + SHUTDOWN + SHUTDOWN + + + + STANDBY + STANDBY + + + + SUSPECT + SUSPECT + + + + GLOBAL + GLOBAL + + + + LOCAL + LOCAL + + + + MULTI_USER + MULTI_USER + + + + RESTRICTED_USER + RESTRICTED_USER + + + + SINGLE_USER + SINGLE_USER + + + + CHECKSUM + CHECKSUM + + + + NONE + NONE + + + + TORN_PAGE_DETECTION + TORN_PAGE_DETECTION + + + + VarDecimal Storage Format Enabled + VarDecimal Storage Format Enabled + + + + SQL Server 2008 (100) + SQL Server 2008 (100) + + + + Encryption Enabled + Encryption Enabled + + + + OFF + OFF + + + + ON + ON + + + + PRIMARY + PRIMARY + + + + For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns + For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns + + + + SQL Server 2012 (110) + SQL Server 2012 (110) + + + + SQL Server 2014 (120) + SQL Server 2014 (120) + + + + SQL Server 2016 (130) + SQL Server 2016 (130) + + + + SQL Server vNext (140) + SQL Server vNext (140) + + + + None + None + + + + Partial + Partial + + + + FILESTREAM Files + FILESTREAM Files + + + + No Applicable Filegroup + No Applicable Filegroup + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs b/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs index c042a023..09356f69 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs @@ -14,7 +14,9 @@ using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Channel; using Microsoft.SqlTools.Utility; - +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Admin; + namespace Microsoft.SqlTools.ServiceLayer.Hosting { /// @@ -24,6 +26,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting /// public sealed class ServiceHost : ServiceHostBase { + private const string ProviderName = "MSSQL"; + private const string ProviderDescription = "Microsoft SQL Server"; + private const string ProviderProtocolVersion = "1.0"; + /// /// This timeout limits the amount of time that shutdown tasks can take to complete /// prior to the process shutting down. @@ -186,6 +192,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting }); } + /// + /// Handles a request for the capabilities request + /// internal async Task HandleCapabilitiesRequest( CapabilitiesRequest initializeParams, RequestContext requestContext) @@ -195,300 +204,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting { Capabilities = new DmpServerCapabilities { - ProtocolVersion = "1.0", - ProviderName = "MSSQL", - ProviderDisplayName = "Microsoft SQL Server", - ConnectionProvider = ServiceHost.BuildConnectionProviderOptions() + ProtocolVersion = ServiceHost.ProviderProtocolVersion, + ProviderName = ServiceHost.ProviderName, + ProviderDisplayName = ServiceHost.ProviderDescription, + ConnectionProvider = ConnectionProviderOptionsHelper.BuildConnectionProviderOptions(), + AdminServicesProvider = AdminServicesProviderOptionsHelper.BuildAdminServicesProviderOptions() } } ); } - internal static ConnectionProviderOptions BuildConnectionProviderOptions() - { - return new ConnectionProviderOptions - { - Options = new ConnectionOption[] - { - new ConnectionOption - { - Name = "server", - DisplayName = "Server Name", - Description = "Name of the SQL Server instance", - ValueType = ConnectionOption.ValueTypeString, - SpecialValueType = ConnectionOption.SpecialValueServerName, - IsIdentity = true, - IsRequired = true, - GroupName = "Source" - }, - new ConnectionOption - { - Name = "database", - DisplayName = "Database Name", - Description = "The name of the initial catalog or database int the data source", - ValueType = ConnectionOption.ValueTypeString, - SpecialValueType = ConnectionOption.SpecialValueDatabaseName, - IsIdentity = true, - IsRequired = false, - GroupName = "Source" - }, - new ConnectionOption - { - Name = "authenticationType", - DisplayName = "Authentication Type", - Description = "Specifies the method of authenticating with SQL Server", - ValueType = ConnectionOption.ValueTypeCategory, - SpecialValueType = ConnectionOption.SpecialValueAuthType, - CategoryValues = new CategoryValue[] - { new CategoryValue {DisplayName = "SQL Login", Name = "SqlLogin" }, - new CategoryValue {DisplayName = "Integrated Auth", Name= "Integrated" } - }, - IsIdentity = true, - IsRequired = true, - GroupName = "Security" - }, - new ConnectionOption - { - Name = "user", - DisplayName = "User Name", - Description = "Indicates the user ID to be used when connecting to the data source", - ValueType = ConnectionOption.ValueTypeString, - SpecialValueType = ConnectionOption.SpecialValueUserName, - IsIdentity = true, - IsRequired = true, - GroupName = "Security" - }, - new ConnectionOption - { - Name = "password", - DisplayName = "Password", - Description = "Indicates the password to be used when connecting to the data source", - ValueType = ConnectionOption.ValueTypePassword, - SpecialValueType = ConnectionOption.SpecialValuePasswordName, - IsIdentity = true, - IsRequired = true, - GroupName = "Security" - }, - new ConnectionOption - { - Name = "applicationIntent", - DisplayName = "Application Intent", - Description = "Declares the application workload type when connecting to a server", - ValueType = ConnectionOption.ValueTypeCategory, - CategoryValues = new CategoryValue[] { - new CategoryValue { Name = "ReadWrite", DisplayName = "ReadWrite" }, - new CategoryValue {Name = "ReadOnly", DisplayName = "ReadOnly" } - }, - GroupName = "Initialization" - }, - new ConnectionOption - { - Name = "asynchronousProcessing", - DisplayName = "Asynchronous processing enabled", - Description = "When true, enables usage of the Asynchronous functionality in the .Net Framework Data Provider", - ValueType = ConnectionOption.ValueTypeBoolean, - GroupName = "Initialization" - }, - new ConnectionOption - { - Name = "connectTimeout", - DisplayName = "Connect Timeout", - Description = - "The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error", - ValueType = ConnectionOption.ValueTypeNumber, - DefaultValue = "15", - GroupName = "Initialization" - }, - new ConnectionOption - { - Name = "currentLanguage", - DisplayName = "Current Language", - Description = "The SQL Server language record name", - ValueType = ConnectionOption.ValueTypeString, - GroupName = "Initialization" - }, - new ConnectionOption - { - Name = "columnEncryptionSetting", - DisplayName = "Column Encryption Setting", - Description = "Default column encryption setting for all the commands on the connection", - ValueType = ConnectionOption.ValueTypeCategory, - GroupName = "Security", - CategoryValues = new CategoryValue[] { - new CategoryValue { Name = "Disabled" }, - new CategoryValue {Name = "Enabled" } - } - }, - new ConnectionOption - { - Name = "encrypt", - DisplayName = "Encrypt", - Description = - "When true, SQL Server uses SSL encryption for all data sent between the client and server if the servers has a certificate installed", - GroupName = "Security", - ValueType = ConnectionOption.ValueTypeBoolean - }, - new ConnectionOption - { - Name = "persistSecurityInfo", - DisplayName = "Persist Security Info", - Description = "When false, security-sensitive information, such as the password, is not returned as part of the connection", - GroupName = "Security", - ValueType = ConnectionOption.ValueTypeBoolean - }, - new ConnectionOption - { - Name = "trustServerCertificate", - DisplayName = "Trust Server Certificate", - Description = "When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate", - GroupName = "Security", - ValueType = ConnectionOption.ValueTypeBoolean - }, - new ConnectionOption - { - Name = "attachedDBFileName", - DisplayName = "Attached DB File Name", - Description = "The name of the primary file, including the full path name, of an attachable database", - ValueType = ConnectionOption.ValueTypeString, - GroupName = "Source" - }, - new ConnectionOption - { - Name = "contextConnection", - DisplayName = "Context Connection", - Description = "When true, indicates the connection should be from the SQL server context. Available only when running in the SQL Server process", - ValueType = ConnectionOption.ValueTypeBoolean, - GroupName = "Source" - }, - new ConnectionOption - { - Name = "port", - DisplayName = "Port", - ValueType = ConnectionOption.ValueTypeNumber - }, - new ConnectionOption - { - Name = "connectRetryCount", - DisplayName = "Connect Retry Count", - Description = "Number of attempts to restore connection", - ValueType = ConnectionOption.ValueTypeNumber, - DefaultValue = "1", - GroupName = "Connection Resiliency" - }, - new ConnectionOption - { - Name = "connectRetryInterval", - DisplayName = "Connect Retry Interval", - Description = "Delay between attempts to restore connection", - ValueType = ConnectionOption.ValueTypeNumber, - DefaultValue = "10", - GroupName = "Connection Resiliency" - - }, - new ConnectionOption - { - Name = "applicationName", - DisplayName = "Application Name", - Description = "The name of the application", - ValueType = ConnectionOption.ValueTypeString, - GroupName = "Context" - }, - new ConnectionOption - { - Name = "workstationId", - DisplayName = "Workstation Id", - Description = "The name of the workstation connecting to SQL Server", - ValueType = ConnectionOption.ValueTypeString, - GroupName = "Context" - }, - new ConnectionOption - { - Name = "pooling", - DisplayName = "Pooling", - Description = "When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool", - ValueType = ConnectionOption.ValueTypeBoolean, - GroupName = "Pooling" - }, - new ConnectionOption - { - Name = "maxPoolSize", - DisplayName = "Max Pool Size", - Description = "The maximum number of connections allowed in the pool", - ValueType = ConnectionOption.ValueTypeNumber, - GroupName = "Pooling" - }, - new ConnectionOption - { - Name = "minPoolSize", - DisplayName = "Min Pool Size", - Description = "The minimum number of connections allowed in the pool", - ValueType = ConnectionOption.ValueTypeNumber, - GroupName = "Pooling" - }, - new ConnectionOption - { - Name = "loadBalanceTimeout", - DisplayName = "Load Balance Timeout", - Description = "The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed", - ValueType = ConnectionOption.ValueTypeNumber, - GroupName = "Pooling" - }, - new ConnectionOption - { - Name = "replication", - DisplayName = "Replication", - Description = "Used by SQL Server in Replication", - ValueType = ConnectionOption.ValueTypeBoolean, - GroupName = "Replication" - }, - new ConnectionOption - { - Name = "attachDbFilename", - DisplayName = "Attach Db Filename", - ValueType = ConnectionOption.ValueTypeString - }, - new ConnectionOption - { - Name = "failoverPartner", - DisplayName = "Failover Partner", - Description = "the name or network address of the instance of SQL Server that acts as a failover partner", - ValueType = ConnectionOption.ValueTypeString, - GroupName = " Source" - }, - new ConnectionOption - { - Name = "multiSubnetFailover", - DisplayName = "Multi Subnet Failover", - ValueType = ConnectionOption.ValueTypeBoolean - }, - new ConnectionOption - { - Name = "multipleActiveResultSets", - DisplayName = "Multiple Active ResultSets", - Description = "When true, multiple result sets can be returned and read from one connection", - ValueType = ConnectionOption.ValueTypeBoolean, - GroupName = "Advanced" - }, - new ConnectionOption - { - Name = "packetSize", - DisplayName = "Packet Size", - Description = "Size in bytes of the network packets used to communicate with an instance of SQL Server", - ValueType = ConnectionOption.ValueTypeNumber, - GroupName = "Advanced" - }, - new ConnectionOption - { - Name = "typeSystemVersion", - DisplayName = "Type System Version", - Description = "Indicates which server type system then provider will expose through the DataReader", - ValueType = ConnectionOption.ValueTypeString, - GroupName = "Advanced" - } - } - }; - } - /// /// Handles the version request. Sends back the server version as result. /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/project.json b/src/Microsoft.SqlTools.ServiceLayer/project.json index d240f6e2..f4dcfe3f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/project.json +++ b/src/Microsoft.SqlTools.ServiceLayer/project.json @@ -4,8 +4,9 @@ "buildOptions": { "debugType": "portable", "emitEntryPoint": true, - "preserveCompilationContext": true - }, + "preserveCompilationContext": true, + "allowUnsafe": true + }, "configurations": { "Integration": { "buildOptions": { @@ -20,7 +21,7 @@ "Newtonsoft.Json": "9.0.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.17049.0", + "Microsoft.SqlServer.Smo": "140.17050.0", "Microsoft.SqlServer.Management.SqlScriptPublishModel": "140.17049.0", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", @@ -34,6 +35,7 @@ "System.Diagnostics.Process": "4.1.0", "System.Threading.Thread": "4.0.0", "System.Runtime.Loader": "4.0.0", + "System.Xml.XmlDocument": "4.0.1", "System.Composition": "1.0.31-beta-24326-02", "Microsoft.Extensions.DependencyModel": "1.0.0", "Microsoft.SqlTools.Hosting": { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Admin/DatabaseAdminTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Admin/DatabaseAdminTests.cs new file mode 100644 index 00000000..812c0a86 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Admin/DatabaseAdminTests.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.LanguageServices; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +using Xunit; +using Moq; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; +using Microsoft.SqlTools.ServiceLayer.Admin; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AdminServices +{ + /// + /// Tests for the ServiceHost Language Service tests + /// + public class CreateDatabaseTests + { + private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects() + { + var textDocument = new TextDocumentPosition + { + TextDocument = new TextDocumentIdentifier { Uri = Test.Common.Constants.OwnerUri }, + Position = new Position + { + Line = 0, + Character = 0 + } + }; + + var result = LiveConnectionHelper.InitLiveConnectionInfo(); + result.TextDocumentPosition = textDocument; + return result; + } + + /// + /// Validate creating a database with valid input + /// + [Fact] + public async void CreateDatabaseWithValidInputTest() + { + var result = GetLiveAutoCompleteTestObjects(); + var requestContext = new Mock>(); + requestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + var dbParams = new CreateDatabaseParams + { + OwnerUri = result.ConnectionInfo.OwnerUri, + DatabaseInfo = new DatabaseInfo() + }; + + await AdminService.HandleCreateDatabaseRequest(dbParams, requestContext.Object); + + requestContext.VerifyAll(); + } + + + /// + /// Get a default database info object + /// + [Fact] + public async void GetDefaultDatebaseInfoTest() + { + var result = GetLiveAutoCompleteTestObjects(); + var requestContext = new Mock>(); + requestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + var dbParams = new DefaultDatabaseInfoParams + { + OwnerUri = result.ConnectionInfo.OwnerUri + }; + + await AdminService.HandleDefaultDatabaseInfoRequest(dbParams, requestContext.Object); + + requestContext.VerifyAll(); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/project.json b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/project.json index 40b5bed1..b522d847 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/project.json +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/project.json @@ -17,7 +17,7 @@ "System.Runtime.Serialization.Primitives": "4.1.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.17049.0", + "Microsoft.SqlServer.Smo": "140.17050.0", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", "System.ComponentModel.TypeConverter": "4.1.0", diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/project.json b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/project.json index 54f32f67..c763dc72 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/project.json +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/project.json @@ -18,7 +18,7 @@ "System.Runtime.Serialization.Primitives": "4.1.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.17049.0", + "Microsoft.SqlServer.Smo": "140.17050.0", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", "System.ComponentModel.TypeConverter": "4.1.0", diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Admin/CreateDatabaseTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Admin/CreateDatabaseTests.cs deleted file mode 100644 index 3cc64b49..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Admin/CreateDatabaseTests.cs +++ /dev/null @@ -1,16 +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 System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Admin -{ - public class CreateDatabaseTests - { - } -} diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Admin/CreateLoginTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Admin/CreateLoginTests.cs deleted file mode 100644 index c4716a95..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Admin/CreateLoginTests.cs +++ /dev/null @@ -1,16 +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 System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Admin -{ - public class CreateLoginTests - { - } -} diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionDetailsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionDetailsTests.cs index c44b8d8d..c19acf9a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionDetailsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionDetailsTests.cs @@ -7,7 +7,7 @@ using System.Linq; using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Xunit; - +using Microsoft.SqlTools.ServiceLayer.Connection; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection { @@ -121,7 +121,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection public void ConnectionDetailsOptionsShouldBeDefinedInConnectionProviderOptions() { ConnectionDetails details = new ConnectionDetails(); - ConnectionProviderOptions optionMetadata = Hosting.ServiceHost.BuildConnectionProviderOptions(); + ConnectionProviderOptions optionMetadata = ConnectionProviderOptionsHelper.BuildConnectionProviderOptions(); var index = 0; var expectedForStrings = "Value for strings"; diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/project.json b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/project.json index 617939e2..01f94331 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/project.json +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/project.json @@ -19,7 +19,7 @@ "System.Runtime.Serialization.Primitives": "4.1.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.17049.0", + "Microsoft.SqlServer.Smo": "140.17050.0", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", "System.ComponentModel.TypeConverter": "4.1.0",