User management support classes (#1856)

* WIP

* Fix nullable warnings in UserData class

* WIP2

* WIP

* Refresh database prototype classes

* Fix some typos & merge issues

* WIP

* WIP

* WIP

* Additional updates

* Remove unneded using
This commit is contained in:
Karl Burtram
2023-02-08 18:02:08 -08:00
committed by GitHub
parent ee086e2067
commit 2ef5f0918a
24 changed files with 3584 additions and 1336 deletions

View File

@@ -6,35 +6,94 @@
#nullable disable
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.SqlServer.Management.Common;
using System.Diagnostics;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.Management;
using SizeUnits = Microsoft.SqlTools.ServiceLayer.Management.DbSize.SizeUnits;
using static Microsoft.SqlTools.ServiceLayer.Management.DbSize;
namespace Microsoft.SqlTools.ServiceLayer.Admin
{
public static class AzureSqlDbHelper
{
/// <summary>
/// Registry sub key for the AzureServiceObjectives overrides
/// </summary>
private const string AzureServiceObjectivesRegSubKey = @"AzureServiceObjectives";
/// <summary>
/// Contains the various editions available for an Azure Database
/// The implementation is opaque to consumers
/// </summary>
/// ****IMPORTANT**** - If updating this enum make sure that the other logic in this class is updated as well
public enum AzureEdition
[DebuggerDisplay("{Name,nq}")]
public class AzureEdition
{
Web = 0,
Business = 1,
Basic = 2,
Standard = 3,
Premium = 4,
DataWarehouse = 5,
PremiumRS = 6
public static readonly AzureEdition Basic = new AzureEdition("Basic", "SR.BasicAzureEdition");
public static readonly AzureEdition Standard = new AzureEdition("Standard", "SR.StandardAzureEdition");
public static readonly AzureEdition Premium = new AzureEdition("Premium", "SR.PremiumAzureEdition");
public static readonly AzureEdition DataWarehouse = new AzureEdition("DataWarehouse", "SR.DataWarehouseAzureEdition");
public static readonly AzureEdition GeneralPurpose = new AzureEdition("GeneralPurpose", "SR.GeneralPurposeAzureEdition");
public static readonly AzureEdition BusinessCritical = new AzureEdition("BusinessCritical", "SR.BusinessCriticalAzureEdition");
public static readonly AzureEdition Hyperscale = new AzureEdition("Hyperscale", "SR.HyperscaleAzureEdition");
// Free does not offer DatabaseSize >=1GB, hence it's not "supported".
//public static readonly AzureEdition Free = new AzureEdition("Free", SR.FreeAzureEdition);
// Stretch and system do not seem to be applicable, so I'm commenting them out
//public static readonly AzureEdition Stretch = new AzureEdition("Stretch", SR.StretchAzureEdition);
//public static readonly AzureEdition System = new AzureEdition("System", SR.SystemAzureEdition);
internal string Name { get; private set; }
internal string DisplayName { get; private set; }
internal AzureEdition(string name, string displayName)
{
Name = name;
DisplayName = displayName;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is AzureEdition && ((AzureEdition)obj).Name.Equals(Name);
}
public static bool operator ==(AzureEdition left, AzureEdition right)
{
return ReferenceEquals(left, right) || ((object)left != null && left.Equals(right));
}
public static bool operator !=(AzureEdition left, AzureEdition right)
{
return !(left == right);
}
public override string ToString()
{
return Name;
}
}
/// <summary>
/// Given a string, returns the matching AzureEdition instance.
/// </summary>
/// <param name="edition"></param>
/// <returns></returns>
public static AzureEdition AzureEditionFromString(string edition)
{
var azureEdition =
AzureServiceObjectiveInfo.Keys.FirstOrDefault(
key => key.Name.ToLowerInvariant().Equals(edition.ToLowerInvariant()));
if (azureEdition != null)
{
return azureEdition;
}
if (edition.Contains('\''))
{
throw new ArgumentException("ErrorInvalidEdition");
}
// we don't know what it is but Azure lets you send any value you want
// including an empty string
return new AzureEdition(edition.ToLowerInvariant(), edition);
}
/// <summary>
@@ -45,47 +104,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
<AzureEdition, KeyValuePair<int, DbSize[]>>
{
{
AzureEdition.Web, new KeyValuePair<int, DbSize[]>(
1, //1GB
new[]
{
new DbSize(100, SizeUnits.MB),
new DbSize(1, SizeUnits.GB), //Default
new DbSize(5, SizeUnits.GB)
})
},
{
AzureEdition.Business, new KeyValuePair<int, DbSize[]>(
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<int, DbSize[]>(
3, //2GB
AzureEdition.Basic,
new KeyValuePair<int, DbSize[]>(
4, //2GB
new[]
{
new DbSize(100, SizeUnits.MB),
new DbSize(250, SizeUnits.MB),
new DbSize(500, SizeUnits.MB),
new DbSize(1, SizeUnits.GB),
new DbSize(2, SizeUnits.GB) //Default
new DbSize(2, SizeUnits.GB),
})
},
{
AzureEdition.Standard,
new KeyValuePair<int, DbSize[]>(
13, //250GB
14, //250GB
new[]
{
new DbSize(100, SizeUnits.MB),
new DbSize(250, SizeUnits.MB),
new DbSize(500, SizeUnits.MB),
new DbSize(1, SizeUnits.GB),
new DbSize(2, SizeUnits.GB),
@@ -98,16 +136,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
new DbSize(100, SizeUnits.GB),
new DbSize(150, SizeUnits.GB),
new DbSize(200, SizeUnits.GB),
new DbSize(250, SizeUnits.GB) //Default
new DbSize(250, SizeUnits.GB), //Default
new DbSize(300, SizeUnits.GB),
new DbSize(400, SizeUnits.GB),
new DbSize(500, SizeUnits.GB),
new DbSize(750, SizeUnits.GB),
new DbSize(1024, SizeUnits.GB),
})
},
{
AzureEdition.Premium,
new KeyValuePair<int, DbSize[]>(
16, //500GB
17, //500GB
new[]
{
new DbSize(100, SizeUnits.MB),
new DbSize(250, SizeUnits.MB),
new DbSize(500, SizeUnits.MB),
new DbSize(1, SizeUnits.GB),
new DbSize(2, SizeUnits.GB),
@@ -124,6 +168,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
new DbSize(300, SizeUnits.GB),
new DbSize(400, SizeUnits.GB),
new DbSize(500, SizeUnits.GB), //Default
new DbSize(750, SizeUnits.GB),
new DbSize(1024, SizeUnits.GB) //Following portal to display this as GB instead of 1TB
})
},
@@ -143,23 +188,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
new DbSize(20480, SizeUnits.GB),
new DbSize(30720, SizeUnits.GB),
new DbSize(40960, SizeUnits.GB),
new DbSize(51200, SizeUnits.GB)
new DbSize(51200, SizeUnits.GB),
new DbSize(61440, SizeUnits.GB),
new DbSize(71680, SizeUnits.GB),
new DbSize(81920, SizeUnits.GB),
new DbSize(92160, SizeUnits.GB),
new DbSize(102400, SizeUnits.GB),
new DbSize(153600, SizeUnits.GB),
new DbSize(204800, SizeUnits.GB),
new DbSize(245760, SizeUnits.GB),
})
},
{
AzureEdition.PremiumRS,
AzureEdition.GeneralPurpose,
new KeyValuePair<int, DbSize[]>(
16, //500GB
0, //32GB
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(32, SizeUnits.GB),
new DbSize(40, SizeUnits.GB),
new DbSize(50, SizeUnits.GB),
new DbSize(100, SizeUnits.GB),
@@ -168,34 +215,154 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
new DbSize(250, SizeUnits.GB),
new DbSize(300, SizeUnits.GB),
new DbSize(400, SizeUnits.GB),
new DbSize(500, SizeUnits.GB), //Default
new DbSize(500, SizeUnits.GB),
new DbSize(750, SizeUnits.GB),
new DbSize(1024, SizeUnits.GB), //Following portal to display this as GB instead of 1TB
new DbSize(1536, SizeUnits.GB),
new DbSize(3072, SizeUnits.GB),
new DbSize(4096, SizeUnits.GB),
})
},
{
AzureEdition.BusinessCritical,
new KeyValuePair<int, DbSize[]>(
0, //32GB
new[]
{
new DbSize(32, 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),
new DbSize(750, SizeUnits.GB),
new DbSize(1024, SizeUnits.GB), //Following portal to display this as GB instead of 1TB
new DbSize(1536, SizeUnits.GB),
new DbSize(2048, SizeUnits.GB),
new DbSize(4096, SizeUnits.GB)
})
},
{
AzureEdition.Hyperscale,
new KeyValuePair<int, DbSize[]>(0, new[] { new DbSize(0, SizeUnits.MB) })
},
};
/// <summary>
/// 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).
/// can be overridden in the UI.
///
/// The key is the index of the default value for the list
/// </summary>
/// <remarks>Try to keep this data structure (particularly the default values for each SLO) in sync with
/// the heuristic in TryGetAzureServiceLevelObjective() in %SDXROOT%\sql\ssms\core\sqlmanagerui\src\azureservicelevelobjectiveprovider.cs
/// </remarks>
private static readonly Dictionary<AzureEdition, KeyValuePair<int, string[]>> AzureServiceObjectiveInfo = new Dictionary
<AzureEdition, KeyValuePair<int, string[]>>
{
{AzureEdition.Basic, new KeyValuePair<int, string[]>(0, new[] {"Basic"})},
{AzureEdition.Standard, new KeyValuePair<int, string[]>(2, new[] {"S0", "S1", "S2", "S3"})},
{AzureEdition.Basic, new KeyValuePair<int, string[]>(0, new string[] {"Basic"})},
{
AzureEdition.Standard,
new KeyValuePair<int, string[]>(0, new[] {"S0", "S1", "S2", "S3", "S4", "S6", "S7", "S9", "S12"})
},
{AzureEdition.Premium, new KeyValuePair<int, string[]>(0, new[] {"P1", "P2", "P4", "P6", "P11", "P15"})},
{AzureEdition.PremiumRS, new KeyValuePair<int, string[]>(0, new []{"PRS1", "PRS2", "PRS4", "PRS6"})},
{AzureEdition.DataWarehouse, new KeyValuePair<int, string[]>(3, new[] {"DW100", "DW200", "DW300", "DW400", "DW500", "DW600", "DW1000", "DW1200", "DW1500", "DW2000", "DW3000", "DW6000"})}
{
AzureEdition.DataWarehouse,
new KeyValuePair<int, string[]>(3,
new[]
{
"DW100", "DW200", "DW300", "DW400", "DW500", "DW600", "DW1000", "DW1200", "DW1500", "DW2000",
"DW3000", "DW6000", "DW1000c","DW1500c","DW2000c",
"DW2500c","DW3000c","DW5000c","DW6000c","DW7500c",
"DW10000c","DW15000c","DW30000c"
})
},
{
// Added missing Vcore sku's
// Reference:https://docs.microsoft.com/en-us/azure/sql-database/sql-database-vcore-resource-limits-single-databases
AzureEdition.GeneralPurpose,
new KeyValuePair<int, string[]>(6 /* Default = GP_Gen5_2 */,
new[]
{
"GP_Gen4_1", "GP_Gen4_2", "GP_Gen4_4", "GP_Gen4_8", "GP_Gen4_16","GP_Gen4_24",
"GP_Gen5_2","GP_Gen5_4","GP_Gen5_8","GP_Gen5_16","GP_Gen5_24","GP_Gen5_32","GP_Gen5_40","GP_Gen5_80"
})
},
{
// Added missing Vcore sku's
// Reference:https://docs.microsoft.com/en-us/azure/sql-database/sql-database-vcore-resource-limits-single-databases
AzureEdition.BusinessCritical,
new KeyValuePair<int, string[]>(6 /* Default = BC_Gen5_2 */,
new[]
{ "BC_Gen4_1", "BC_Gen4_2", "BC_Gen4_4", "BC_Gen4_8", "BC_Gen4_16","BC_Gen4_24",
"BC_Gen5_2","BC_Gen5_4","BC_Gen5_8","BC_Gen5_16","BC_Gen5_24", "BC_Gen5_32", "BC_Gen5_40","BC_Gen5_80"
})
},
{
// HS_Gen5_2 is the default since, as of 2/25/2020, customers, unless on an allowed list, are already prevented from choosing Gen4.
AzureEdition.Hyperscale,
new KeyValuePair<int, string[]>(11, new[] {
"HS_Gen4_1", "HS_Gen4_2", "HS_Gen4_3", "HS_Gen4_4", "HS_Gen4_5", "HS_Gen4_6", "HS_Gen4_7", "HS_Gen4_8", "HS_Gen4_9", "HS_Gen4_10",
"HS_Gen4_24", "HS_Gen5_2", "HS_Gen5_4", "HS_Gen5_6", "HS_Gen5_8", "HS_Gen5_10", "HS_Gen5_14", "HS_Gen5_16", "HS_Gen5_18", "HS_Gen5_20",
"HS_Gen5_24", "HS_Gen5_32", "HS_Gen5_40", "HS_Gen5_80"
})
}
};
/// <summary>
/// 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.
/// </summary>
static AzureSqlDbHelper()
//Supported BackupStorageRedundancy doc link:https://docs.microsoft.com/en-us/sql/t-sql/statements/create-database-transact-sql?view=azuresqldb-current&tabs=sqlpool
private static readonly Dictionary<string, string> bsrAPIToUIValueMapping = new Dictionary<string, string>()
{
{ "GRS", "Geo" },
{ "LRS", "Local" },
{ "ZRS", "Zone" }
};
//KeyValuePair contains the BackupStorageRedundancy values for all azure editions.
private static readonly KeyValuePair<int, string[]> keyValuePair = new KeyValuePair<int, string[]>(0, bsrAPIToUIValueMapping.Values.ToArray());
private static readonly Dictionary<AzureEdition, KeyValuePair<int, string[]>> AzureBackupStorageRedundancy = new Dictionary
<AzureEdition, KeyValuePair<int, string[]>>
{
{
AzureEdition.Basic, keyValuePair
},
{
AzureEdition.Standard, keyValuePair
},
{
AzureEdition.Premium, keyValuePair
},
{
AzureEdition.DataWarehouse, keyValuePair
},
{
AzureEdition.GeneralPurpose, keyValuePair
},
{
AzureEdition.BusinessCritical, keyValuePair
},
{
AzureEdition.Hyperscale, keyValuePair
}
};
/// <summary>
/// Get the storageAccount Type string value from the dictionary backupStorageTypes.
/// </summary>
/// <param name="storageAccountType">Current StorageAccountType</param>
/// <returns>StorageAccountType string value for the current storageType</returns>
public static string GetStorageAccountTypeFromString(string storageAccountType)
{
if (bsrAPIToUIValueMapping.ContainsKey(storageAccountType))
{
return bsrAPIToUIValueMapping[storageAccountType];
}
return storageAccountType;
}
/// <summary>
@@ -240,6 +407,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
return false;
}
/// <summary>
/// Get the backupStorageRedundancy value for the given azure edition.
/// </summary>
/// <param name="edition">Azure Edition</param>
/// <param name="backupStorageRedundancy">Supported BackupStorageRedundancy value</param>
/// <returns>backupStorageRedundancy value for the given azure edition</returns>
public static bool TryGetBackupStorageRedundancy(AzureEdition edition,
out KeyValuePair<int, string[]> backupStorageRedundancy)
{
if (AzureBackupStorageRedundancy.TryGetValue(edition, out backupStorageRedundancy))
{
return true;
}
backupStorageRedundancy = new KeyValuePair<int, string[]>(-1, new string[0]);
return false;
}
/// <summary>
/// Gets the default database size for a specified Azure Edition
/// </summary>
@@ -270,7 +456,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
KeyValuePair<int, string[]> pair;
if (AzureServiceObjectiveInfo.TryGetValue(edition, out pair))
if (TryGetServiceObjectiveInfo(edition, out pair))
{
//Bounds check since this value can be entered by users
if (pair.Key >= 0 && pair.Key < pair.Value.Length)
@@ -282,6 +468,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
return defaultServiceObjective;
}
public static string GetDefaultBackupStorageRedundancy(AzureEdition edition)
{
string defaultBackupStorageRedundancy = "";
KeyValuePair<int, string[]> pair;
if (TryGetBackupStorageRedundancy(edition, out pair))
{
//Bounds check since this value can be entered by users
if (pair.Key >= 0 && pair.Key < pair.Value.Length)
{
defaultBackupStorageRedundancy = pair.Value[pair.Key];
}
}
return defaultBackupStorageRedundancy;
}
/// <summary>
/// Gets the localized Azure Edition display name
/// </summary>
@@ -289,82 +493,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <returns></returns>
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;
return edition.DisplayName;
}
/// <summary>
/// Parses a display name back into its corresponding AzureEdition.
/// Parses a display name back into its corresponding AzureEdition.
/// If it doesn't match a known edition, returns one whose Name is a lowercase version of the
/// given displayName
/// </summary>
/// <param name="displayName"></param>
/// <param name="edition"></param>
/// <returns>TRUE if the conversion succeeded, FALSE if it did not. </returns>
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;
edition = AzureServiceObjectiveInfo.Keys.FirstOrDefault(key => key.DisplayName.Equals(displayName)) ??
AzureEditionFromString(displayName);
return true;
}
/// <summary>
@@ -374,23 +518,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// 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.
/// <returns></returns>
public static IEnumerable<AzureEdition> GetValidAzureEditionOptions(ServerVersion version)
public static IEnumerable<AzureEdition> GetValidAzureEditionOptions(object unused)
{
//Azure v12 and above doesn't have the Web and Business tiers
if (version.Major >= 12)
{
return new List<AzureEdition>()
{
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<AzureEdition>();
yield return AzureEdition.Basic;
yield return AzureEdition.Standard;
yield return AzureEdition.Premium;
yield return AzureEdition.DataWarehouse;
yield return AzureEdition.BusinessCritical;
yield return AzureEdition.GeneralPurpose;
//yield return AzureEdition.Free;
yield return AzureEdition.Hyperscale;
//yield return AzureEdition.Stretch;
//yield return AzureEdition.System;
}
}
}

