mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-19 09:35:36 -05:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
|
||||
this.currentState.pageVerify = PageVerify.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
this.currentState.pageVerify = PageVerify.TornPageDetection;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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("<");
|
||||
break;
|
||||
case '>':
|
||||
sb.Append(">");
|
||||
break;
|
||||
case '&':
|
||||
sb.Append("&");
|
||||
break;
|
||||
default:
|
||||
sb.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
144
src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs
Normal file
144
src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
185
src/Microsoft.SqlTools.ServiceLayer/Utility/LanguageUtils.cs
Normal file
185
src/Microsoft.SqlTools.ServiceLayer/Utility/LanguageUtils.cs
Normal 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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user