mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-07 01:25:41 -05:00
Enabling database properties general tab with real time values from SMO (#2093)
* initial commit with all required db handler and props, also getting the data from ADS * database properties view updated * Delete Microsoft.SqlTools.ServiceLayer.sln This file should be ignored * Removed unwanted file * Using DatabaseHandler for properties as one handler per object * removed unused and unnecessary changes * minimal updates * moving type conversion to UI side, properties with original types. * conversion number fixed * Adding Localized strings * using existing objectUrn logic to get the smo object * Adding Integration tests for database properties verification * refactoring * updating test
This commit is contained in:
committed by
GitHub
parent
532f7b0912
commit
5c7dae40e6
@@ -12429,6 +12429,14 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string databaseBackupDate_None
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.databaseBackupDate_None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string BasicAzureEdition
|
public static string BasicAzureEdition
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -17847,6 +17855,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
public const string prototype_file_noApplicableFileGroup = "prototype_file_noApplicableFileGroup";
|
public const string prototype_file_noApplicableFileGroup = "prototype_file_noApplicableFileGroup";
|
||||||
|
|
||||||
|
|
||||||
|
public const string databaseBackupDate_None = "databaseBackupDate_None";
|
||||||
|
|
||||||
|
|
||||||
public const string BasicAzureEdition = "BasicAzureEdition";
|
public const string BasicAzureEdition = "BasicAzureEdition";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6756,6 +6756,10 @@ The Query Processor estimates that implementing the following index could improv
|
|||||||
<value>No Applicable Filegroup</value>
|
<value>No Applicable Filegroup</value>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="databaseBackupDate_None" xml:space="preserve">
|
||||||
|
<value>None</value>
|
||||||
|
<comment></comment>
|
||||||
|
</data>
|
||||||
<data name="BasicAzureEdition" xml:space="preserve">
|
<data name="BasicAzureEdition" xml:space="preserve">
|
||||||
<value>Basic</value>
|
<value>Basic</value>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
|
|||||||
@@ -2800,6 +2800,7 @@ general_containmentType_None = None
|
|||||||
general_containmentType_Partial = Partial
|
general_containmentType_Partial = Partial
|
||||||
filegroups_filestreamFiles = FILESTREAM Files
|
filegroups_filestreamFiles = FILESTREAM Files
|
||||||
prototype_file_noApplicableFileGroup = No Applicable Filegroup
|
prototype_file_noApplicableFileGroup = No Applicable Filegroup
|
||||||
|
databaseBackupDate_None = None
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
# Azure SQL DB
|
# Azure SQL DB
|
||||||
|
|||||||
@@ -8316,6 +8316,11 @@ The Query Processor estimates that implementing the following index could improv
|
|||||||
<target state="new">Hyperscale</target>
|
<target state="new">Hyperscale</target>
|
||||||
<note></note>
|
<note></note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="databaseBackupDate_None">
|
||||||
|
<source>None</source>
|
||||||
|
<target state="new">None</target>
|
||||||
|
<note></note>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
@@ -98,7 +98,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
|
|||||||
public override Task<InitializeViewResult> InitializeObjectView(InitializeViewRequestParams requestParams)
|
public override Task<InitializeViewResult> InitializeObjectView(InitializeViewRequestParams requestParams)
|
||||||
{
|
{
|
||||||
// create a default data context and database object
|
// create a default data context and database object
|
||||||
using (var dataContainer = CreateDatabaseDataContainer(requestParams.ConnectionUri, ConfigAction.Create))
|
ConfigAction configAction = requestParams.IsNewObject ? ConfigAction.Create : ConfigAction.Update;
|
||||||
|
var databaseInfo = !requestParams.IsNewObject ? new DatabaseInfo() { Name = requestParams.Database } : null;
|
||||||
|
using (var dataContainer = CreateDatabaseDataContainer(requestParams, configAction, databaseInfo))
|
||||||
{
|
{
|
||||||
if (dataContainer.Server == null)
|
if (dataContainer.Server == null)
|
||||||
{
|
{
|
||||||
@@ -120,6 +122,27 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
|
|||||||
IsAzureDB = isAzureDB
|
IsAzureDB = isAzureDB
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Collect the Database properties information
|
||||||
|
if (!requestParams.IsNewObject)
|
||||||
|
{
|
||||||
|
var smoDatabase = dataContainer.SqlDialogSubject as Database;
|
||||||
|
databaseViewInfo.ObjectInfo = new DatabaseInfo()
|
||||||
|
{
|
||||||
|
Name = smoDatabase.Name,
|
||||||
|
CollationName = smoDatabase.Collation,
|
||||||
|
DateCreated = smoDatabase.CreateDate.ToString(),
|
||||||
|
LastDatabaseBackup = smoDatabase.LastBackupDate == DateTime.MinValue ? SR.databaseBackupDate_None : smoDatabase.LastBackupDate.ToString(),
|
||||||
|
LastDatabaseLogBackup = smoDatabase.LastLogBackupDate == DateTime.MinValue ? SR.databaseBackupDate_None : smoDatabase.LastLogBackupDate.ToString(),
|
||||||
|
MemoryAllocatedToMemoryOptimizedObjectsInMb = DatabaseUtils.ConvertKbtoMb(smoDatabase.MemoryAllocatedToMemoryOptimizedObjectsInKB),
|
||||||
|
MemoryUsedByMemoryOptimizedObjectsInMb = DatabaseUtils.ConvertKbtoMb(smoDatabase.MemoryUsedByMemoryOptimizedObjectsInKB),
|
||||||
|
NumberOfUsers = smoDatabase.Users.Count,
|
||||||
|
Owner = smoDatabase.Owner,
|
||||||
|
SizeInMb = smoDatabase.Size,
|
||||||
|
SpaceAvailableInMb = DatabaseUtils.ConvertKbtoMb(smoDatabase.SpaceAvailable),
|
||||||
|
Status = smoDatabase.Status.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// azure sql db doesn't have a sysadmin fixed role
|
// azure sql db doesn't have a sysadmin fixed role
|
||||||
var compatibilityLevelEnabled = !isDw && (dataContainer.LoggedInUserIsSysadmin || isAzureDB);
|
var compatibilityLevelEnabled = !isDw && (dataContainer.LoggedInUserIsSysadmin || isAzureDB);
|
||||||
if (isAzureDB)
|
if (isAzureDB)
|
||||||
@@ -200,7 +223,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
|
|||||||
public override Task Save(DatabaseViewContext context, DatabaseInfo obj)
|
public override Task Save(DatabaseViewContext context, DatabaseInfo obj)
|
||||||
{
|
{
|
||||||
ConfigureDatabase(
|
ConfigureDatabase(
|
||||||
context.Parameters.ConnectionUri,
|
context.Parameters,
|
||||||
obj,
|
obj,
|
||||||
context.Parameters.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
|
context.Parameters.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
|
||||||
RunType.RunNow);
|
RunType.RunNow);
|
||||||
@@ -210,39 +233,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
|
|||||||
public override Task<string> Script(DatabaseViewContext context, DatabaseInfo obj)
|
public override Task<string> Script(DatabaseViewContext context, DatabaseInfo obj)
|
||||||
{
|
{
|
||||||
var script = ConfigureDatabase(
|
var script = ConfigureDatabase(
|
||||||
context.Parameters.ConnectionUri,
|
context.Parameters,
|
||||||
obj,
|
obj,
|
||||||
context.Parameters.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
|
context.Parameters.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
|
||||||
RunType.ScriptToWindow);
|
RunType.ScriptToWindow);
|
||||||
return Task.FromResult(script);
|
return Task.FromResult(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CDataContainer CreateDatabaseDataContainer(string connectionUri, ConfigAction configAction, DatabaseInfo? database = null)
|
private CDataContainer CreateDatabaseDataContainer(InitializeViewRequestParams requestParams, ConfigAction configAction, DatabaseInfo? database = null)
|
||||||
{
|
{
|
||||||
ConnectionInfo connectionInfo = this.GetConnectionInfo(connectionUri);
|
ConnectionInfo connectionInfo = this.GetConnectionInfo(requestParams.ConnectionUri);
|
||||||
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connectionInfo, databaseExists: configAction != ConfigAction.Create);
|
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connectionInfo, databaseExists: configAction != ConfigAction.Create);
|
||||||
if (dataContainer.Server == null)
|
if (dataContainer.Server == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(serverNotExistsError);
|
throw new InvalidOperationException(serverNotExistsError);
|
||||||
}
|
}
|
||||||
string objectUrn = (configAction != ConfigAction.Create && database != null)
|
string objectUrn = configAction != ConfigAction.Create && database != null
|
||||||
? string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
? string.Format(System.Globalization.CultureInfo.InvariantCulture, "Server/Database[@Name='{0}']", Urn.EscapeString(database.Name))
|
||||||
"Server/Database[@Name='{0}']",
|
: string.Format(System.Globalization.CultureInfo.InvariantCulture, "Server");
|
||||||
Urn.EscapeString(database.Name))
|
|
||||||
: string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
|
||||||
"Server");
|
|
||||||
dataContainer.SqlDialogSubject = dataContainer.Server.GetSmoObject(objectUrn);
|
dataContainer.SqlDialogSubject = dataContainer.Server.GetSmoObject(objectUrn);
|
||||||
return dataContainer;
|
return dataContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ConfigureDatabase(string connectionUri, DatabaseInfo database, ConfigAction configAction, RunType runType)
|
private string ConfigureDatabase(InitializeViewRequestParams requestParams, DatabaseInfo database, ConfigAction configAction, RunType runType)
|
||||||
{
|
{
|
||||||
if (database.Name == null)
|
if (database.Name == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Database name not provided.");
|
throw new ArgumentException("Database name not provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var dataContainer = CreateDatabaseDataContainer(connectionUri, configAction, database))
|
using (var dataContainer = CreateDatabaseDataContainer(requestParams, configAction, database))
|
||||||
{
|
{
|
||||||
if (dataContainer.Server == null)
|
if (dataContainer.Server == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
|
|||||||
public string? RecoveryModel { get; set; }
|
public string? RecoveryModel { get; set; }
|
||||||
public string? CompatibilityLevel { get; set; }
|
public string? CompatibilityLevel { get; set; }
|
||||||
public string? ContainmentType { get; set; }
|
public string? ContainmentType { get; set; }
|
||||||
|
public string? DateCreated { get; set; }
|
||||||
|
public string? LastDatabaseBackup { get; set; }
|
||||||
|
public string? LastDatabaseLogBackup { get; set; }
|
||||||
|
public double? MemoryAllocatedToMemoryOptimizedObjectsInMb { get; set; }
|
||||||
|
public double? MemoryUsedByMemoryOptimizedObjectsInMb { get; set; }
|
||||||
|
public int? NumberOfUsers { get; set; }
|
||||||
|
public double? SizeInMb { get; set; }
|
||||||
|
public double? SpaceAvailableInMb { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
public string? AzureBackupRedundancyLevel { get; set; }
|
public string? AzureBackupRedundancyLevel { get; set; }
|
||||||
public string? AzureServiceLevelObjective { get; set; }
|
public string? AzureServiceLevelObjective { get; set; }
|
||||||
public string? AzureEdition { get; set; }
|
public string? AzureEdition { get; set; }
|
||||||
|
|||||||
@@ -345,5 +345,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
|||||||
return new string(nameChars);
|
return new string(nameChars);
|
||||||
}
|
}
|
||||||
private static readonly HashSet<char> illegalFilenameCharacters = new HashSet<char>(new char[] { '\\', '/', ':', '*', '?', '"', '<', '>', '|' });
|
private static readonly HashSet<char> illegalFilenameCharacters = new HashSet<char>(new char[] { '\\', '/', ':', '*', '?', '"', '<', '>', '|' });
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts value in KBs to MBs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="valueInKb">value in kilo bytes</param>
|
||||||
|
/// <returns>Returns as double type</returns>
|
||||||
|
public static double ConvertKbtoMb(double valueInKb)
|
||||||
|
{
|
||||||
|
return (Math.Round(valueInKb / 1024, 2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,50 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
/// This test validates the newly created database properties and verifies with some default values
|
||||||
|
public async Task VerifyDatabasePropertiesTest()
|
||||||
|
{
|
||||||
|
// setup, drop database if exists.
|
||||||
|
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", serverType: TestServerType.OnPrem);
|
||||||
|
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo))
|
||||||
|
{
|
||||||
|
var server = new Server(new ServerConnection(sqlConn));
|
||||||
|
|
||||||
|
var testDatabase = ObjectManagementTestUtils.GetTestDatabaseInfo();
|
||||||
|
var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDatabase.Name);
|
||||||
|
await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// create database
|
||||||
|
var parametersForCreation = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, "master", true, SqlObjectType.Database, "", "");
|
||||||
|
await ObjectManagementTestUtils.SaveObject(parametersForCreation, testDatabase);
|
||||||
|
Assert.That(DatabaseExists(testDatabase.Name!, server), $"Expected database '{testDatabase.Name}' was not created succesfully");
|
||||||
|
|
||||||
|
// Get database properties and verify
|
||||||
|
var parametersForUpdate = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, testDatabase.Name, false, SqlObjectType.Database, "", objUrn);
|
||||||
|
DatabaseViewInfo databaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase);
|
||||||
|
Assert.That(databaseViewInfo.ObjectInfo, Is.Not.Null, $"Expected result should not be empty");
|
||||||
|
Assert.That(databaseViewInfo.ObjectInfo.Name, Is.EqualTo(testDatabase.Name), $"database name should be matched");
|
||||||
|
Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DateCreated, Is.Not.Null, $"database name should be matched");
|
||||||
|
Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).NumberOfUsers, Is.GreaterThan(0), $"Default database users count should not be zero");
|
||||||
|
Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).LastDatabaseBackup, Is.EqualTo(testDatabase.LastDatabaseBackup), $"Should have no database last backup date");
|
||||||
|
Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).LastDatabaseLogBackup, Is.EqualTo(testDatabase.LastDatabaseLogBackup), $"Should have no database backup log date");
|
||||||
|
Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).SizeInMb, Is.GreaterThan(0), $"Should have default database size when created");
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn, throwIfNotExist: true);
|
||||||
|
Assert.That(DatabaseExists(testDatabase.Name!, server), Is.False, $"Database '{testDatabase.Name}' was not dropped succesfully");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup using SMO if Drop didn't work
|
||||||
|
DropDatabase(server, testDatabase.Name!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool DatabaseExists(string dbName, Server server)
|
private bool DatabaseExists(string dbName, Server server)
|
||||||
{
|
{
|
||||||
server.Databases.Refresh();
|
server.Databases.Refresh();
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
|
|||||||
CollationName = "SQL_Latin1_General_CP1_CI_AS",
|
CollationName = "SQL_Latin1_General_CP1_CI_AS",
|
||||||
CompatibilityLevel = "SQL Server 2022 (160)",
|
CompatibilityLevel = "SQL Server 2022 (160)",
|
||||||
ContainmentType = "None",
|
ContainmentType = "None",
|
||||||
RecoveryModel = "Full"
|
RecoveryModel = "Full",
|
||||||
|
LastDatabaseBackup = "None",
|
||||||
|
LastDatabaseLogBackup = "None"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +154,26 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
|
|||||||
await Service.HandleDisposeViewRequest(new DisposeViewRequestParams { ContextId = parameters.ContextId }, disposeViewRequestContext.Object);
|
await Service.HandleDisposeViewRequest(new DisposeViewRequestParams { ContextId = parameters.ContextId }, disposeViewRequestContext.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static async Task<DatabaseViewInfo> GetDatabaseObject(InitializeViewRequestParams parameters, SqlObject obj)
|
||||||
|
{
|
||||||
|
// Initialize the view
|
||||||
|
DatabaseViewInfo databaseViewInfo = new DatabaseViewInfo();
|
||||||
|
var initViewRequestContext = new Mock<RequestContext<SqlObjectViewInfo>>();
|
||||||
|
initViewRequestContext
|
||||||
|
.Setup(x => x.SendResult(It.IsAny<SqlObjectViewInfo>()))
|
||||||
|
.Returns(Task.FromResult<SqlObjectViewInfo>(null))
|
||||||
|
.Callback<DatabaseViewInfo>(r => databaseViewInfo = r);
|
||||||
|
await Service.HandleInitializeViewRequest(parameters, initViewRequestContext.Object);
|
||||||
|
|
||||||
|
// Dispose the view
|
||||||
|
var disposeViewRequestContext = new Mock<RequestContext<DisposeViewRequestResponse>>();
|
||||||
|
disposeViewRequestContext.Setup(x => x.SendResult(It.IsAny<DisposeViewRequestResponse>()))
|
||||||
|
.Returns(Task.FromResult<DisposeViewRequestResponse>(new DisposeViewRequestResponse()));
|
||||||
|
await Service.HandleDisposeViewRequest(new DisposeViewRequestParams { ContextId = parameters.ContextId }, disposeViewRequestContext.Object);
|
||||||
|
|
||||||
|
return databaseViewInfo;
|
||||||
|
}
|
||||||
|
|
||||||
internal static async Task<string> ScriptObject(InitializeViewRequestParams parameters, SqlObject obj)
|
internal static async Task<string> ScriptObject(InitializeViewRequestParams parameters, SqlObject obj)
|
||||||
{
|
{
|
||||||
// Initialize the view
|
// Initialize the view
|
||||||
|
|||||||
Reference in New Issue
Block a user