View File

@@ -6,16 +6,16 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Resources;
using System.Data;
using System.IO;
using System.Resources;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Smo = Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using System.Collections.Generic;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Management;
using Smo = Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.Admin
{
@@ -56,6 +56,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
public string name;
public bool isReadOnly;
public bool isDefault;
public bool isAutogrowAllFiles;
public FileGroupType fileGroupType = FileGroupType.RowsFileGroup;
/// <summary>
@@ -66,17 +67,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
this.name = String.Empty;
this.isReadOnly = false;
this.isDefault = false;
this.isAutogrowAllFiles = false;
}
/// <summary>
/// Creates an instance of FilegroupData
/// </summary>
public FilegroupData(FileGroupType fileGroupType)
: this(name: String.Empty, isReadOnly: false, isDefault: false, fileGroupType: fileGroupType, isAutogrowAllFiles: false)
{
this.name = String.Empty;
this.isReadOnly = false;
this.isDefault = false;
this.fileGroupType = fileGroupType;
}
/// <summary>
@@ -87,10 +86,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <param name="isDefault">Default filegroup or not</param>
/// <param name="fileGroupType">FileGroupType</param>
public FilegroupData(string name, bool isReadOnly, bool isDefault, FileGroupType fileGroupType)
: this(name, isReadOnly, isDefault, fileGroupType, isAutogrowAllFiles: false)
{
}
/// <summary>
/// Initializes a new instance of the FilegroupData class.
/// </summary>
/// <param name="name">filegroup name</param>
/// <param name="isReadOnly">Readonly or not</param>
/// <param name="isDefault">Default filegroup or not</param>
/// <param name="fileGroupType">FileGroupType</param>
/// <param name="isAutogrowAllFiles">Autogrow all files enabled or not</param>
public FilegroupData(string name, bool isReadOnly, bool isDefault, FileGroupType fileGroupType, bool isAutogrowAllFiles)
{
this.name = name;
this.isReadOnly = isReadOnly;
this.isDefault = isDefault;
this.isAutogrowAllFiles = isAutogrowAllFiles;
this.fileGroupType = fileGroupType;
}
@@ -99,11 +112,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// </summary>
/// <param name="other"></param>
public FilegroupData(FilegroupData other)
: this(other.name, other.isReadOnly, other.isDefault, other.fileGroupType, other.isAutogrowAllFiles)
{
this.name = other.name;
this.isReadOnly = other.isReadOnly;
this.isDefault = other.isDefault;
this.fileGroupType = other.fileGroupType;
}
/// <summary>
@@ -135,6 +145,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
set
{
System.Diagnostics.Debug.Assert(!this.Exists, "can't rename existing filegroups");
if (!this.Exists)
{
string oldname = this.currentState.name;
@@ -178,6 +189,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
}
/// <summary>
/// Whether the filegroup has AUTOGROW_ALL_FILES enabled
/// </summary>
public bool IsAutogrowAllFiles
{
get { return this.currentState.isAutogrowAllFiles; }
set
{
if (this.currentState.isAutogrowAllFiles != value)
{
this.currentState.isAutogrowAllFiles = value;
this.parent.NotifyObservers();
}
}
}
/// <summary>
/// Whether the filegroup is of filestream type
/// </summary>
@@ -268,13 +296,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <param name="parent"></param>
/// <param name="filegroupType"></param>
public FilegroupPrototype(DatabasePrototype parent, FileGroupType filegroupType)
: this(parent: parent,
name: String.Empty,
isReadOnly: false,
isDefault: false,
filegroupType: filegroupType,
exists: false,
isAutogrowAllFiles: false)
{
this.originalState = new FilegroupData(filegroupType);
this.currentState = this.originalState.Clone();
this.parent = parent;
this.filegroupExists = false;
this.removed = false;
}
/// <summary>
@@ -288,8 +317,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <param name="exists">filegroup exists or not</param>
public FilegroupPrototype(DatabasePrototype parent, string name, bool isReadOnly, bool isDefault,
FileGroupType filegroupType, bool exists)
: this(parent, name, isReadOnly, isDefault, filegroupType, exists, isAutogrowAllFiles: false)
{
this.originalState = new FilegroupData(name, isReadOnly, isDefault, filegroupType);
}
/// <summary>
/// Initializes a new instance of the FilegroupPrototype class.
/// </summary>
/// <param name="parent">instance of DatabasePrototype</param>
/// <param name="name">file group name</param>
/// <param name="isReadOnly">whether it is readonly or not</param>
/// <param name="isDefault">is default or not</param>
/// <param name="filegroupType">filegrouptype</param>
/// <param name="exists">filegroup exists or not</param>
/// <param name="isAutogrowAllFiles">is autogrow all files enabled or not</param>
public FilegroupPrototype(DatabasePrototype parent, string name, bool isReadOnly, bool isDefault,
FileGroupType filegroupType, bool exists, bool isAutogrowAllFiles)
{
this.originalState = new FilegroupData(name, isReadOnly, isDefault, filegroupType, isAutogrowAllFiles);
this.currentState = this.originalState.Clone();
this.parent = parent;
@@ -321,7 +366,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
fg = db.FileGroups[this.Name];
}
else
{
{
fg = new FileGroup(db, this.Name, this.FileGroupType);
db.FileGroups.Add(fg);
}
@@ -332,6 +377,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
filegroupChanged = true;
}
if (fg.IsSupportedProperty("AutogrowAllFiles") &&
(!this.Exists || (fg.AutogrowAllFiles != this.IsAutogrowAllFiles)))
{
fg.AutogrowAllFiles = this.IsAutogrowAllFiles;
filegroupChanged = true;
}
if (this.Exists && filegroupChanged)
{
fg.Alter();
@@ -352,7 +404,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
!this.Exists ||
this.Removed ||
(this.originalState.isDefault != this.currentState.isDefault) ||
(this.originalState.isReadOnly != this.currentState.isReadOnly));
(this.originalState.isReadOnly != this.currentState.isReadOnly) ||
(this.originalState.isAutogrowAllFiles != this.currentState.isAutogrowAllFiles));
return result;
}
@@ -570,7 +623,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (FileGrowthType.Percent == file.GrowthType)
{
this.isGrowthInPercent = true;
this.growthInPercent = (int) file.Growth;
this.growthInPercent = (int)file.Growth;
this.growthInKilobytes = 10240.0;
// paranoia - make sure percent amount is greater than 1
@@ -642,7 +695,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (FileGrowthType.Percent == fileGrowthType)
{
this.isGrowthInPercent = true;
this.growthInPercent = (int) file.Growth;
this.growthInPercent = (int)file.Growth;
this.growthInKilobytes = 10240.0;
// paranoia - make sure percent amount is greater than 1
@@ -1020,9 +1073,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
set
{
System.Diagnostics.Debug.Assert(!this.Exists, "Can't change the filegroup of an existing file.");
if ((FileType.Data == this.currentState.fileType ||
FileType.FileStream == this.currentState.fileType) && !this.Exists && (value != null))
{
if (this.IsPrimaryFile && (value != null))
{
System.Diagnostics.Debug.Assert(value.Name == "PRIMARY", "Primary file must belong to primary filegroup");
}
if (this.currentState.filegroup != null)
{
this.currentState.filegroup.OnFileGroupDeletedHandler -=
@@ -1079,6 +1139,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
set
{
if (value == true)
{
System.Diagnostics.Debug.Assert(FileGroup.Name == "PRIMARY", "Primary file must belong to primary filegroup");
}
this.currentState.isPrimaryFile = value;
this.database.NotifyObservers();
}
@@ -1310,6 +1375,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <param name="db">The database from which the file is to be removed</param>
private void RemoveFile(Database db)
{
System.Diagnostics.Debug.Assert(this.Removed, "We're removing a file we arn't supposed to remove");
if (this.Exists)
{
if (FileType.Log == this.DatabaseFileType)
@@ -1404,7 +1471,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (this.Autogrowth.IsGrowthInPercent)
{
file.GrowthType = FileGrowthType.Percent;
file.Growth = (double) this.Autogrowth.GrowthInPercent;
file.Growth = (double)this.Autogrowth.GrowthInPercent;
}
else
{
@@ -1436,7 +1503,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (this.currentState.autogrowth.IsGrowthInPercent)
{
newFileGrowthType = FileGrowthType.Percent;
newGrowth = (double) this.currentState.autogrowth.GrowthInPercent;
newGrowth = (double)this.currentState.autogrowth.GrowthInPercent;
}
else
{
@@ -1455,7 +1522,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (this.originalState.autogrowth.IsGrowthInPercent)
{
originalFileGrowthType = FileGrowthType.Percent;
originalGrowth = (double) this.originalState.autogrowth.GrowthInPercent;
originalGrowth = (double)this.originalState.autogrowth.GrowthInPercent;
}
else
{
@@ -1628,7 +1695,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (this.Autogrowth.IsGrowthInPercent)
{
file.GrowthType = FileGrowthType.Percent;
file.Growth = (double) this.Autogrowth.GrowthInPercent;
file.Growth = (double)this.Autogrowth.GrowthInPercent;
}
else
{
@@ -1660,7 +1727,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (this.currentState.autogrowth.IsGrowthInPercent)
{
newFileGrowthType = FileGrowthType.Percent;
newGrowth = (double) this.currentState.autogrowth.GrowthInPercent;
newGrowth = (double)this.currentState.autogrowth.GrowthInPercent;
}
else
{
@@ -1679,7 +1746,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (this.originalState.autogrowth.IsGrowthInPercent)
{
originalFileGrowthType = FileGrowthType.Percent;
originalGrowth = (double) this.originalState.autogrowth.GrowthInPercent;
originalGrowth = (double)this.originalState.autogrowth.GrowthInPercent;
}
else
{
@@ -1764,12 +1831,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
else
{
System.Diagnostics.Debug.Assert(false,
"Client must set the ConnectionInfo property of the CDataContainer passed to the DatabasePrototype constructor");
// $CONSIDER throwing an exception here.
connectionInfo = context.ConnectionInfo;
}
// get default data file size
request.Urn = "Server/Database[@Name='model']/FileGroup[@Name='PRIMARY']/File";
request.Fields = new String[1] {"Size"};
request.Fields = new String[1] { "Size" };
try
{
@@ -1781,8 +1851,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
defaultDataFileSize = DatabaseFilePrototype.RoundUpToNearestMegabyte(size);
}
catch (Exception)
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
// user doesn't have access to model so we set the default size
// to be 5 MB
defaultDataFileSize = 5120.0;
@@ -1790,7 +1861,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
// get default log file size
request.Urn = "Server/Database[@Name='model']/LogFile";
request.Fields = new String[1] {"Size"};
request.Fields = new String[1] { "Size" };
try
{
@@ -1799,8 +1870,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
defaultLogFileSize = DatabaseFilePrototype.RoundUpToNearestMegabyte(size);
}
catch (Exception)
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
// user doesn't have access to model so we set the default size
// to be 1MB
defaultLogFileSize = 1024.0;
@@ -1808,7 +1881,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
// get default data and log folders
request.Urn = "Server/Setting";
request.Fields = new String[] {"DefaultFile", "DefaultLog"};
request.Fields = new String[] { "DefaultFile", "DefaultLog" };
try
{
@@ -1819,7 +1892,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (defaultDataFolder.Length == 0 || defaultLogFolder.Length == 0)
{
request.Urn = "Server/Information";
request.Fields = new string[] {"MasterDBPath", "MasterDBLogPath"};
request.Fields = new string[] { "MasterDBPath", "MasterDBLogPath" };
fileInfo = enumerator.Process(connectionInfo, request);
if (defaultDataFolder.Length == 0)
@@ -1858,8 +1931,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
rest);
}
}
catch (Exception)
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
}
}
@@ -1899,7 +1973,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (defaultDataAutogrowth.IsGrowthInPercent)
{
defaultDataAutogrowth.GrowthInPercent = (int) datafile.Growth;
defaultDataAutogrowth.GrowthInPercent = (int)datafile.Growth;
defaultDataAutogrowth.GrowthInMegabytes = 10;
}
else
@@ -1945,7 +2019,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
if (defaultLogAutogrowth.IsGrowthInPercent)
{
defaultLogAutogrowth.GrowthInPercent = (int) logfile.Growth;
defaultLogAutogrowth.GrowthInPercent = (int)logfile.Growth;
defaultLogAutogrowth.GrowthInMegabytes = 10;
}
else
@@ -2001,7 +2075,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnFilegroupDeleted(object sender, FilegroupDeletedEventArgs e)
{
{
System.Diagnostics.Debug.Assert(this.FileGroup == sender, "received filegroup deleted notification from wrong filegroup");
e.DeletedFilegroup.OnFileGroupDeletedHandler -= new FileGroupDeletedEventHandler(OnFilegroupDeleted);
// SQL Server deletes all the files in a filegroup when the filegroup is removed
@@ -2021,7 +2096,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <param name="fileName">The proposed file name to check</param>
private void CheckFileName(string fileName)
{
char[] badFileCharacters = new char[] {'\\', '/', ':', '*', '?', '\"', '<', '>', '|'};
char[] badFileCharacters = new char[] { '\\', '/', ':', '*', '?', '\"', '<', '>', '|' };
bool isAllWhitespace = (fileName.Trim(null).Length == 0);
@@ -2043,6 +2118,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
else
{
int i = fileName.IndexOfAny(badFileCharacters);
System.Diagnostics.Debug.Assert(-1 < i, "unexpected error type");
message = String.Format(System.Globalization.CultureInfo.CurrentCulture,
resourceManager.GetString("error_fileNameContainsIllegalCharacter"), fileName, fileName[i]);
@@ -2059,7 +2135,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <returns>The equivalent number of megabytes</returns>
internal static int KilobytesToMegabytes(double kilobytes)
{
return (int) Math.Ceiling(kilobytes/kilobytesPerMegabyte);
return (int)Math.Ceiling(kilobytes / kilobytesPerMegabyte);
}
/// <summary>
@@ -2069,7 +2145,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <returns>The equivalent number of kilobytes</returns>
internal static double MegabytesToKilobytes(int megabytes)
{
return (((double) megabytes)*kilobytesPerMegabyte);
return (((double)megabytes) * kilobytesPerMegabyte);
}
/// <summary>
@@ -2080,8 +2156,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <returns>The number of kb in the next larger mb</returns>
internal static double RoundUpToNearestMegabyte(double kilobytes)
{
double megabytes = Math.Ceiling(kilobytes/kilobytesPerMegabyte);
return (megabytes*kilobytesPerMegabyte);
double megabytes = Math.Ceiling(kilobytes / kilobytesPerMegabyte);
return (megabytes * kilobytesPerMegabyte);
}
/// <summary>
@@ -2112,6 +2188,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <exception cref="InvalidOperationException">If logical name is empty, or physical name is invalid.</exception>
private string MakeDiskFileName(string logicalName, string preferredPhysicalName, string suffix)
{
System.Diagnostics.Debug.Assert(logicalName != null, "unexpected param - logical name cannot be null");
System.Diagnostics.Debug.Assert(suffix != null, "unexpected param - suffix cannot be null. Pass String.Empty instead.");
ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(DatabasePrototype).GetAssembly());
string filePath = String.Empty; // returned to the caller.
@@ -2132,6 +2210,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
string message = String.Empty;
message = resourceManager.GetString("error_emptyFileName");
System.Diagnostics.Debug.Assert(message != null, "unexpected error string missing.");
throw new InvalidOperationException(message);
}
@@ -2266,7 +2345,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
{
ResourceManager resourceManager =
new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
typeof (DatabaseAlreadyExistsException).GetAssembly());
typeof(DatabaseAlreadyExistsException).Assembly);
format = resourceManager.GetString("error.databaseAlreadyExists");
}
@@ -2288,7 +2367,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
{
ResourceManager manager =
new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
typeof (DatabasePrototype).GetAssembly());
typeof(DatabasePrototype).Assembly);
List<string> standardValues = null;
TypeConverter.StandardValuesCollection result = null;
@@ -2331,7 +2410,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
{
ResourceManager manager =
new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
typeof (DatabasePrototype90).GetAssembly());
typeof(DatabasePrototype90).Assembly);
List<string> standardValues = new List<string>();
TypeConverter.StandardValuesCollection result = null;
@@ -2373,7 +2452,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
{
ResourceManager manager =
new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
typeof (DatabasePrototype80).GetAssembly());
typeof(DatabasePrototype80).Assembly);
List<string> standardValues = new List<string>();
TypeConverter.StandardValuesCollection result = null;
@@ -2417,7 +2496,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
{
ResourceManager manager =
new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
typeof (DatabasePrototype80).GetAssembly());
typeof(DatabasePrototype80).Assembly);
List<string> standardValues = new List<string>();
TypeConverter.StandardValuesCollection result = null;
@@ -2501,7 +2580,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
{
ResourceManager manager =
new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
typeof (DatabasePrototype).GetAssembly());
typeof(DatabasePrototype).Assembly);
List<string> standardValues = new List<string>();
TypeConverter.StandardValuesCollection result = null;
@@ -2539,176 +2618,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
return true;
}
}
///// <summary>
///// Helper class to provide standard values for populating drop down boxes on
///// properties displayed in the Properties Grid
///// </summary>
//internal class DynamicValuesConverter : StringConverter
//{
// /// <summary>
// /// This method returns a list of dynamic values
// /// for various Properties in this class.
// /// </summary>
// /// <param name="context"></param>
// /// <returns>List of Database Status Types </returns>
// public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
// {
// var standardValues = new List<object>();
// 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<int, string[]> 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<int, DbSize[]> 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;
// }
//}
///// <summary>
///// Helper class to provide standard values for populating drop down boxes on
///// database scoped configuration properties displayed in the Properties Grid
///// </summary>
//internal class DatabaseScopedConfigurationOnOffTypes : StringConverter
//{
// /// <summary>
// /// This method returns a list of database scoped configuration on off values
// /// which will be populated as a drop down list.
// /// </summary>
// /// <param name="context"></param>
// /// <returns>Database scoped configurations which will populate the drop down list.</returns>
// public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
// {
// ResourceManager manager =
// new ResourceManager("Microsoft.SqlServer.Management.SqlManagerUI.CreateDatabaseStrings",
// typeof (DatabasePrototype).GetAssembly());
// List<string> standardValues = new List<string>();
// 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;
// }
//}
}

View File

@@ -273,6 +273,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
break;
}
System.Diagnostics.Debug.Assert(result != null && result.Length != 0, "no string found for database scoped configuration value");
return result;
}
@@ -291,11 +293,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
return DatabaseScopedConfigurationOnOff.Off;
}
else if (displayText == manager.GetString("prototype_db_prop_databasescopedconfig_value_on") || !forSecondary)
{
{
return DatabaseScopedConfigurationOnOff.On;
}
else
{
{
return DatabaseScopedConfigurationOnOff.Primary;
}
}

View File

@@ -9,6 +9,8 @@ using System.ComponentModel;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlServer.Management.Common;
namespace Microsoft.SqlTools.ServiceLayer.Admin
{
@@ -50,6 +52,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
}
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
@@ -152,6 +168,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
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;
@@ -185,5 +206,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
}
}
int Lcid
{
get { return this.DefaultLanguage.lcid; }
}
ServerConnection Connection
{
get { return this.context.ServerConnection; }
}
}
}

View File

@@ -0,0 +1,77 @@
//
// 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.Management;
using System;
using System.ComponentModel;
namespace Microsoft.SqlTools.ServiceLayer.Admin
{
/// <summary>
/// Database properties for SqlServer 2017
/// </summary>
internal class DatabasePrototype140 : DatabasePrototype110
{
/// <summary>
/// Database properties for SqlServer 2017 class constructor
/// </summary>
public DatabasePrototype140(CDataContainer context)
: base(context)
{
}
/// <summary>
/// Whether or not the UI should show File Groups
/// </summary>
public override bool HideFileSettings
{
get
{
return (this.context != null && this.context.Server != null && (this.context.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance || this.context.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlOnDemand));
}
}
/// <summary>
/// The recovery model for the database
/// </summary>
[Browsable(false)]
public override RecoveryModel RecoveryModel
{
get
{
if (this.context != null &&
this.context.Server != null &&
this.context.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance)
{
return RecoveryModel.Full;
}
else
{
return this.currentState.recoveryModel;
}
}
set
{
if (this.context != null &&
this.context.Server != null &&
this.context.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance &&
value != RecoveryModel.Full)
{
System.Diagnostics.Debug.Assert(false, "Managed Instance supports only FULL recovery model!");
throw new ArgumentException("Managed Instance supports only FULL recovery model!");
}
else
{
base.RecoveryModel = value;
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
//
// 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.Smo;
using Microsoft.SqlTools.ServiceLayer.Management;
using System.ComponentModel;
namespace Microsoft.SqlTools.ServiceLayer.Admin
{
/// <summary>
/// Database properties for SqlServer 2022
/// </summary>
internal class DatabasePrototype160 : DatabasePrototype140
{
/// <summary>
/// Database properties for SqlServer 2022 class constructor
/// </summary>
public DatabasePrototype160(CDataContainer context)
: base(context)
{
}
[Category("Category_Ledger"),
DisplayNameAttribute("Property_IsLedgerDatabase")]
public bool IsLedger
{
get {
return this.currentState.isLedger;
}
set
{
this.currentState.isLedger = value;
this.NotifyObservers();
}
}
protected override void SaveProperties(Database db)
{
base.SaveProperties(db);
if (db.IsSupportedProperty("IsLedger"))
{
// Ledger can only be set on a new database, it is read-only after creation
if (!this.Exists)
{
db.IsLedger = this.IsLedger;
}
}
}
}
}

View File

@@ -84,7 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
this.currentState.pageVerify = PageVerify.None;
}
else
{
{
this.currentState.pageVerify = PageVerify.TornPageDetection;
}

View File

@@ -11,28 +11,29 @@ using Microsoft.Data.SqlClient;
using System.Globalization;
using System.Linq;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Management;
using AzureEdition = Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper.AzureEdition;
using System;
using System.Data;
namespace Microsoft.SqlTools.ServiceLayer.Admin
{
/// <summary>
/// Database properties for SQL Azure DB.
/// Business/Web editions are up to compat level 100 now
/// </summary>
[TypeConverter(typeof(DynamicValueTypeConverter))]
internal class DatabasePrototypeAzure : DatabasePrototype100
internal class DatabasePrototypeAzure : DatabasePrototype160
{
#region Constants
public const string Category_Azure = "Category_Azure";
public const string Category_Azure_BRS = "Category_Azure_BRS";
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";
public const string Property_AzureBackupStorageRedundancy = "Property_AzureBackupStorageRedundancy";
#endregion Constants
public DatabasePrototypeAzure(CDataContainer context, DatabaseEngineEdition editionToCreate = DatabaseEngineEdition.SqlDatabase)
@@ -42,9 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
#region Properties
[Category(Category_Azure),
DisplayNameAttribute(Property_AzureMaxSize)]
public string MaxSize
{
get
@@ -53,13 +52,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
set
{
this.currentState.maxSize = DbSize.ParseDbSize(value);
this.currentState.maxSize = string.IsNullOrEmpty(value) ? null : DbSize.ParseDbSize(value);
this.NotifyObservers();
}
}
[Category(Category_Azure),
DisplayNameAttribute(Property_AzureCurrentServiceLevelObjective)]
public string CurrentServiceLevelObjective
{
get
@@ -68,23 +65,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
set
{
if (value != null && value.Contains('\''))
{
throw new ArgumentException("Error_InvalidServiceLevelObjective");
}
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
{
@@ -94,35 +83,79 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}
}
[Category(Category_Azure),
DisplayNameAttribute(Property_AzureEdition)]
//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 this.currentState.azureEditionDisplayValue;
return AzureSqlDbHelper.GetAzureEditionDisplayName(this.currentState.azureEdition);
}
set
{
// TODO set from here should probably allow for the fact that System is a valid edition for
// actual system DBs. Not handling for now
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;
}
// set
// {
// AzureEdition edition;
// if (AzureSqlDbHelper.TryGetAzureEditionFromDisplayName(value, out edition))
// {
// //Try to get the ServiceLevelObjective from the api,if not the default hardcoded service level objectives will be retrieved.
// string serverLevelObjective = AzureServiceLevelObjectiveProvider.TryGetAzureServiceLevelObjective(value, AzureServiceLocation);
this.currentState.azureEdition = edition;
this.currentState.azureEditionDisplayValue = value;
this.CurrentServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(edition);
this.MaxSize = AzureSqlDbHelper.GetDatabaseDefaultSize(edition).ToString();
this.NotifyObservers();
}
}
// if (!string.IsNullOrEmpty(serverLevelObjective))
// {
// this.currentState.azureEdition = edition;
// this.currentState.currentServiceLevelObjective = serverLevelObjective;
// // Instead of creating db instance with default Edition, update EditionToCreate while selecting Edition from the UI.
// this.EditionToCreate = MapAzureEditionToDbEngineEdition(edition);
// string storageAccountType = AzureServiceLevelObjectiveProvider.TryGetAzureStorageType(value, AzureServiceLocation);
// if (!string.IsNullOrEmpty(storageAccountType))
// {
// this.currentState.backupStorageRedundancy = storageAccountType;
// }
// // Try to get the azure maxsize from the api,if not the default hardcoded maxsize will be retrieved.
// DbSize dbSize = AzureServiceLevelObjectiveProvider.TryGetAzureMaxSize(value, serverLevelObjective, AzureServiceLocation);
// if (!string.IsNullOrEmpty(dbSize.ToString()))
// {
// this.currentState.maxSize = new DbSize(dbSize.Size, dbSize.SizeUnit);
// }
// }
// else
// {
// 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.EditionToCreate = MapAzureEditionToDbEngineEdition(edition);
// this.CurrentServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(edition);
// this.BackupStorageRedundancy = AzureSqlDbHelper.GetDefaultBackupStorageRedundancy(edition);
// var defaultSize = AzureSqlDbHelper.GetDatabaseDefaultSize(edition);
// this.MaxSize = defaultSize == null ? String.Empty : defaultSize.ToString();
// }
// this.NotifyObservers();
// }
// else
// {
// //Can't really do much if we fail to parse the display name so just leave it as is and log a message
// System.Diagnostics.Debug.Assert(false,
// string.Format(CultureInfo.InvariantCulture,
// "Failed to parse edition display name '{0}' back into AzureEdition", value));
// }
// }
}
/// <summary>
/// Mapping funtion to get the Database engine edition based on the selected AzureEdition value
/// </summary>
/// <param name="edition">Selected dropdown Azure Edition value</param>
/// <returns>Corresponding DatabaseEngineEdition value</returns>
private static DatabaseEngineEdition MapAzureEditionToDbEngineEdition(AzureEdition edition)
{
// As of now we only know for sure that AzureEdition.DataWarehouse maps to
// DatabaseEngineEdition.SqlDataWarehouse, for all others we keep the default value
// as before which was 'SqlDatabase'
return edition == AzureEdition.DataWarehouse ? DatabaseEngineEdition.SqlDataWarehouse : DatabaseEngineEdition.SqlDatabase;
}
public override IList<FilegroupPrototype> Filegroups
@@ -148,6 +181,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
get { return this.ServerVersion.Major > 11 && this.AzureEdition != AzureEdition.DataWarehouse; }
}
// [Browsable(false)]
// public SubscriptionLocationKey AzureServiceLocation { get; set; }
public string BackupStorageRedundancy
{
get
{
return this.currentState.backupStorageRedundancy;
}
set
{
this.currentState.backupStorageRedundancy = value;
this.NotifyObservers();
}
}
#endregion Properties
#region DatabasePrototype overrides
@@ -159,69 +208,71 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
/// <returns>The SMO database object that was created or modified</returns>
public override Database ApplyChanges()
{
// For v12 Non-DW DBs lets use SMO
if (this.ServerVersion.Major >= 12 && this.AzureEdition != AzureEdition.DataWarehouse)
{
return base.ApplyChanges();
}
//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))
{
using (var cmd = new SqlCommand())
Database database = base.ApplyChanges();
if (this.AzureEdition != AzureEdition.DataWarehouse)
{
// We don't need to alter BSR value if the user is just scripting or if the DB is not creating.
if (database != null && this.context.Server.ConnectionContext.SqlExecutionModes != SqlExecutionModes.CaptureSql)
{
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)
string alterAzureDbBackupStorageRedundancy = DatabasePrototypeAzure.CreateModifySqlDBBackupStorageRedundancyStatement(this.Name, this.currentState.backupStorageRedundancy);
using (var conn = this.context.ServerConnection.GetDatabaseConnection(this.Name).SqlConnectionObject)
{
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();
//While scripting the database, there is already an open connection. So, we are checking the state of the connection here.
if (conn != null && conn.State == ConnectionState.Closed)
{
conn.Open();
using (var cmd = new SqlCommand { Connection = conn })
{
cmd.CommandText = alterAzureDbBackupStorageRedundancy;
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();
return database;
}
// 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;
string 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 = this.context.ServerConnection.GetDatabaseConnection("master").SqlConnectionObject)
{
var cmd = new SqlCommand { 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 != null && currentState.azureEdition != originalState.azureEdition) ||
(!string.IsNullOrEmpty(currentState.currentServiceLevelObjective) && currentState.currentServiceLevelObjective != originalState.currentServiceLevelObjective) ||
(currentState.maxSize != null && 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();
return db;
}
#endregion DatabasePrototype overrides
@@ -229,30 +280,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
protected override void SaveProperties(Database db)
{
base.SaveProperties(db);
if (this.ServerVersion.Major >= 12 && this.AzureEdition != AzureEdition.DataWarehouse)
// treat null as defaults/unchanged
// SMO will only script changed values so if the user changes edition and size and SLO are empty the alter
// will change the db to the default size and slo for the new edition
// if the new combination of edition/size/slo is invalid the alter will fail
if (this.currentState.maxSize != null && (!this.Exists || (this.originalState.maxSize != this.currentState.maxSize)))
{
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;
}
db.MaxSizeInBytes = this.currentState.maxSize.SizeInBytes;
}
if (this.currentState.azureEdition != null && (!this.Exists || (this.originalState.azureEdition != this.currentState.azureEdition)))
{
db.AzureEdition = this.currentState.azureEdition.ToString();
}
if (!string.IsNullOrEmpty(this.currentState.currentServiceLevelObjective) && (!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 AlterDbStatementFormat = @"ALTER DATABASE [{0}] {1}";
private const string ModifySqlDwDbStatementFormat = @"MODIFY (MAXSIZE={0} {1})";
private const string AzureServiceLevelObjectiveOptionFormat = @"SERVICE_OBJECTIVE = '{0}'";
private const string SetReadOnlyOption = @"SET READ_ONLY";
@@ -260,6 +309,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
private const string SetRecursiveTriggersOptionFormat = @"SET RECURSIVE_TRIGGERS {0}";
private const string On = @"ON";
private const string Off = @"OFF";
private const string ModifySqlDbBackupStorageRedundancy = @"MODIFY BACKUP_STORAGE_REDUNDANCY = '{0}'";
/// <summary>
/// Creates an ALTER DATABASE statement to modify the Read-Only status of the target DB
@@ -288,29 +338,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
recursiveTriggersEnabled ? DatabasePrototypeAzure.On : DatabasePrototypeAzure.Off));
}
/// <summary>
/// Creates an ALTER DATABASE statement to modify the Azure Database properties (Edition, MaxSize and Service Level Objective)
/// for the target database
/// </summary>
/// <param name="dbName"></param>
/// <param name="edition"></param>
/// <param name="maxSize"></param>
/// <param name="serviceLevelObjective"></param>
/// <returns></returns>
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));
}
/// <summary>
/// Creates an ALTER DATABASE statement to modify the Azure DataWarehouse properties (MaxSize and Service Level Objective)
/// for the target database
@@ -332,6 +359,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
sloOption));
}
/// <summary>
/// Creates the ATLER DATABASE statement from the given backup storage redundancy option.
/// </summary>
/// <param name="dbName"></param>
/// <param name="option"></param>
/// <returns></returns>
protected static string CreateModifySqlDBBackupStorageRedundancyStatement(string dbName, string option)
{
//Note: We allow user to select any one of the value from the UI for backupStorageRedundancy. So, we are inlining the value.
return CreateAzureAlterDbStatement(dbName,
string.Format(CultureInfo.InvariantCulture,
ModifySqlDbBackupStorageRedundancy,
option));
}
/// <summary>
/// Creates the ALTER DATABASE statement from the given op
/// </summary>

View File

@@ -3,11 +3,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security;
@@ -30,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
SQL,
OLAP, //This type is used only for non-express sku
SQLCE,
SQLCE,
UNKNOWN
}
@@ -38,32 +38,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
#region Fields
private ServerConnection serverConnection;
private Server m_server = null;
protected XmlDocument m_doc = null;
private XmlDocument originalDocument = null;
private SqlOlapConnectionInfoBase connectionInfo = null;
private SqlConnectionInfoWithConnection sqlCiWithConnection;
private ServerConnection? serverConnection;
private Server? m_server;
protected XmlDocument? m_doc;
private XmlDocument? originalDocument;
private SqlOlapConnectionInfoBase? connectionInfo;
private SqlConnectionInfoWithConnection? sqlCiWithConnection;
private bool ownConnection = true;
private IManagedConnection managedConnection;
protected string serverName;
private IManagedConnection? managedConnection;
protected string? serverName;
//This member is used for non-express sku only
protected string olapServerName;
protected string? olapServerName;
protected string sqlceFilename;
protected string? sqlceFilename;
private ServerType serverType = ServerType.UNKNOWN;
private Hashtable m_hashTable = null;
private Hashtable? m_hashTable;
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 SqlSmoObject? sqlDialogSubject;
private int sqlServerVersion = 0;
private int sqlServerEffectiveVersion = 0;
#endregion
@@ -73,7 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// gets/sets XmlDocument with parameters
/// </summary>
public XmlDocument Document
public XmlDocument? Document
{
get
{
@@ -85,8 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (value != null)
{
//this.originalDocument = (XmlDocument) value.Clone();
this.originalDocument = value;
this.originalDocument = (XmlDocument)value.Clone();
}
else
{
@@ -111,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// gets/sets SMO server object
/// </summary>
public Server Server
public Server? Server
{
get
{
@@ -127,26 +125,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// connection info that should be used by the dialogs
/// </summary>
public SqlOlapConnectionInfoBase ConnectionInfo
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;
// 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<string> dbNamesComparer = ServerConnection.ConnectionFactory.GetInstance(conn.ServerConnection).ServerComparer as IComparer<string>;
// if (dbNamesComparer.Compare(this.RelevantDatabaseName, conn.DatabaseName) != 0)
// {
// ServerConnection serverConnection = conn.ServerConnection.GetDatabaseConnection(this.RelevantDatabaseName, true, conn.AccessToken);
// ((SqlConnectionInfoWithConnection)this.connectionInfo).ServerConnection = serverConnection;
// }
// }
//}
// Don't update the database name if this is a Gen3 connection since Gen3 supports USE from the server connection.
if (conn != null &&
conn.ServerConnection.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase &&
!(conn.ServerConnection.DatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse &&
conn.ServerConnection.ProductVersion.Major >= 12))
{
if (this.RelevantDatabaseName != null)
{
IComparer<string> dbNamesComparer = new ServerComparer(conn.ServerConnection, "master");
if (dbNamesComparer.Compare(this.RelevantDatabaseName, conn.DatabaseName) != 0 && this.connectionInfo != null)
{
ServerConnection databaseConnection = conn.ServerConnection.GetDatabaseConnection(this.RelevantDatabaseName, true, conn.AccessToken);
((SqlConnectionInfoWithConnection)this.connectionInfo).ServerConnection = databaseConnection;
}
}
}
return this.connectionInfo;
}
}
@@ -163,11 +164,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
if (this.serverType != ServerType.SQL)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only for SQL connection");
throw new InvalidOperationException();
}
if (this.connectionInfo == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only after ConnectionInfo property has been set");
throw new InvalidOperationException();
}
@@ -177,12 +182,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
else
{
SqlConnectionInfo sci = this.connectionInfo as SqlConnectionInfo;
SqlConnectionInfo? sci = this.connectionInfo as SqlConnectionInfo;
System.Diagnostics.Debug.Assert(sci != null, "CDataContainer.ServerConnection: connection info MUST be SqlConnectionInfo");
this.serverConnection = new ServerConnection(sci);
}
}
System.Diagnostics.Debug.Assert(this.serverConnection != null);
return this.serverConnection;
}
}
@@ -191,7 +197,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// returns SMO server connection object constructed off the connectionInfo.
/// This method cannot work until ConnectionInfo property has been set
/// </summary>
public SqlConnectionInfoWithConnection SqlInfoWithConnection
public SqlConnectionInfoWithConnection? SqlInfoWithConnection
{
get
{
@@ -199,11 +205,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
if (this.serverType != ServerType.SQL)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only for SQL connection");
throw new InvalidOperationException();
}
if (this.connectionInfo == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only after ConnectionInfo property has been set");
throw new InvalidOperationException();
}
@@ -214,16 +224,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
else
{
SqlConnectionInfo sci = this.connectionInfo as SqlConnectionInfo;
SqlConnectionInfo? sci = this.connectionInfo as SqlConnectionInfo;
System.Diagnostics.Debug.Assert(sci != null, "CDataContainer.ServerConnection: connection info MUST be SqlConnectionInfo");
this.serverConnection = new ServerConnection(sci);
}
}
System.Diagnostics.Debug.Assert(this.serverConnection != null);
return this.sqlCiWithConnection;
}
}
public string ServerName
public string? ServerName
{
get
{
@@ -247,7 +259,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
}
public string SqlCeFileName
public string? SqlCeFileName
{
get
{
@@ -260,7 +272,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
//This member is used for non-express sku only
public string OlapServerName
public string? OlapServerName
{
get
{
@@ -418,11 +430,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// The SQL SMO object that is the subject of the dialog.
/// </summary>
public SqlSmoObject SqlDialogSubject
public SqlSmoObject? SqlDialogSubject
{
get
{
SqlSmoObject result = null;
SqlSmoObject? result;
if (this.sqlDialogSubject != null)
{
@@ -430,7 +442,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
else
{
result = this.Server.GetSmoObject(this.ObjectUrn);
result = this.Server?.GetSmoObject(this.ObjectUrn);
}
return result;
@@ -451,6 +463,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
bool result = false;
System.Diagnostics.Debug.Assert(this.Server != null, "SMO Server object is null!");
System.Diagnostics.Debug.Assert(this.Server.ConnectionContext != null, "SMO Server Connection object is null!");
if (this.Server != null && this.Server.ConnectionContext != null)
{
result = this.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin);
@@ -471,6 +486,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
string result = String.Empty;
string urnText = this.GetDocumentPropertyString("urn");
System.Diagnostics.Debug.Assert(urnText.Length != 0, "couldn't get relevant URN");
if (urnText.Length != 0)
{
Urn urn = new Urn(urnText);
@@ -501,6 +518,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
this.sqlServerVersion = 9;
System.Diagnostics.Debug.Assert(this.ConnectionInfo != null, "ConnectionInfo is null!");
System.Diagnostics.Debug.Assert(ServerType.SQL == this.ContainerServerType, "unexpected server type");
if ((this.ConnectionInfo != null) && (ServerType.SQL == this.ContainerServerType))
{
Enumerator enumerator = new Enumerator();
@@ -520,99 +540,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
/// <summary>
/// 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.
/// </summary>
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
@@ -628,9 +555,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="ciObj">connection info containing live connection</param>
public CDataContainer(object ciObj, bool ownConnection)
{
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
if (ci == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.CDataContainer(SqlConnectionInfoWithConnection): specified connection info is null");
throw new ArgumentNullException("ci");
}
ApplyConnectionInfo(ci, ownConnection);
@@ -645,9 +574,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="ci">connection info containing live connection</param>
public CDataContainer(ServerType serverType, object ciObj, bool ownConnection)
{
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
if (ci == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.CDataContainer(SqlConnectionInfoWithConnection): specified connection info is null");
throw new ArgumentNullException("ci");
}
@@ -656,12 +587,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (serverType == ServerType.SQL)
{
//NOTE: ServerConnection property will constuct the object if needed
m_server = new Server(ServerConnection);
}
//NOTE: ServerConnection property will construct the object if needed
m_server = new Server(ServerConnection);
}
else
{
throw new ArgumentException(SR.UnknownServerType(serverType.ToString()));
throw new ArgumentException(SR.UnknownServerType(serverType.ToString()));
}
}
@@ -674,7 +605,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="userName">User name for not trusted connections</param>
/// <param name="password">Password for not trusted connections</param>
/// <param name="xmlParameters">XML string with parameters</param>
public CDataContainer(ServerType serverType, string serverName, bool trusted, string userName, SecureString password, string databaseName, string xmlParameters, string azureAccountToken = null)
public CDataContainer(ServerType serverType, string serverName, bool trusted, string userName, SecureString password, string databaseName, string xmlParameters, string? azureAccountToken = null)
{
this.serverType = serverType;
this.serverName = serverName;
@@ -710,6 +641,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="xmlParameters">XML string with parameters</param>
public CDataContainer(CDataContainer dataContainer, string xmlParameters)
{
//BUGBUG - should we be reusing same SqlConnectionInfoWithConnection if it is available?
System.Diagnostics.Debug.Assert(dataContainer.Server != null, "DataContainer.Server can not be null.");
Server = dataContainer.Server;
this.serverName = dataContainer.serverName;
this.serverType = dataContainer.serverType;
@@ -718,11 +652,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
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 (this.connectionInfo is SqlConnectionInfo)
{
System.Diagnostics.Debug.Assert(this.sqlCiWithConnection != null, "CDataContainer.ConnectionInfo setter: for SQL connection info you MUST use SqlConnectionInfoWithConnection derived class!");
}
if (xmlParameters != null)
{
XmlDocument doc = GenerateXmlDocumentFromString(xmlParameters);
@@ -764,20 +703,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (site != null)
{
// see if service provider supports INodeInformation interface from the object explorer
// 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;
Trace.TraceInformation("CDataContainer.Init has non-null IServiceProvider");
//see if service provider supports IManagedConnection interface from the object explorer
//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)
{
Trace.TraceInformation("CDataContainer.Init has non-null IManagedConnection");
this.SetManagedConnection(managedConnection);
}
}
this.Document = doc;
LoadData();
LoadData();
// finish the initialization
this.Init(doc);
@@ -808,13 +751,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
// 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;
}
}
@@ -837,21 +773,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (!bStatus || this.serverName.Length == 0)
{
if (this.sqlCiWithConnection != null)
{
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;
}
}
this.serverType = ServerType.SQL;
}
else
{
this.serverType = ServerType.UNKNOWN;
}
}
else
{
@@ -877,10 +807,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
// Ensure there is no password in the XML document
string temp = string.Empty;
string? temp = string.Empty;
if (param.GetParam("password", ref temp))
{
temp = null;
temp = null;
System.Diagnostics.Debug.Assert(false, "Plaintext password found in XML document! This must be fixed!");
throw new SecurityException();
}
@@ -899,7 +831,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </summary>
/// <param name="managedConnection"></param>
internal void SetManagedConnection(IManagedConnection managedConnection)
{
{
System.Diagnostics.Debug.Assert(this.managedConnection == null, "CDataContainer.SetManagedConnection: overwriting the previous value");
this.managedConnection = managedConnection;
ApplyConnectionInfo(managedConnection.Connection, true);//it will do some extra initialization
@@ -912,7 +845,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <returns>The property value</returns>
public object GetDocumentPropertyValue(string propertyName)
{
object result = null;
object? result = null;
STParameters param = new STParameters(this.Document);
param.GetBaseParam(propertyName, ref result);
@@ -974,6 +907,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </summary>
private void InitializeObjectNameAndSchema()
{
System.Diagnostics.Debug.Assert(ServerType.SQL == this.serverType, "This method only valid for SQL Servers");
string documentUrn = this.GetDocumentPropertyString("urn");
if (documentUrn.Length != 0)
{
@@ -1003,8 +938,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
string userName,
SecureString password,
string databaseName,
string azureAccountToken)
{
string? azureAccountToken)
{
System.Diagnostics.Debug.Assert(this.serverType == ServerType.SQL, "GetTempSqlConnectionInfoWithConnection should only be called for SQL Server type");
SqlConnectionInfoWithConnection tempCI = new SqlConnectionInfoWithConnection(serverName);
tempCI.SingleConnection = false;
tempCI.Pooled = false;
@@ -1031,7 +968,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSqlConnectionClosed(object sender, EventArgs e)
private void OnSqlConnectionClosed(object? sender, EventArgs e)
{
}
@@ -1042,7 +979,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="ci"></param>
private void ApplyConnectionInfo(SqlOlapConnectionInfoBase ci, bool ownConnection)
{
System.Diagnostics.Debug.Assert(this.connectionInfo == null, "CDataContainer.ApplyConnectionInfo: overwriting non-null connection info!");
System.Diagnostics.Debug.Assert(ci != null, "CDataContainer.ApplyConnectionInfo: ci is null!");
this.connectionInfo = ci;
this.ownConnection = ownConnection;
@@ -1050,12 +989,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
this.sqlCiWithConnection = ci as SqlConnectionInfoWithConnection;
if (this.sqlCiWithConnection != null)
{
{
// we want to be notified if it is closed
this.sqlCiWithConnection.ConnectionClosed += new EventHandler(OnSqlConnectionClosed);
}
}
private static bool MustRethrow(Exception exception)
{
bool result = false;
@@ -1132,7 +1071,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// MUST be called, as we'll be closing SQL connection inside this call
/// </summary>
private void Dispose(bool disposing)
{
{
try
{
//take care of live SQL connection
@@ -1175,42 +1114,56 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
this.managedConnection.Close();
}
this.managedConnection = null;
}
}
}
catch (Exception)
{
{
}
}
#endregion
/// <summary>
/// Create a data container object
/// </summary>
/// <param name="connInfo">connection info</param>
/// <param name="databaseExists">flag indicating whether to create taskhelper for existing database or not</param>
internal static CDataContainer CreateDataContainer(
ConnectionInfo connInfo,
ConnectionInfo connInfo,
bool databaseExists = false,
XmlDocument containerDoc = null)
XmlDocument? containerDoc = null)
{
containerDoc ??= CreateDataContainerDocument(connInfo, databaseExists);
var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer");
var connectionInfoWithConnection = new SqlConnectionInfoWithConnection();
connectionInfoWithConnection.ServerConnection = serverConnection;
return CreateDataContainer(connectionInfoWithConnection, containerDoc);
}
/// <summary>
/// Create a data container object
/// </summary>
/// <param name="connInfo">connection info</param>
/// <param name="databaseExists">flag indicating whether to create taskhelper for existing database or not</param>
internal static CDataContainer CreateDataContainer(
SqlConnectionInfoWithConnection connectionInfoWithConnection,
XmlDocument containerDoc)
{
CDataContainer dataContainer = new CDataContainer(ServerType.SQL, connectionInfoWithConnection, true);
dataContainer.Init(containerDoc);
return dataContainer;
}
internal static System.Security.SecureString BuildSecureStringFromPassword(string password) {
internal static System.Security.SecureString BuildSecureStringFromPassword(string password)
{
var passwordSecureString = new System.Security.SecureString();
if (password != null) {
foreach (char c in password) {
if (password != null)
{
foreach (char c in password)
{
passwordSecureString.AppendChar(c);
}
}

View File

@@ -0,0 +1,900 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Management
{
public class ActionContext
{
#region private members
/// <summary>
/// Name of the object
/// </summary>
string name;
/// <summary>
/// connection to the server
/// </summary>
private ServerConnection connection;
/// <summary>
/// Connection context
/// </summary>
private string contextUrn;
/// <summary>
/// Parent node in the tree
/// </summary>
//private INodeInformation parent;
/// <summary>
/// Weak reference to the tree node this is paired with
/// </summary>
WeakReference NavigableItemReference;
/// <summary>
/// Property handlers
/// </summary>
//private IList<IPropertyHandler> propertyHandlers;
/// <summary>
/// Property bag
/// </summary>
NameObjectCollection properties;
/// <summary>
/// Object to lock on when we are modifying public state
/// </summary>
private object itemStateLock = new object();
/// <summary>
/// Cached UrnPath
/// </summary>
private string urnPath;
#endregion
#region constructors
public ActionContext(ServerConnection connection, string name, string contextUrn)
{
if (connection == null)
{
throw new ArgumentNullException("connection");
}
if (contextUrn == null)
{
throw new ArgumentNullException("context");
}
if (name == null)
{
throw new ArgumentNullException("name");
}
this.connection = connection;
this.contextUrn = contextUrn;
this.name = name;
properties = new NameObjectCollection();
//propertyHandlers = null;
NavigableItemReference = null;
}
#endregion
#region INodeInformation implementation
public ServerConnection Connection
{
get
{
return this.connection;
}
set
{
lock (this.itemStateLock)
{
this.connection = value;
}
}
}
public string ContextUrn
{
get
{
return this.contextUrn;
}
set
{
lock (this.itemStateLock)
{
this.contextUrn = value;
}
}
}
public string NavigationContext
{
get
{
return GetNavigationContext(this);
}
}
public string UrnPath
{
get
{
this.urnPath ??= ActionContext.BuildUrnPath(this.NavigationContext);
return this.urnPath;
}
}
public string InvariantName
{
get
{
string name = this["UniqueName"] as string;
if (!string.IsNullOrEmpty(name))
return name;
StringBuilder uniqueName = new StringBuilder();
foreach (string urnValue in GetUrnPropertyValues())
{
if (uniqueName.Length > 0)
uniqueName.Append(".");
uniqueName.Append(urnValue);
}
return (uniqueName.Length > 0) ? uniqueName.ToString() : new Urn(ContextUrn).Type;
}
}
/// <summary>
/// property bag for this node
/// </summary>
public object this[string name] => properties[name];
public object CreateObjectInstance()
{
return CreateObjectInstance(this.ContextUrn, this.Connection);
}
#endregion
#region ISfcPropertyProvider implementation
public NameObjectCollection GetPropertySet()
{
return this.properties;
}
#endregion
#region NodeName helper
public string Name
{
get
{
return this.name;
}
set
{
lock (this.itemStateLock)
{
this.name = value;
}
}
}
#endregion
#region property bag support
public NameObjectCollection Properties
{
get
{
return this.properties;
}
set
{
lock (this.itemStateLock)
{
this.properties = value;
}
}
}
#endregion
#region helpers
public static string GetNavigationContext(ActionContext source)
{
string context = source.ContextUrn;
// see if this is a folder
string name = source["UniqueName"] as string;
if (name == null || name.Length == 0)
{
name = source.Name;
}
string queryHint = source["QueryHint"] as string;
if (queryHint == null || queryHint.Length == 0)
{
context = string.Format(
System.Globalization.CultureInfo.InvariantCulture
, "{0}/Folder[@Name='{1}']"
, source.ContextUrn
, Urn.EscapeString(name));
}
else
{
context = string.Format(
System.Globalization.CultureInfo.InvariantCulture
, "{0}/Folder[@Name='{1}' and @Type='{2}']"
, source.ContextUrn
, Urn.EscapeString(name)
, Urn.EscapeString(queryHint));
}
return context;
}
/// <summary>
/// Get the values of the keys in the current objects Urn
/// e.g. For Table[@Name='Foo' and @Schema='Bar'] return Foo and Bar
/// </summary>
/// <returns></returns>
private IEnumerable<string> GetUrnPropertyValues()
{
Urn urn = new Urn(ContextUrn);
Enumerator enumerator = new Enumerator();
RequestObjectInfo request = new RequestObjectInfo(urn, RequestObjectInfo.Flags.UrnProperties);
ObjectInfo info = enumerator.Process(connection, request);
if (info == null || info.UrnProperties == null)
yield break;
// Special order for Schema and Name
if (properties.Contains("Schema"))
yield return urn.GetAttribute("Schema");
if (properties.Contains("Name"))
yield return urn.GetAttribute("Name");
foreach (ObjectProperty obj in info.UrnProperties)
{
if (obj.Name.Equals("Name", StringComparison.OrdinalIgnoreCase) || obj.Name.Equals("Schema", StringComparison.OrdinalIgnoreCase))
continue;
yield return urn.GetAttribute(obj.Name);
}
}
public static string BuildUrnPath(string urn)
{
StringBuilder urnPathBuilder = new StringBuilder(urn != null ? urn.Length : 0);
string folderName = string.Empty;
bool replaceLeafValueInQuery = false;
if (!string.IsNullOrEmpty(urn))
{
Urn urnObject = new Urn(urn);
while (urnObject != null)
{
string objectType = urnObject.Type;
if (string.CompareOrdinal(objectType, "Folder") == 0)
{
folderName = urnObject.GetAttribute("Name").Replace(" ", "");
if (folderName != null)
{
objectType = string.Format("{0}Folder", folderName);
}
}
// Build the path
if (urnPathBuilder.Length > 0)
{
urnPathBuilder.Insert(0, '/');
}
if (objectType.Length > 0)
{
urnPathBuilder.Insert(0, objectType);
}
// Remove one element from the urn
urnObject = urnObject.Parent;
}
// Build the query
if (replaceLeafValueInQuery)
{
// This is another special case for DTS urns.
// When we want to request data for an individual package
// we need to use a special urn with Leaf="2" attribute,
// replacing the Leaf='1' that comes from OE.
urnObject = new Urn(urn.Replace("@Leaf='1'", "@Leaf='2'"));
}
else
{
urnObject = new Urn(urn);
}
}
return urnPathBuilder.ToString();
}
public static object CreateObjectInstance(string urn, ServerConnection serverConnection)
{
if (string.IsNullOrEmpty(urn))
{
return null;
}
try
{
SfcObjectQuery oq = null;
Urn urnObject = new Microsoft.SqlServer.Management.Sdk.Sfc.Urn(urn);
// i have to find domain from Urn.
// DomainInstanceName thrown NotImplemented Exception
// so, i have to walk Urn tree to the top
Urn current = urnObject;
while (current.Parent != null)
{
current = current.Parent;
}
string domainName = current.Type;
if (domainName == "Server")
{
oq = new SfcObjectQuery(new Microsoft.SqlServer.Management.Smo.Server(serverConnection));
}
else
{
SqlConnection connection = serverConnection.SqlConnectionObject;
if (connection == null)
{
return null;
}
// no need to check return value - this method will throw, if domain is incorrect
SfcDomainInfo ddi = Microsoft.SqlServer.Management.Sdk.Sfc.SfcRegistration.Domains[domainName];
ISfcDomain domain = (ISfcDomain)Activator.CreateInstance(ddi.RootType, new SqlStoreConnection(connection));
oq = new SfcObjectQuery(domain);
}
foreach (object obj in oq.ExecuteIterator(new SfcQueryExpression(urn), null, null))
{
return obj;
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
return null;
}
return null;
}
#endregion
}
public class DataContainerXmlGenerator
{
#region private members
/// <summary>
/// additional xml to be passed to the dialog
/// </summary>
protected string rawXml = string.Empty;
/// <summary>
/// do not pass this type information to the dialog.
/// e.g. New Database menu item on an existing database should not pass the database name through,
/// so we set itemType as Database.
/// </summary>
protected string? itemType = string.Empty;
/// <summary>
/// Additional query to perform and pass the results to the dialog.
/// </summary>
protected string? invokeMultiChildQueryXPath = null;
private ActionContext context;
/// <summary>
/// The node in the hierarchy that owns this
/// </summary>
public virtual ActionContext Context
{
get { return context; }
set { context = value; }
}
private string mode;
/// <summary>
/// mode
/// </summary>
/// <example>
/// "new" "properties"
/// </example>
public string Mode
{
get { return mode; }
set { mode = value; }
}
#endregion
#region construction
/// <summary>
///
/// </summary>
public DataContainerXmlGenerator(ActionContext context, string mode = "new")
{
this.context = context;
this.mode = mode;
}
#endregion
#region IObjectBuilder implementation
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddProperty(string name, object value)
{
// RAWXML is xml that is added to the document we're passing to the dialog with no additional
// processing
if (string.Compare(name, "rawxml", StringComparison.OrdinalIgnoreCase) == 0)
{
this.rawXml += value.ToString();
}
// ITEMTYPE is for new menu items where we do not want to pass in the information for this type
// e.g. New Database menu item on an existing database should not pass the database name through,
// so we set ITEMTYPE as Database.
else if (string.Compare(name, "itemtype", StringComparison.OrdinalIgnoreCase) == 0)
{
this.itemType = value.ToString();
}
// Allows us to query below the current level in the enumerator and pass the results through to
// the dialog. Usefull for Do xyz on all for menu's on folders.
else if (string.Compare(name, "multichildqueryxpath", StringComparison.OrdinalIgnoreCase) == 0)
{
this.invokeMultiChildQueryXPath = value.ToString();
}
}
#endregion
#region xml
#region xml document generation
/// <summary>
/// Generate an XmlDocument that contains all of the context needed to launch a dialog
/// </summary>
/// <returns>XmlDocument</returns>
public virtual XmlDocument GenerateXmlDocument()
{
MemoryStream memoryStream = new MemoryStream();
// build the xml
XmlTextWriter xmlWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
// write out the document headers
StartXmlDocument(xmlWriter);
// write xml specific to each connection type
GenerateConnectionXml(xmlWriter);
// generate the xml specific to the item we are being launched against
GenerateItemContext(xmlWriter);
// write out any of out properties to the document
WritePropertiesToXml(xmlWriter);
// close the document headers
EndXmlDocument(xmlWriter);
// make sure everything is commited
xmlWriter.Flush();
// Resets the stream to the beginning
memoryStream.Seek(0, SeekOrigin.Begin);
// done composing the XML string, now build the document
XmlDocument doc = new XmlDocument();
// don't lose leading or trailing whitespace
doc.PreserveWhitespace = true;
// directly create the document from the memoryStream.
// We do this because using an xmlreader in between would an extra
// overhead and it also messes up the new line characters in the original
// stream (converts all \r to \n).-anchals
doc.Load(memoryStream);
return doc;
}
#endregion
#region document start/end
/// <summary>
/// Write the starting elements needed by the dialog framework
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void StartXmlDocument(XmlWriter xmlWriter)
{
XmlGeneratorHelper.StartXmlDocument(xmlWriter);
}
/// <summary>
/// Close the elements needed by the dialog framework
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void EndXmlDocument(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// close params
xmlWriter.WriteEndElement();
// close formdescription
xmlWriter.WriteEndElement();
}
#endregion
#region server specific generation
/// <summary>
/// Generate the XML that will allow the dialog to connect to the server
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateConnectionXml(XmlWriter xmlWriter)
{
XmlGeneratorHelper.GenerateConnectionXml(xmlWriter, this.Context);
}
/// <summary>
/// Generate SQL Server specific connection information
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateSqlConnectionXml(XmlWriter xmlWriter)
{
XmlGeneratorHelper.GenerateSqlConnectionXml(xmlWriter, this.Context);
}
#endregion
#region item context generation
/// <summary>
/// Generate context specific to the node this menu item is being launched against.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateItemContext(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// There are two ways we can add context information.
// The first is just off of the node we were launched against. We will break the urn down
// into it's individual components. And pass them to the dialog.
// The second is by performing a query relative to the node we were launched against
// and adding any urns that are returned. No other process will be performed on the urn
// see if we are invoking on single, or multiple items
if (InvokeOnSingleItemOnly())
{
// no query, just an individual item
GenerateIndividualItemContext(xmlWriter);
}
else
{
GenerateMultiItemContext(xmlWriter);
}
}
/// <summary>
/// Generate the context for an individual item.
/// While Generating the context we will break down the Urn to it's individual elements
/// and pass each Type attribute in individually.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateIndividualItemContext(XmlWriter xmlWriter)
{
XmlGeneratorHelper.GenerateIndividualItemContext(xmlWriter, itemType, this.Context);
}
/// <summary>
/// Generate Context for multiple items.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateMultiItemContext(XmlWriter xmlWriter)
{
// there will be a query performed
GenerateItemContextFromQuery(xmlWriter);
}
/// <summary>
/// Generate Context with the results of a Query. We will just pass in the multiple
/// Urn's if any that are the results of the query.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateItemContextFromQuery(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// generate the request
Request request = new Request();
// only need urn
request.Fields = new string[] { "Urn" };
request.Urn = new Urn(this.Context.ContextUrn + "/" + this.invokeMultiChildQueryXPath);
DataTable dt;
// run the query
Enumerator enumerator = new Enumerator();
EnumResult result = enumerator.Process(this.Context.Connection, request);
if (result.Type == ResultType.DataTable)
{
dt = result;
}
else
{
dt = ((DataSet)result).Tables[0];
}
//TODO: Consider throwing if there are no results.
// Write the results to the XML document
foreach (DataRow row in dt.Rows)
{
WriteUrnInformation(xmlWriter, row[0].ToString());
}
}
/// <summary>
/// Writes a Urn to the XML. If this is an Olap connection we will also write out
/// the Olap Path, which is the AMO equivelent of a Urn.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
/// <param name="urn">Urn to be written</param>
protected virtual void WriteUrnInformation(XmlWriter xmlWriter, string? urn)
{
XmlGeneratorHelper.WriteUrnInformation(xmlWriter, urn, this.Context);
}
/// <summary>
/// Get the list of Urn attributes for this item.
/// </summary>
/// <param name="urn">Urn to be checked</param>
/// <returns>string array of Urn attribute names. This can be zero length but will not be null</returns>
protected virtual string[] GetUrnAttributes(Urn urn)
{
string[]? urnAttributes = null;
if (urn.XPathExpression != null && urn.XPathExpression.Length > 0)
{
int index = urn.XPathExpression.Length - 1;
if (index > 0)
{
System.Collections.SortedList list = urn.XPathExpression[index].FixedProperties;
System.Collections.ICollection keys = list.Keys;
urnAttributes = new string[keys.Count];
int i = 0;
foreach (object o in keys)
{
string? key = o.ToString();
if (key != null)
{
urnAttributes[i++] = key;
}
}
}
}
return urnAttributes != null ? urnAttributes : new string[0];
}
#endregion
#region write properties
/// <summary>
/// Write properties set for this menu item. These can be set to pass different information
/// to the dialog independently of the node type.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void WritePropertiesToXml(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// mode could indicate properties or new
if (Mode != null && Mode.Length > 0)
{
xmlWriter.WriteElementString("mode", Mode);
}
// raw xml to be passed to the dialog.
// mostly used to control instance awareness.
if (rawXml != null && rawXml.Length > 0)
{
xmlWriter.WriteRaw(rawXml);
}
// mostly used to restrict the context for new item off of an item of that type
// some dialogs require this is passed in so they know what item type they are
// supposed to be creating.
if (this.itemType.Length > 0)
{
xmlWriter.WriteElementString("itemtype", this.itemType);
}
}
#endregion
#endregion
#region protected helpers
/// <summary>
/// Inidicates whether the source is a single or multiple items.
/// </summary>
/// <returns></returns>
protected virtual bool InvokeOnSingleItemOnly()
{
return (this.invokeMultiChildQueryXPath == null || this.invokeMultiChildQueryXPath.Length == 0);
}
#endregion
}
/// <summary>
/// provides helper methods to generate LaunchForm XML and launch certain wizards and dialogs
/// </summary>
public static class XmlGeneratorHelper
{
/// <summary>
/// Write the starting elements needed by the dialog framework
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void StartXmlDocument(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
xmlWriter.WriteStartElement("formdescription");
xmlWriter.WriteStartElement("params");
}
/// <summary>
/// Writes a Urn to the XML. If this is an Olap connection we will also write out
/// the Olap Path, which is the AMO equivelent of a Urn.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
/// <param name="urn">Urn to be written</param>
public static void WriteUrnInformation(XmlWriter xmlWriter, string urn, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// write the Urn
xmlWriter.WriteElementString("urn", urn);
}
/// <summary>
/// Generate the XML that will allow the dialog to connect to the server
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void GenerateConnectionXml(XmlWriter xmlWriter, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// framework also needs to know the type
string serverType = string.Empty;
// Generate Connection specific XML.
if (context.Connection is ServerConnection)
{
GenerateSqlConnectionXml(xmlWriter, context);
serverType = "sql";
}
else
{
System.Diagnostics.Debug.Assert(false, "Warning: Connection type is unknown.");
}
System.Diagnostics.Debug.Assert(serverType.Length > 0, "serverType has not been defined");
xmlWriter.WriteElementString("servertype", serverType);
}
/// <summary>
/// Generate SQL Server specific connection information
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void GenerateSqlConnectionXml(XmlWriter xmlWriter, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// write the server name
xmlWriter.WriteElementString("servername", context.Connection.ServerInstance);
}
/// <summary>
/// Generate the context for an individual item.
/// While Generating the context we will break down the Urn to it's individual elements
/// and pass each Type attribute in individually.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void GenerateIndividualItemContext(XmlWriter xmlWriter, string itemType, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
System.Diagnostics.Debug.Assert(context.ContextUrn != null, "No context available.");
Urn urn = new Urn(context.ContextUrn);
foreach (KeyValuePair<string, string> item in ExtractUrnPart(itemType, urn))
{
xmlWriter.WriteElementString(item.Key, item.Value);
}
// if we are filtering out the information for this level (e.g. new database on a database should not
// pass in the information relating to the selected database. We need to make sure that the Urn we pass
// in is trimmed as well.
Urn sourceUrn = new Urn(context.ContextUrn);
if (itemType != null
&& itemType.Length > 0
&& sourceUrn.Type == itemType)
{
sourceUrn = sourceUrn.Parent;
}
// as well as breaking everything down we will write the Urn directly
// into the XML. Some dialogs will use the individual items, some will
// use the Urn.
WriteUrnInformation(xmlWriter, sourceUrn, context);
}
public static IEnumerable<KeyValuePair<string, string>> ExtractUrnPart(string itemType, Urn urn)
{
// break the urn up into individual xml elements, and add each item
// so Database[@Name='foo']/User[@Name='bar']
// will become
// <database>foo</database>
// <user>bar</user>
// Note: We don't care about server. It is taken care of elsewhere.
// The dialogs need every item to be converted to lower case or they will not
// be able to retrieve the information.
do
{
// server information has already gone in, and is server type specific
// don't get it from the urn
if (urn.Parent != null)
{
// get the attributes for this part of the Urn. For Olap this is ID, for
// everything else it is usually Name, although Schema may also be used for SQL
string[] urnAttributes = UrnUtils.GetUrnAttributes(urn);
// make sure we are not supposed to skip this type. The skip allows us to bring up a "new"
// dialog on an item of that type without passing in context.
// e.g. New Database... on AdventureWorks should not pass in <database>AdventureWorks</Database>
if (string.Compare(urn.Type, itemType, StringComparison.OrdinalIgnoreCase) != 0)
{
for (int i = 0; i < urnAttributes.Length; ++i)
{
// Some Urn attributes require special handling. Don't ask me why
string thisUrnAttribute = urnAttributes[i].ToLower(CultureInfo.InvariantCulture);
string elementName;
switch (thisUrnAttribute)
{
case "schema":
case "categoryid":
elementName = thisUrnAttribute;
break;
default:
elementName = urn.Type.ToLower(CultureInfo.InvariantCulture); // I think it's always the same as thisUrnAttribute but I'm not sure
break;
}
yield return new KeyValuePair<string, string>(elementName, urn.GetAttribute(urnAttributes[i]));
}
}
}
urn = urn.Parent;
}
while (urn != null);
}
}
}

View File

@@ -207,7 +207,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </returns>
protected virtual bool DoPreProcessExecution(RunType runType, out ExecutionMode executionResult)
{
//ask the framework to do normal execution by calling OnRunNOw methods
//ask the framework to do normal execution by calling OnRunNow methods
//of the views one by one
executionResult = ExecutionMode.Success;
return true;

View File

@@ -0,0 +1,135 @@
//
// 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.IO;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Management.Sdk.Sfc;
namespace Microsoft.SqlTools.ServiceLayer.Management
{
/// <summary>
/// Provides helper functions for converting urn enumerator urns
/// to olap path equivilent.
/// </summary>
#if DEBUG || EXPOSE_MANAGED_INTERNALS
public
#else
internal
#endif
class UrnDataPathConverter
{
// static only members
private UrnDataPathConverter()
{
}
/// <summary>
/// Convert a Urn to and olap compatible datapath string
/// </summary>
/// <param name="urn">Source urn</param>
/// <returns>string that Xml that can be used as an olap path</returns>
/// <remarks>
/// Node types are
/// ServerID
/// DatabaseID
/// CubeID
/// DimensionID
/// MeasureGroupID
/// PartitionID
/// MiningStructureID
/// MininingModelID
///
/// These currently map mostly to enuerator types with the addition of ID
///
/// string is of the format <ObjectTypeID>ObjectID</ObjectTypeID><3E>.<ObjectTypeID>ObjectID</ObjectTypeID>
///
/// </remarks>
public static string ConvertUrnToDataPath(Urn urn)
{
String element = String.Empty;
if(urn == null)
{
throw new ArgumentNullException("urn");
}
StringWriter stringWriter = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
ConvertUrnToDataPath(urn, xmlWriter);
xmlWriter.Flush();
xmlWriter.Close();
return stringWriter.ToString();
}
/// <summary>
/// Datapath conversion helper. Does the conversion using XmlWriter and recursion.
/// </summary>
/// <param name="urn">Urn to be converted</param>
/// <param name="writer">XmlWriter that the results will be written to.</param>
private static void ConvertUrnToDataPath(Urn urn, XmlWriter xmlWriter)
{
if(urn == null)
{
throw new ArgumentNullException("urn");
}
if(xmlWriter == null)
{
throw new ArgumentNullException("xmlWriter");
}
// preserve the order so do the parent first
Urn parent = urn.Parent;
if(parent != null)
{
ConvertUrnToDataPath(parent, xmlWriter);
}
String tag = urn.Type;
// don't put server into the olap path.
if(tag != "OlapServer")
{
xmlWriter.WriteElementString(tag + "ID", urn.GetAttribute("ID"));
}
}
/// <summary>
/// Convert an xml body string that is compatible with a string representation
/// (i.e. deal with < > &)
/// </summary>
/// <param name="s">source</param>
/// <returns>string that can be used as the body for xml stored in a string</returns>
public static string TokenizeXml(string source)
{
System.Diagnostics.Debug.Assert(false, "do not use this function. See bugs 322423 and 115450 in SQLBU Defect tracking");
if(null == source) return String.Empty;
StringBuilder sb = new StringBuilder();
foreach(char c in source)
{
switch(c)
{
case '<':
sb.Append("&lt;");
break;
case '>':
sb.Append("&gt;");
break;
case '&':
sb.Append("&amp;");
break;
default:
sb.Append(c);
break;
}
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,46 @@
//
// 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.Sdk.Sfc;
namespace Microsoft.SqlTools.ServiceLayer.Management
{
internal class UrnUtils
{
private UrnUtils () { }
/// <summary>
/// Get the list of Urn attributes for this item.
/// </summary>
/// <param name="urn">Urn to be checked</param>
/// <returns>String array of Urn attribute names. This can be zero length but will not be null</returns>
public static string[] GetUrnAttributes(Urn urn)
{
String[]? urnAttributes = null;
if(urn.XPathExpression != null && urn.XPathExpression.Length > 0)
{
int index = urn.XPathExpression.Length - 1;
if(index >= 0)
{
System.Collections.SortedList list = urn.XPathExpression[index].FixedProperties;
System.Collections.ICollection keys = list.Keys;
urnAttributes = new String[keys.Count];
int i = 0;
foreach(object o in keys)
{
string? key = o.ToString();
if (key != null)
urnAttributes[i++] = key;
}
}
}
return urnAttributes != null ? urnAttributes : new String[0];
}
}
}

View File

@@ -58,7 +58,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary>
public class UserInfo
{
DatabaseUserType? Type { get; set; }
public DatabaseUserType? Type { get; set; }
public string UserName { get; set; }
public string LoginName { get; set; }
@@ -72,9 +74,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
public bool isAAD { get; set; }
public ExtendedProperty[] ExtendedProperties { get; set; }
public ExtendedProperty[]? ExtendedProperties { get; set; }
public SecurablePermissions[] SecurablePermissions { get; set; }
public SecurablePermissions[]? SecurablePermissions { get; set; }
}
}

View File

@@ -0,0 +1,66 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{
/// <summary>
/// Create User parameters
/// </summary>
public class CreateUserParams : GeneralRequestDetails
{
public string OwnerUri { get; set; }
public UserInfo User { get; set; }
}
/// <summary>
/// Create User result
/// </summary>
public class CreateUserResult : ResultStatus
{
public UserInfo User { get; set; }
}
/// <summary>
/// Create User request type
/// </summary>
public class CreateUserRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<CreateUserParams, CreateUserResult> Type =
RequestType<CreateUserParams, CreateUserResult>.Create("objectmanagement/createuser");
}
/// <summary>
/// Delete User params
/// </summary>
public class DeleteUserParams : GeneralRequestDetails
{
public string OwnerUri { get; set; }
public string UserName { get; set; }
}
/// <summary>
/// Delete User request type
/// </summary>
public class DeleteUserRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<DeleteUserParams, ResultStatus> Type =
RequestType<DeleteUserParams, ResultStatus>.Create("objectmanagement/deleteuser");
}
}

View File

@@ -9,8 +9,8 @@ using System;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Security;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Dmf;
using Microsoft.SqlServer.Management.Sdk.Sfc;
@@ -31,7 +31,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
private bool disposed;
private ConnectionService connectionService = null;
private ConnectionService connectionService;
private static readonly Lazy<SecurityService> instance = new Lazy<SecurityService>(() => new SecurityService());
@@ -93,6 +93,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// Login request handlers
this.ServiceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true);
this.ServiceHost.SetRequestHandler(DeleteLoginRequest.Type, HandleDeleteLoginRequest, true);
// User request handlers
this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, HandleCreateUserRequest, true);
}
@@ -170,112 +173,73 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
#region "User Handlers"
private UserPrototype InitUserNew(CDataContainer dataContainer)
internal Task<Tuple<bool, string>> ConfigureUser(
string ownerUri,
UserInfo user,
ConfigAction configAction,
RunType runType)
{
// this.DataContainer = context;
// this.parentDbUrn = new Urn(this.DataContainer.ParentUrn);
// this.objectUrn = new Urn(this.DataContainer.ObjectUrn);
ExhaustiveUserTypes currentUserType;
UserPrototypeFactory userPrototypeFactory = UserPrototypeFactory.GetInstance(dataContainer);
if (dataContainer.IsNewObject)
return Task<Tuple<bool, string>>.Run(() =>
{
if (IsParentDatabaseContained(dataContainer.ParentUrn, dataContainer))
try
{
currentUserType = ExhaustiveUserTypes.SqlUserWithPassword;
}
else
{
currentUserType = ExhaustiveUserTypes.LoginMappedUser;
}
}
else
{
currentUserType = this.GetCurrentUserTypeForExistingUser(
dataContainer.Server.GetSmoObject(dataContainer.ObjectUrn) as User);
}
UserPrototype currentUserPrototype = userPrototypeFactory.GetUserPrototype(currentUserType);
return currentUserPrototype;
}
private ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User user)
{
switch (user.UserType)
{
case UserType.SqlUser:
if (user.IsSupportedProperty("AuthenticationType"))
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo);
if (connInfo == null)
{
if (user.AuthenticationType == AuthenticationType.Windows)
{
return ExhaustiveUserTypes.WindowsUser;
}
else if (user.AuthenticationType == AuthenticationType.Database)
{
return ExhaustiveUserTypes.SqlUserWithPassword;
}
throw new ArgumentException("Invalid connection URI '{0}'", ownerUri);
}
return ExhaustiveUserTypes.LoginMappedUser;
case UserType.NoLogin:
return ExhaustiveUserTypes.SqlUserWithoutLogin;
case UserType.Certificate:
return ExhaustiveUserTypes.CertificateMappedUser;
case UserType.AsymmetricKey:
return ExhaustiveUserTypes.AsymmetricKeyMappedUser;
default:
return ExhaustiveUserTypes.Unknown;
}
var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer");
var connectionInfoWithConnection = new SqlConnectionInfoWithConnection();
connectionInfoWithConnection.ServerConnection = serverConnection;
string urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']",
Urn.EscapeString(serverConnection.DatabaseName));
ActionContext context = new ActionContext(serverConnection, "new_user", urn);
DataContainerXmlGenerator containerXml = new DataContainerXmlGenerator(context);
containerXml.AddProperty("itemtype", "User");
XmlDocument xmlDoc = containerXml.GenerateXmlDocument();
bool objectExists = configAction != ConfigAction.Create;
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connectionInfoWithConnection, xmlDoc);
using (var actions = new UserActions(dataContainer, user, configAction))
{
var executionHandler = new ExecutonHandler(actions);
executionHandler.RunNow(runType, this);
}
return new Tuple<bool, string>(true, string.Empty);
}
catch (Exception ex)
{
return new Tuple<bool, string>(false, ex.ToString());
}
});
}
private bool IsParentDatabaseContained(Urn parentDbUrn, CDataContainer dataContainer)
/// <summary>
/// Handle request to create a user
/// </summary>
internal async Task HandleCreateUserRequest(CreateUserParams parameters, RequestContext<CreateUserResult> requestContext)
{
string parentDbName = parentDbUrn.GetNameForType("Database");
Database parentDatabase = dataContainer.Server.Databases[parentDbName];
var result = await ConfigureUser(parameters.OwnerUri,
parameters.User,
ConfigAction.Create,
RunType.RunNow);
if (parentDatabase.IsSupportedProperty("ContainmentType")
&& parentDatabase.ContainmentType == ContainmentType.Partial)
await requestContext.SendResult(new CreateUserResult()
{
return true;
}
return false;
User = parameters.User,
Success = result.Item1,
ErrorMessage = result.Item2
});
}
private void GetUserTypeOptions(CDataContainer dataContainer)
{
if (SqlMgmtUtils.IsSql11OrLater(dataContainer.Server.ServerVersion)
&& IsParentDatabaseContained(dataContainer.ParentUrn, dataContainer))
{
// this.userTypeComboBox.Items.AddRange(
// new string[]{
// UserSR.SqlUserWithPasswordUserTypeText
// }
//);
}
if (SqlMgmtUtils.IsYukonOrAbove(dataContainer.Server))
{
// this.userTypeComboBox.Items.AddRange(
// new string[]{
// UserSR.AsymmetricKeyUserTypeText,
// UserSR.CertificateUserTypeText,
// UserSR.WithoutLoginSqlUserTypeText,
// UserSR.WindowsUserTypeText
// }
// );
}
// this.userTypeComboBox.Items.AddRange(
// new string[]{
// UserSR.LoginMappedSqlUserTypeText
// }
// );
}
private void GetDefaultLanguageOptions(CDataContainer dataContainer)
{
// this.defaultLanguageComboBox.Items.Clear();
@@ -298,65 +262,54 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
private SecureString GetReadOnlySecureString(string secret)
{
SecureString ss = new SecureString();
foreach (char c in secret.ToCharArray())
{
ss.AppendChar(c);
}
ss.MakeReadOnly();
// code needs to be ported into the useraction class
// public void UserMemberships_OnRunNow(object sender, CDataContainer dataContainer)
// {
// UserPrototype currentPrototype = UserPrototypeFactory.GetInstance(dataContainer).CurrentPrototype;
return ss;
}
// //In case the UserGeneral/OwnedSchemas pages are loaded,
// //those will takes care of applying membership changes also.
// //Hence, we only need to apply changes in this method when those are not loaded.
// if (!currentPrototype.IsRoleMembershipChangesApplied)
// {
// //base.OnRunNow(sender);
public void UserMemberships_OnRunNow(object sender, CDataContainer dataContainer)
{
UserPrototype currentPrototype = UserPrototypeFactory.GetInstance(dataContainer).CurrentPrototype;
// User user = currentPrototype.ApplyChanges();
//In case the UserGeneral/OwnedSchemas pages are loaded,
//those will takes care of applying membership changes also.
//Hence, we only need to apply changes in this method when those are not loaded.
if (!currentPrototype.IsRoleMembershipChangesApplied)
{
//base.OnRunNow(sender);
// //this.ExecutionMode = ExecutionMode.Success;
// dataContainer.ObjectName = currentPrototype.Name;
// dataContainer.SqlDialogSubject = user;
// }
User user = currentPrototype.ApplyChanges();
// //setting back to original after changes are applied
// currentPrototype.IsRoleMembershipChangesApplied = false;
// }
//this.ExecutionMode = ExecutionMode.Success;
dataContainer.ObjectName = currentPrototype.Name;
dataContainer.SqlDialogSubject = user;
}
// /// <summary>
// /// implementation of OnPanelRunNow
// /// </summary>
// /// <param name="node"></param>
// public void UserOwnedSchemas_OnRunNow(object sender, CDataContainer dataContainer)
// {
// UserPrototype currentPrototype = UserPrototypeFactory.GetInstance(dataContainer).CurrentPrototype;
//setting back to original after changes are applied
currentPrototype.IsRoleMembershipChangesApplied = false;
}
// //In case the UserGeneral/Membership pages are loaded,
// //those will takes care of applying schema ownership changes also.
// //Hence, we only need to apply changes in this method when those are not loaded.
// if (!currentPrototype.IsSchemaOwnershipChangesApplied)
// {
// //base.OnRunNow(sender);
/// <summary>
/// implementation of OnPanelRunNow
/// </summary>
/// <param name="node"></param>
public void UserOwnedSchemas_OnRunNow(object sender, CDataContainer dataContainer)
{
UserPrototype currentPrototype = UserPrototypeFactory.GetInstance(dataContainer).CurrentPrototype;
// User user = currentPrototype.ApplyChanges();
//In case the UserGeneral/Membership pages are loaded,
//those will takes care of applying schema ownership changes also.
//Hence, we only need to apply changes in this method when those are not loaded.
if (!currentPrototype.IsSchemaOwnershipChangesApplied)
{
//base.OnRunNow(sender);
// //this.ExecutionMode = ExecutionMode.Success;
// dataContainer.ObjectName = currentPrototype.Name;
// dataContainer.SqlDialogSubject = user;
// }
User user = currentPrototype.ApplyChanges();
//this.ExecutionMode = ExecutionMode.Success;
dataContainer.ObjectName = currentPrototype.Name;
dataContainer.SqlDialogSubject = user;
}
//setting back to original after changes are applied
currentPrototype.IsSchemaOwnershipChangesApplied = false;
}
// //setting back to original after changes are applied
// currentPrototype.IsSchemaOwnershipChangesApplied = false;
// }
// how to populate defaults from prototype, will delete once refactored
// private void InitializeValuesInUiControls()
@@ -855,7 +808,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
Database database = dataContainer.Server.Databases[databaseName];
System.Diagnostics.Debug.Assert(database!= null, "database is null");
DatabaseRole role = null;
DatabaseRole role;
if (isPropertiesMode == true) // in properties mode -> alter role
{
@@ -888,7 +841,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private void DbRole_LoadSchemas(string databaseName, string dbroleName, ServerConnection serverConnection)
{
bool isPropertiesMode = false;
HybridDictionary schemaOwnership = null;
HybridDictionary schemaOwnership;
schemaOwnership = new HybridDictionary();
Enumerator en = new Enumerator();

View File

@@ -0,0 +1,144 @@
//
// 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 Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Security
{
internal class UserActions : ManagementActionBase
{
#region Variables
//private UserPrototypeData userData;
private UserPrototype userPrototype;
private UserInfo user;
private ConfigAction configAction;
#endregion
#region Constructors / Dispose
/// <summary>
/// required when loading from Object Explorer context
/// </summary>
/// <param name="context"></param>
public UserActions(
CDataContainer context,
UserInfo user,
ConfigAction configAction)
{
this.DataContainer = context;
this.user = user;
this.configAction = configAction;
this.userPrototype = InitUserNew(context, user);
}
// /// <summary>
// /// Clean up any resources being used.
// /// </summary>
// protected override void Dispose(bool disposing)
// {
// base.Dispose(disposing);
// }
#endregion
/// <summary>
/// called on background thread by the framework to execute the action
/// </summary>
/// <param name="node"></param>
public override void OnRunNow(object sender)
{
if (this.configAction == ConfigAction.Drop)
{
// if (this.credentialData.Credential != null)
// {
// this.credentialData.Credential.DropIfExists();
// }
}
else
{
this.userPrototype.ApplyChanges();
}
}
private UserPrototype InitUserNew(CDataContainer dataContainer, UserInfo user)
{
// this.DataContainer = context;
// this.parentDbUrn = new Urn(this.DataContainer.ParentUrn);
// this.objectUrn = new Urn(this.DataContainer.ObjectUrn);
ExhaustiveUserTypes currentUserType;
UserPrototypeFactory userPrototypeFactory = UserPrototypeFactory.GetInstance(dataContainer, user);
if (dataContainer.IsNewObject)
{
if (IsParentDatabaseContained(dataContainer.ParentUrn, dataContainer))
{
currentUserType = ExhaustiveUserTypes.SqlUserWithPassword;
}
else
{
currentUserType = ExhaustiveUserTypes.LoginMappedUser;
}
}
else
{
currentUserType = this.GetCurrentUserTypeForExistingUser(
dataContainer.Server.GetSmoObject(dataContainer.ObjectUrn) as User);
}
UserPrototype currentUserPrototype = userPrototypeFactory.GetUserPrototype(currentUserType);
return currentUserPrototype;
}
private ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User user)
{
switch (user.UserType)
{
case UserType.SqlUser:
if (user.IsSupportedProperty("AuthenticationType"))
{
if (user.AuthenticationType == AuthenticationType.Windows)
{
return ExhaustiveUserTypes.WindowsUser;
}
else if (user.AuthenticationType == AuthenticationType.Database)
{
return ExhaustiveUserTypes.SqlUserWithPassword;
}
}
return ExhaustiveUserTypes.LoginMappedUser;
case UserType.NoLogin:
return ExhaustiveUserTypes.SqlUserWithoutLogin;
case UserType.Certificate:
return ExhaustiveUserTypes.CertificateMappedUser;
case UserType.AsymmetricKey:
return ExhaustiveUserTypes.AsymmetricKeyMappedUser;
default:
return ExhaustiveUserTypes.Unknown;
}
}
private bool IsParentDatabaseContained(Urn parentDbUrn, CDataContainer dataContainer)
{
string parentDbName = parentDbUrn.GetNameForType("Database");
Database parentDatabase = dataContainer.Server.Databases[parentDbName];
if (parentDatabase.IsSupportedProperty("ContainmentType")
&& parentDatabase.ContainmentType == ContainmentType.Partial)
{
return true;
}
return false;
}
}
}

View File

@@ -5,11 +5,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlTools.ServiceLayer.Management;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Security
{
@@ -101,7 +103,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.isMember = new Dictionary<string, bool>();
}
public UserPrototypeData(CDataContainer context)
public UserPrototypeData(CDataContainer context, UserInfo userInfo)
{
this.isSchemaOwned = new Dictionary<string, bool>();
this.isMember = new Dictionary<string, bool>();
@@ -110,10 +112,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
this.LoadUserData(context);
}
else
{
this.name = userInfo.UserName;
this.mappedLoginName = userInfo.LoginName;
this.defaultSchemaName = userInfo.DefaultSchema;
this.password = DatabaseUtils.GetReadOnlySecureString(userInfo.Password);
}
this.LoadRoleMembership(context);
this.LoadSchemaData(context);
this.LoadSchemaData(context);
}
public UserPrototypeData Clone()
@@ -465,7 +474,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
var comparer = this.parent.GetStringComparer();
if (comparer.Compare(dbRole.Name, "public") != 0)
{
this.roleNames.Add(dbRole.Name);
roleNames.Add(dbRole.Name);
}
}
return roleNames;
@@ -483,7 +492,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
foreach (Schema sch in this.parent.Schemas)
{
this.schemaNames.Add(sch.Name);
schemaNames.Add(sch.Name);
}
return schemaNames;
}
@@ -539,7 +548,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
enumerator.Reset();
String? nullString = null;
string? nullString = null;
while (enumerator.MoveNext())
{
@@ -597,15 +606,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
if ((this.currentState.userType == UserType.Certificate)
&&(!this.Exists || (user.Certificate != this.currentState.certificateName))
)
&&(!this.Exists || (user.Certificate != this.currentState.certificateName)))
{
user.Certificate = this.currentState.certificateName;
}
if ((this.currentState.userType == UserType.AsymmetricKey)
&& (!this.Exists || (user.AsymmetricKey != this.currentState.asymmetricKeyName))
)
&& (!this.Exists || (user.AsymmetricKey != this.currentState.asymmetricKeyName)))
{
user.AsymmetricKey = this.currentState.asymmetricKeyName;
}
@@ -621,7 +628,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
result = this.parent.Users[this.originalState.name];
result?.Refresh();
System.Diagnostics.Debug.Assert(0 == String.Compare(this.originalState.name, this.currentState.name, StringComparison.Ordinal), "name of existing user has changed");
System.Diagnostics.Debug.Assert(0 == string.Compare(this.originalState.name, this.currentState.name, StringComparison.Ordinal), "name of existing user has changed");
if (result == null)
{
throw new Exception();
@@ -756,7 +763,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
//Default Schema was not supported before Denali for windows group.
User user = this.GetUser();
if (this.Exists && user.LoginType == LoginType.WindowsGroup)
if (this.Exists && user.LoginType == Microsoft.SqlServer.Management.Smo.LoginType.WindowsGroup)
{
return SqlMgmtUtils.IsSql11OrLater(this.context.Server.ConnectionContext.ServerVersion);
}
@@ -993,15 +1000,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
private UserPrototypeFactory(CDataContainer context)
private UserPrototypeFactory(CDataContainer context, UserInfo user)
{
this.context = context;
this.originalData = new UserPrototypeData(this.context);
this.originalData = new UserPrototypeData(this.context, user);
this.currentData = this.originalData.Clone();
}
public static UserPrototypeFactory GetInstance(CDataContainer context)
public static UserPrototypeFactory GetInstance(CDataContainer context, UserInfo user)
{
if (singletonInstance != null
&& singletonInstance.context != context)
@@ -1009,7 +1016,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
singletonInstance = null;
}
singletonInstance ??= new UserPrototypeFactory(context);
singletonInstance ??= new UserPrototypeFactory(context, user);
return singletonInstance;
}
@@ -1076,87 +1083,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
CertificateMappedUser,
AsymmetricKeyMappedUser
};
internal class LanguageUtils
{
/// <summary>
/// Gets alias for a language name.
/// </summary>
/// <param name="connectedServer"></param>
/// <param name="languageName"></param>
/// <returns>Returns string.Empty in case it doesn't find a matching languageName on the server</returns>
public static string GetLanguageAliasFromName(Server connectedServer,
string languageName)
{
string languageAlias = string.Empty;
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language lang in connectedServer.Languages)
{
if (lang.Name == languageName)
{
languageAlias = lang.Alias;
break;
}
}
return languageAlias;
}
/// <summary>
/// Gets name for a language alias.
/// </summary>
/// <param name="connectedServer"></param>
/// <param name="languageAlias"></param>
/// <returns>Returns string.Empty in case it doesn't find a matching languageAlias on the server</returns>
public static string GetLanguageNameFromAlias(Server connectedServer,
string languageAlias)
{
string languageName = string.Empty;
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language lang in connectedServer.Languages)
{
if (lang.Alias == languageAlias)
{
languageName = lang.Name;
break;
}
}
return languageName;
}
/// <summary>
/// Sets exhaustive fields required for displaying and working with default languages in server,
/// database and user dialogs as default init fields so that queries are not sent again and again.
/// </summary>
/// <param name="connectedServer">server on which languages will be enumerated</param>
public static void SetLanguageDefaultInitFieldsForDefaultLanguages(Server connectedServer)
{
string[] fieldsNeeded = new string[] { "Alias", "Name", "LocaleID", "LangID" };
connectedServer.SetDefaultInitFields(typeof(Language), fieldsNeeded);
}
}
internal class ObjectNoLongerExistsException : Exception
{
private static string ExceptionMessage
{
get
{
return "Object no longer exists";
}
}
public ObjectNoLongerExistsException()
: base(ExceptionMessage)
{
//
// TODO: Add constructor logic here
//
}
}
}

View File

@@ -9,6 +9,7 @@ using Microsoft.SqlTools.ServiceLayer.Management;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
@@ -67,5 +68,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
parameters.Add($"{paramName}", contentBytes);
return $"@{paramName}";
}
public static SecureString GetReadOnlySecureString(string secret)
{
SecureString ss = new SecureString();
foreach (char c in secret.ToCharArray())
{
ss.AppendChar(c);
}
ss.MakeReadOnly();
return ss;
}
}
}

View File

@@ -0,0 +1,185 @@
//
// 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.Common;
using Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Summary description for CUtils.
/// </summary>
internal class LanguageUtils
{
/// <summary>
/// Gets alias for a language name.
/// </summary>
/// <param name="connectedServer"></param>
/// <param name="languageName"></param>
/// <returns>Returns string.Empty in case it doesn't find a matching languageName on the server</returns>
public static string GetLanguageAliasFromName(Server connectedServer,
string languageName)
{
string languageAlias = string.Empty;
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language lang in connectedServer.Languages)
{
if (lang.Name == languageName)
{
languageAlias = lang.Alias;
break;
}
}
return languageAlias;
}
/// <summary>
/// Gets name for a language alias.
/// </summary>
/// <param name="connectedServer"></param>
/// <param name="languageAlias"></param>
/// <returns>Returns string.Empty in case it doesn't find a matching languageAlias on the server</returns>
public static string GetLanguageNameFromAlias(Server connectedServer,
string languageAlias)
{
string languageName = string.Empty;
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language lang in connectedServer.Languages)
{
if (lang.Alias == languageAlias)
{
languageName = lang.Name;
break;
}
}
return languageName;
}
/// <summary>
/// Gets lcid for a languageId.
/// </summary>
/// <param name="connectedServer"></param>
/// <param name="languageAlias"></param>
/// <returns>Throws exception in case it doesn't find a matching languageId on the server</returns>
public static int GetLcidFromLangId(Server connectedServer,
int langId)
{
int lcid = -1; //Unacceptable Lcid.
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language lang in connectedServer.Languages)
{
if (lang.LangID == langId)
{
lcid = lang.LocaleID;
break;
}
}
if (lcid == -1) //Ideally this will never happen.
{
throw new ArgumentOutOfRangeException("langId", "This language id is not present in sys.syslanguages catalog.");
}
return lcid;
}
/// <summary>
/// Gets languageId for a lcid.
/// </summary>
/// <param name="connectedServer"></param>
/// <param name="languageAlias"></param>
/// <returns>Throws exception in case it doesn't find a matching lcid on the server</returns>
public static int GetLangIdFromLcid(Server connectedServer,
int lcid)
{
int langId = -1; //Unacceptable LangId.
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language lang in connectedServer.Languages)
{
if (lang.LocaleID == lcid)
{
langId = lang.LangID;
break;
}
}
if (langId == -1) //Ideally this will never happen.
{
throw new ArgumentOutOfRangeException("lcid", "This locale id is not present in sys.syslanguages catalog.");
}
return langId;
}
/// <summary>
/// returns a language choice alias for that language
/// </summary>
/// <param name="langid"></param>
/// <returns></returns>
public static LanguageChoice GetLanguageChoiceAlias(Server connectedServer,
int lcid)
{
SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
foreach (Language smoL in connectedServer.Languages)
{
if (smoL.LocaleID == lcid)
{
string alias = smoL.Alias;
return new LanguageChoice(alias, lcid);
}
}
return new LanguageChoice(String.Empty, lcid);
}
/// <summary>
/// Sets exhaustive fields required for displaying and working with default languages in server,
/// database and user dialogs as default init fields so that queries are not sent again and again.
/// </summary>
/// <param name="connectedServer">server on which languages will be enumerated</param>
public static void SetLanguageDefaultInitFieldsForDefaultLanguages(Server connectedServer)
{
string[] fieldsNeeded = new string[] { "Alias", "Name", "LocaleID", "LangID" };
connectedServer.SetDefaultInitFields(typeof(Language), fieldsNeeded);
}
}
#region interface - ILanguageLcidWithConnectionInfo - used by property editors to talk with data object
interface ILanguageLcidWithConnectionInfo
{
int Lcid { get; }
ServerConnection Connection { get; }
}
#endregion
#region class - LanguageChoice
internal class LanguageChoice
{
public string alias;
public System.Int32 lcid;
public LanguageChoice(string alias, System.Int32 lcid)
{
this.alias = alias;
this.lcid = lcid;
}
public override string ToString()
{
return alias;
}
}
#endregion
}

View File

@@ -0,0 +1,562 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using Microsoft.SqlServer.Management.Sdk.Sfc;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Provides a sorted collection of associated String keys and Object values that can be accessed either with the key or with the index.
/// </summary>
public class NameObjectCollection
{
#region struct NameValuePair
[DebuggerDisplay("{Name}:{Value}")]
internal struct NameValuePair
{
private string name;
private object value;
internal NameValuePair(string name)
{
this.name = name;
this.value = null;
}
internal NameValuePair(string name, object value)
{
this.name = name;
this.value = ConvertValue(value);
}
internal string Name
{
get { return this.name; }
}
internal object Value
{
get { return this.value; }
set { this.value = ConvertValue(value); }
}
public override bool Equals(object obj)
{
if (obj is NameValuePair)
{
return Equals((NameValuePair)obj);
}
return false;
}
public bool Equals(NameValuePair other)
{
return Name == other.Name;
}
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public static bool operator ==(NameValuePair p1, NameValuePair p2)
{
return p1.Equals(p2);
}
public static bool operator !=(NameValuePair p1, NameValuePair p2)
{
return !p1.Equals(p2);
}
private static object ConvertValue(object value)
{
// Depending of whether values come from a DataTable or
// a data reader, some property values can be either enum values or integers.
// For example the value of LoginType can be either SqlLogin or 1.
// Enums cause a problems because they gets converted to a string rather than an
// integer value in OE expression evaluation code.
// Since originally all the values came from data tables and the rest of OE code expects
// integers we are going to conver any enum valus to integers here
if (value != null && value.GetType().IsEnum)
{
value = Convert.ToInt32(value);
}
return value;
}
}
#endregion
#region Property
internal class Property : ISfcProperty
{
private NameValuePair pair;
internal Property(NameValuePair pair)
{
this.pair = pair;
}
#region ISfcProperty implementation
/// <summary>
/// Name of property
/// </summary>
public string Name
{
get { return pair.Name; }
}
/// <summary>
/// Type of property
/// </summary>
public Type Type
{
get { return pair.Value.GetType(); }
}
/// <summary>
/// Check whether the value is enabled or not
/// </summary>
public bool Enabled
{
get { return true; }
}
/// <summary>
/// Value of property
/// </summary>
public object Value
{
get { return pair.Value; }
set { throw new NotSupportedException(); }
}
/// <summary>
/// Indicates whether the property is required to persist the current state of the object
/// </summary>
public bool Required
{
get { return false; }
}
/// <summary>
/// Indicates that Consumer should be theat this property as read-only
/// </summary>
public bool Writable
{
get { return false; }
}
/// <summary>
/// Indicates whether the property value has been changed.
/// </summary>
public bool Dirty
{
get { return false; }
}
/// <summary>
/// Indicates whether the properties data has been read, and is null
/// </summary>
public bool IsNull
{
get { return pair.Value == null || pair.Value is DBNull; }
}
/// <summary>
/// Aggregated list of custom attributes associated with property
/// </summary>
public AttributeCollection Attributes
{
get { return null; }
}
#endregion
}
#endregion
private List<NameValuePair> pairs;
/// <summary>
/// Initializes a new instance of the NameObjectCollection class that is empty.
/// </summary>
public NameObjectCollection()
{
this.pairs = new List<NameValuePair>();
}
/// <summary>
/// Initializes a new instance of the NameObjectCollection class that is empty and has the specified initial capacity.
/// </summary>
/// <param name="capacity">The approximate number of entries that the NameObjectCollection instance can initially contain.</param>
public NameObjectCollection(int capacity)
{
this.pairs = new List<NameValuePair>(capacity);
}
/// <summary>
/// Adds an entry with the specified key and value into the NameObjectCollection instance.
/// </summary>
/// <param name="name">The String key of the entry to add. The key can be null.</param>
/// <param name="value">The Object value of the entry to add. The value can be null.</param>
public void Add(string name, object value)
{
this.pairs.Add(new NameValuePair(name, value));
}
/// <summary>
/// Removes all entries from the NameObjectCollection instance.
/// </summary>
public void Clear()
{
this.pairs.Clear();
}
/// <summary>
/// Gets the value of the specified entry from the NameObjectCollection instance. C# indexer
/// </summary>
public object this[int index]
{
get
{
return Get(index);
}
set
{
Set(index, value);
}
}
/// <summary>
/// Gets the value of the specified entry from the NameObjectCollection instance. C# indexer
/// </summary>
public object this[string name]
{
get
{
return Get(name);
}
set
{
Set(name, value);
}
}
/// <summary>
/// Gets the value of the specified entry from the NameObjectCollection instance.
/// </summary>
/// <param name="index">Gets the value of the entry at the specified index of the NameObjectCollection instance.</param>
/// <returns>An Object that represents the value of the first entry with the specified key, if found; otherwise null</returns>
public object Get(int index)
{
return this.pairs[index].Value;
}
/// <summary>
/// Gets the value of the first entry with the specified key from the NameObjectCollection instance.
/// </summary>
/// <param name="name">The String key of the entry to get. The key can be a null reference (Nothing in Visual Basic). </param>
/// <returns>An Object that represents the value of the first entry with the specified key, if found; otherwise null</returns>
public object Get(string name)
{
int index = IndexOf(name);
return index >= 0 ? this.pairs[index].Value : null;
}
/// <summary>
/// Returns a String array that contains all the keys in the NameObjectCollection instance.
/// </summary>
/// <returns>A String array that contains all the keys in the NameObjectCollection instance.</returns>
public string[] GetAllKeys()
{
string[] keys = new string[this.pairs.Count];
for (int i = 0; i < this.pairs.Count; i++)
{
keys[i] = this.pairs[i].Name;
}
return keys;
}
/// <summary>
/// Returns an array that contains all the values in the NameObjectCollection instance.
/// </summary>
/// <returns>An Object array that contains all the values in the NameObjectCollection instance.</returns>
public object[] GetAllValues()
{
object[] values = new object[this.pairs.Count];
for (int i = 0; i < this.pairs.Count; i++)
{
values[i] = this.pairs[i].Value;
}
return values;
}
/// <summary>
/// Gets the key of the entry at the specified index of the NameObjectCollection instance.
/// </summary>
/// <param name="index">The zero-based index of the key to get. </param>
/// <returns>A String that represents the key of the entry at the specified index.</returns>
public string GetKey(int index)
{
return this.pairs[index].Name;
}
/// <summary>
/// Gets a value indicating whether the NameObjectCollection instance contains entries whose keys are not null.
/// </summary>
/// <returns>true if the NameObjectCollection instance contains entries whose keys are not a null reference (Nothing in Visual Basic); otherwise, false.</returns>
public bool HasKeys()
{
return this.pairs.Count > 0;
}
/// <summary>
/// Removes the entries with the specified key from the NameObjectCollection instance.
/// </summary>
/// <param name="name"></param>
public void Remove(string name)
{
RemoveAt(IndexOf(name));
}
/// <summary>
/// Removes the entry at the specified index of the NameObjectCollection instance.
/// </summary>
/// <param name="index">The zero-based index of the entry to remove. </param>
public void RemoveAt(int index)
{
this.pairs.RemoveAt(index);
}
/// <summary>
/// Sets the value of the entry at the specified index of the NameObjectCollection instance.
/// </summary>
/// <param name="index">The zero-based index of the entry to set.</param>
/// <param name="value">The Object that represents the new value of the entry to set. The value can be null.</param>
public void Set(int index, object value)
{
NameValuePair pair = this.pairs[index];
pair.Value = value;
this.pairs[index] = pair;
}
/// <summary>
/// Sets the value of the first entry with the specified key in the NameObjectCollection instance, if found; otherwise, adds an entry with the specified key and value into the NameObjectCollection instance.
/// </summary>
/// <param name="name">The String key of the entry to set. The key can be null.</param>
/// <param name="value">The Object that represents the new value of the entry to set. The value can be null.</param>
public void Set(string name, object value)
{
int index = IndexOf(name);
if (index >= 0)
{
Set(index, value);
}
else
{
Add(name, value);
}
}
/// <summary>
/// Copies elements of this collection to an Array starting at a particular array index
/// </summary>
/// <param name="array">The one-dimensional Array that is the destination of the elements copied from NameObjectCollection. The Array must have zero-based indexing.</param>
/// <param name="index">The zero-based index in array at which copying begins.</param>
public void CopyTo(object[] array, int index)
{
GetAllValues().CopyTo(array, index);
}
/// <summary>
/// Gets internal index of NameValuePair
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private int IndexOf(string name)
{
// This version does simple iteration. It relies on GetHashCode for optimization
NameValuePair pair = new NameValuePair(name);
for (int i = 0; i < this.pairs.Count; i++)
{
if (this.pairs[i].Equals(pair))
{
return i;
}
}
return -1;
}
#region ISfcPropertySet implementation
/// <summary>
/// Checks if the property with specified name exists
/// </summary>
/// <param name="propertyName">property name</param>
/// <returns>true if succeeded</returns>
public bool Contains(string propertyName)
{
return IndexOf(propertyName) >= 0;
}
/// <summary>
/// Checks if the property with specified metadata exists
/// </summary>
/// <param name="property">Property</param>
/// <returns>true if succeeded</returns>
public bool Contains(ISfcProperty property)
{
return Contains(property.Name);
}
/// <summary>
/// Checks if the property with specified name and type exists
/// </summary>
/// <typeparam name="T">property type</typeparam>
/// <param name="name">property name</param>
/// <returns>true if succeeded</returns>
public bool Contains<T>(string name)
{
int index = IndexOf(name);
return index >= 0 && this.pairs[index].Value != null && this.pairs[index].Value.GetType() == typeof(T);
}
/// <summary>
/// Attempts to get property value from provider
/// </summary>
/// <typeparam name="T">property type</typeparam>
/// <param name="name">name name</param>
/// <param name="value">property value</param>
/// <returns>true if succeeded</returns>
public bool TryGetPropertyValue<T>(string name, out T value)
{
value = default(T);
int index = IndexOf(name);
if (index >= 0)
{
value = (T)this.pairs[index].Value;
return true;
}
return false;
}
/// <summary>
/// Attempts to get property value from provider
/// </summary>
/// <param name="name">property name</param>
/// <param name="value">property value</param>
/// <returns>true if succeeded</returns>
public bool TryGetPropertyValue(string name, out object value)
{
value = null;
int index = IndexOf(name);
if (index >= 0)
{
value = this.pairs[index].Value;
return true;
}
return false;
}
/// <summary>
/// Attempts to get property metadata
/// </summary>
/// <param name="name">property name</param>
/// <param name="value">propetty information</param>
/// <returns></returns>
public bool TryGetProperty(string name, out ISfcProperty property)
{
property = null;
int index = IndexOf(name);
if (index >= 0)
{
property = new Property(this.pairs[index]);
return true;
}
return false;
}
/// <summary>
/// Enumerates all properties
/// </summary>
/// <returns></returns>
public IEnumerable<ISfcProperty> EnumProperties()
{
foreach (NameValuePair pair in this.pairs)
{
yield return new Property(pair);
}
}
#endregion
#region ICollection implementation
public void CopyTo(Array array, int index)
{
Array.Copy(this.pairs.ToArray(), array, index);
}
public IEnumerator GetEnumerator()
{
// Existing code expects this to enumerate property names
return GetAllKeys().GetEnumerator();
}
public int Count
{
get { return this.pairs.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get { return null; }
}
#endregion
public override string ToString()
{
StringBuilder textBuilder = new StringBuilder();
foreach (NameValuePair pair in this.pairs)
{
if (textBuilder.Length > 0)
{
textBuilder.Append(", ");
}
textBuilder.AppendFormat("{0}={1}", pair.Name, pair.Value);
}
return textBuilder.ToString();
}
}
}

View File

@@ -47,6 +47,23 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
};
}
internal static UserInfo GetTestUserInfo(string loginName)
{
return new UserInfo()
{
Type = DatabaseUserType.UserWithLogin,
UserName = "TestUserName_" + new Random().NextInt64(10000000,90000000).ToString(),
LoginName = loginName,
Password = "placeholder",
DefaultSchema = "dbo",
OwnedSchemas = new string[] { "dbo" },
isEnabled = false,
isAAD = false,
ExtendedProperties = null,
SecurablePermissions = null
};
}
internal static CredentialInfo GetTestCredentialInfo()
{
return new CredentialInfo()

View File

@@ -0,0 +1,69 @@
//
// 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.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
using Microsoft.SqlTools.ServiceLayer.Security;
using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
using Microsoft.SqlTools.ServiceLayer.Test.Common;
using Moq;
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{
/// <summary>
/// Tests for the User management component
/// </summary>
public class UserTests
{
/// <summary>
/// Test the basic Create User method handler
/// </summary>
// [Test]
public async Task TestHandleCreateUserRequest()
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
{
// setup
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
var loginParams = new CreateLoginParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
Login = SecurityTestUtils.GetTestLoginInfo()
};
var createLoginContext = new Mock<RequestContext<CreateLoginResult>>();
createLoginContext.Setup(x => x.SendResult(It.IsAny<CreateLoginResult>()))
.Returns(Task.FromResult(new object()));
// call the create login method
SecurityService service = new SecurityService();
await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object);
// verify the result
createLoginContext.Verify(x => x.SendResult(It.Is<CreateLoginResult>
(p => p.Success && p.Login.LoginName != string.Empty)));
var userParams = new CreateUserParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.LoginName)
};
var createUserContext = new Mock<RequestContext<CreateUserResult>>();
createUserContext.Setup(x => x.SendResult(It.IsAny<CreateUserResult>()))
.Returns(Task.FromResult(new object()));
// call the create login method
await service.HandleCreateUserRequest(userParams, createUserContext.Object);
// verify the result
createUserContext.Verify(x => x.SendResult(It.Is<CreateUserResult>
(p => p.Success && p.User.UserName != string.Empty)));
}
}
}
}