From 07c8069cfac57ce8583b490cb595da70c3efbdf9 Mon Sep 17 00:00:00 2001 From: Sai Avishkar Sreerama <74571829+ssreerama@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:24:31 -0500 Subject: [PATCH] Enabling Files Tab to the database properties (#2169) * sending dsc values to ADS * modifying dsc method with unsupportable property IsValuedefault * getting the options and added a bool flag to maintian checkbox for secondary to save * sending data to ads * Ready for PR with minimal changes of loading UI as expected, TODO:saving logic * Excluding maxdop and resumable options from primary value conversion for 1/0's * Adding Id to the info, as we cannot depend on names, as names can be altered in future * saving successfully, todo-diff servers, script (secondary - primary compare and dont update),test, send null for unsupported * adding nullable dsc for unsupported servers * fixing script generation for some properties that are not touched. the generated script is unharmed but unnecessary here * adding test conditions for database scoped configurations * adding switch case method to get the values * Removing Loc string for the TSQL options * removing unnecessary using statement * sending required data, verify autogrowth... * using fullTextIndexing to open the files tab for sql server and not to other servers * Adding test case and fixing createDatabase issue * sending files as objecinfo * Update src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototype130.cs Co-authored-by: Charles Gagnon * comment update * preparing filegroup and filetype options * sending required all fields * saving file code changes, need more to work * Saving file is completed, todo:edit & remove * Logic to remove the file * add,edit,save working * cleaning merge conflicts accidentally added code * Adding tests to validates Files by adding, removing, updating files * adding comments * all working including tests, except fileStream size question * code review comments updates * memoryoptimized filegroups should be part of filestream group * failing tests fix * Modify tests by create database using SqlTestDb * Update src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs Co-authored-by: Cory Rivera * fixing test * commenting remove file testing as failing pipeline but passing locally * using enum for type * trying to fix the test in server, removed the complaining file from test * removing fulltext param and test fix * fixing the path.. * test fix * missing conflict resolving --------- Co-authored-by: Charles Gagnon Co-authored-by: Cory Rivera --- .../Admin/Database/CreateDatabaseObjects.cs | 19 +++ .../ObjectTypes/Database/DatabaseHandler.cs | 147 +++++++++++++++++- .../ObjectTypes/Database/DatabaseInfo.cs | 20 +++ .../ObjectTypes/Database/DatabaseViewInfo.cs | 12 +- .../Utility/DatabaseUtils.cs | 16 +- .../ObjectManagement/DatabaseHandlerTests.cs | 128 +++++++++++---- .../ObjectManagementTestUtils.cs | 23 +++ 7 files changed, 314 insertions(+), 51 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/CreateDatabaseObjects.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/CreateDatabaseObjects.cs index d894a272..e592acb8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/CreateDatabaseObjects.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/CreateDatabaseObjects.cs @@ -843,6 +843,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin private class FileData { + public int id; public string name; public string physicalName; public string folder; @@ -859,6 +860,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin /// public FileData(DatabasePrototype parent, FileType type) { + this.id = 0; this.name = String.Empty; this.physicalName = String.Empty; this.folder = String.Empty; @@ -879,6 +881,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { this.autogrowth = new Autogrowth(parent, file); this.name = file.Name; + this.id = file.ID; if (file.FileName.EndsWith(":", StringComparison.Ordinal)) { @@ -923,6 +926,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { this.autogrowth = new Autogrowth(parent, file); this.name = file.Name; + this.id = file.ID; try { @@ -960,6 +964,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin /// public FileData(FileData other) { + this.id = other.id; this.name = other.name; this.physicalName = other.physicalName; this.folder = other.folder; @@ -1001,6 +1006,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin #region properties + /// + /// The ID of the file + /// + public int ID + { + get { return this.currentState.id; } + + set + { + this.currentState.id = value; + this.database.NotifyObservers(); + } + } + /// /// The logical name of the file, without extension /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs index 2cf96c7f..0a19ae5c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs @@ -22,6 +22,7 @@ using System.IO; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters; using System.Collections.Specialized; using Microsoft.SqlTools.SqlCore.Utility; +using System.Collections.Concurrent; namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { @@ -39,10 +40,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement private static readonly Dictionary displayRecoveryModels = new Dictionary(); private static readonly Dictionary displayPageVerifyOptions = new Dictionary(); private static readonly Dictionary displayRestrictAccessOptions = new Dictionary(); + private static readonly ConcurrentDictionary displayFileTypes = new ConcurrentDictionary(); private static readonly Dictionary compatLevelEnums = new Dictionary(); private static readonly Dictionary containmentTypeEnums = new Dictionary(); private static readonly Dictionary recoveryModelEnums = new Dictionary(); + private static readonly Dictionary fileTypesEnums = new Dictionary(); internal static readonly string[] AzureEditionNames; internal static readonly string[] AzureBackupLevels; @@ -81,6 +84,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement displayRestrictAccessOptions.Add(DatabaseUserAccess.Single, SR.prototype_db_prop_restrictAccess_value_single); displayRestrictAccessOptions.Add(DatabaseUserAccess.Restricted, SR.prototype_db_prop_restrictAccess_value_restricted); + displayFileTypes.TryAdd(FileType.Data, SR.prototype_file_dataFile); + displayFileTypes.TryAdd(FileType.Log, SR.prototype_file_logFile); + displayFileTypes.TryAdd(FileType.FileStream, SR.prototype_file_filestreamFile); + DscOnOffOptions = new[]{ CommonConstants.DatabaseScopedConfigurations_Value_On, CommonConstants.DatabaseScopedConfigurations_Value_Off @@ -111,6 +118,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { recoveryModelEnums.Add(displayRecoveryModels[key], key); } + foreach (FileType key in displayFileTypes.Keys) + { + fileTypesEnums.Add(displayFileTypes[key], key); + } // Azure SLO info is invariant of server information, so set up static objects we can return later var editions = AzureSqlDbHelper.GetValidAzureEditionOptions(); @@ -203,6 +214,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { ((DatabaseInfo)databaseViewInfo.ObjectInfo).PageVerify = displayPageVerifyOptions[smoDatabase.PageVerify]; ((DatabaseInfo)databaseViewInfo.ObjectInfo).TargetRecoveryTimeInSec = smoDatabase.TargetRecoveryTime; + // Files tab is only supported in SQL Server, but files exists for all servers and used in detach database, cannot depend on files property to check the supportability + ((DatabaseInfo)databaseViewInfo.ObjectInfo).IsFilesTabSupported = true; } if (prototype is DatabasePrototype160) @@ -211,6 +224,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } } databaseScopedConfigurationsCollection = smoDatabase.IsSupportedObject() ? smoDatabase.DatabaseScopedConfigurations : null; + databaseViewInfo.FileTypesOptions = displayFileTypes.Values.ToArray(); + + // Get file groups names + GetFileGroupNames(smoDatabase, databaseViewInfo); } databaseViewInfo.DscOnOffOptions = DscOnOffOptions; databaseViewInfo.DscElevateOptions = DscElevateOptions; @@ -263,7 +280,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement var smoDatabase = dataContainer.SqlDialogSubject as Database; if (smoDatabase != null) { - databaseViewInfo.Files = GetDatabaseFiles(smoDatabase); + ((DatabaseInfo)databaseViewInfo.ObjectInfo).Files = GetDatabaseFiles(smoDatabase); } } } @@ -605,7 +622,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } } - if (database.Owner != null && database.Owner != SR.general_default && viewParams.IsNewObject) + if (database.Owner != null && database.Owner != SR.general_default) { prototype.Owner = database.Owner; } @@ -681,6 +698,61 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } } + if (!viewParams.IsNewObject && database.Files != null) + { + HashSet fileIdsToRemove = new HashSet(prototype.Files.Select(file => file.ID)); + foreach (var file in database.Files) + { + // Add a New file + if (file.Id == 0) + { + DatabaseFilePrototype newFile = new DatabaseFilePrototype(dataContainer, prototype, fileTypesEnums[file.Type]); + newFile.Name = file.Name; + newFile.InitialSize = (int)file.SizeInMb; + newFile.PhysicalName = file.FileNameWithExtension; + newFile.DatabaseFileType = fileTypesEnums[file.Type]; + newFile.Exists = false; + newFile.Autogrowth = GetAutogrowth(prototype, file); + if (!string.IsNullOrEmpty(file.Path.Trim())) + { + newFile.Folder = Utility.DatabaseUtils.ConvertToLocalMachinePath(Path.GetFullPath(file.Path)); + } + + // Log file do not support file groups + if (fileTypesEnums[file.Type] != FileType.Log) + { + FilegroupPrototype fileGroup = new FilegroupPrototype(prototype); + fileGroup.Name = file.FileGroup; + newFile.FileGroup = fileGroup; + } + + // Add newFile to the prototype files + prototype.Files.Add(newFile); + } + // Edit file properties: updating the existed files with modified data + else + { + var existedFile = prototype.Files.FirstOrDefault(x => x.ID == file.Id); + if (existedFile != null) + { + fileIdsToRemove.Remove(file.Id); + existedFile.Name = file.Name; + existedFile.InitialSize = (int)file.SizeInMb; + existedFile.Autogrowth = GetAutogrowth(prototype, file); + } + } + } + + // Remove the file + foreach (var currentFile in prototype.Files) + { + if (fileIdsToRemove.Contains(currentFile.ID)) + { + currentFile.Removed = true; + } + } + } + // AutoCreateStatisticsIncremental can only be set when AutoCreateStatistics is enabled prototype.AutoCreateStatisticsIncremental = database.AutoCreateIncrementalStatistics; prototype.AutoCreateStatistics = database.AutoCreateStatistics; @@ -738,6 +810,20 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } } + private Autogrowth GetAutogrowth(DatabasePrototype prototype, DatabaseFile file) + { + Autogrowth fileAutogrowth = new Autogrowth(prototype); + fileAutogrowth.IsEnabled = file.IsAutoGrowthEnabled; + bool isGrowthInPercent = file.AutoFileGrowthType == FileGrowthType.Percent; + fileAutogrowth.IsGrowthInPercent = isGrowthInPercent; + fileAutogrowth.GrowthInPercent = isGrowthInPercent ? (int)file.AutoFileGrowth : fileAutogrowth.GrowthInPercent; + fileAutogrowth.GrowthInMegabytes = !isGrowthInPercent ? (int)file.AutoFileGrowth : fileAutogrowth.GrowthInMegabytes; + fileAutogrowth.MaximumFileSizeInMegabytes = (int)((0.0 <= file.MaxSizeLimitInMb) ? file.MaxSizeLimitInMb : 0.0); + fileAutogrowth.IsGrowthRestricted = file.MaxSizeLimitInMb > 0.0; + + return fileAutogrowth; + } + /// /// Get supported database collations for this server. /// @@ -898,10 +984,17 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { filesList.Add(new DatabaseFile() { + Id = file.ID, Name = file.Name, - Type = FileType.Data.ToString(), - Path = Path.GetDirectoryName(file.FileName), - FileGroup = fileGroup.Name + Type = file.Parent.FileGroupType == FileGroupType.RowsFileGroup ? displayFileTypes[FileType.Data] : displayFileTypes[FileType.FileStream], + Path = Utility.DatabaseUtils.ConvertToLocalMachinePath(Path.GetDirectoryName(file.FileName)), + FileGroup = fileGroup.Name, + FileNameWithExtension = Path.GetFileName(file.FileName), + SizeInMb = ByteConverter.ConvertKbtoMb(file.Size), + AutoFileGrowth = file.GrowthType == FileGrowthType.Percent ? file.Growth : ByteConverter.ConvertKbtoMb(file.Growth), + AutoFileGrowthType = file.GrowthType, + MaxSizeLimitInMb = file.MaxSize == -1 ? file.MaxSize : ByteConverter.ConvertKbtoMb(file.MaxSize), + IsAutoGrowthEnabled = file.GrowthType != FileGrowthType.None, }); } } @@ -909,15 +1002,53 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { filesList.Add(new DatabaseFile() { + Id = file.ID, Name = file.Name, - Type = FileType.Log.ToString(), - Path = Path.GetDirectoryName(file.FileName), - FileGroup = string.Empty + Type = displayFileTypes[FileType.Log], + Path = Utility.DatabaseUtils.ConvertToLocalMachinePath(Path.GetDirectoryName(file.FileName)), + FileGroup = SR.prototype_file_noFileGroup, + FileNameWithExtension = Path.GetFileName(file.FileName), + SizeInMb = ByteConverter.ConvertKbtoMb(file.Size), + AutoFileGrowth = file.GrowthType == FileGrowthType.Percent ? file.Growth : ByteConverter.ConvertKbtoMb(file.Growth), + AutoFileGrowthType = file.GrowthType, + MaxSizeLimitInMb = file.MaxSize == -1 ? file.MaxSize : ByteConverter.ConvertKbtoMb(file.MaxSize), + IsAutoGrowthEnabled = file.GrowthType != FileGrowthType.None }); } return filesList.ToArray(); } + + /// + /// Get the file group names from the database fileGroup + /// + /// smo database prototype + /// database view info object + private void GetFileGroupNames(Database database, DatabaseViewInfo databaseViewInfo) + { + var rowDataGroups = new List(); + var fileStreamDataGroups = new List(); + foreach (FileGroup fileGroup in database.FileGroups) + { + if (fileGroup.FileGroupType == FileGroupType.FileStreamDataFileGroup || fileGroup.FileGroupType == FileGroupType.MemoryOptimizedDataFileGroup) + { + fileStreamDataGroups.Add(fileGroup.Name); + } + else + { + rowDataGroups.Add(fileGroup.Name); + } + } + + // If no fileStream groups available + if (fileStreamDataGroups.Count == 0) + { + fileStreamDataGroups.Add(SR.prototype_file_noApplicableFileGroup); + } + databaseViewInfo.RowDataFileGroupsOptions = rowDataGroups.ToArray(); + databaseViewInfo.FileStreamFileGroupsOptions = fileStreamDataGroups.ToArray(); + } + /// /// Get supported database compatibility levels for this Azure server. /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs index 28e39ebb..08d3c335 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs @@ -3,6 +3,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.SqlServer.Management.Smo; + namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// @@ -40,6 +42,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public bool EncryptionEnabled { get; set; } public string? RestrictAccess { get; set; } public DatabaseScopedConfigurationsInfo[]? DatabaseScopedConfigurations { get; set; } + public bool? IsFilesTabSupported { get; set; } + public DatabaseFile[] Files { get; set; } } public class DatabaseScopedConfigurationsInfo @@ -49,4 +53,20 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public string ValueForPrimary { get; set; } public string ValueForSecondary { get; set; } } + + public class DatabaseFile + { + public int Id { get; set; } + public string Name { get; set; } + public string Type { get; set; } + public string Path { get; set; } + public string FileGroup { get; set; } + public string FileNameWithExtension { get; set; } + public double SizeInMb { get; set; } + public bool IsAutoGrowthEnabled { get; set; } + public double AutoFileGrowth { get; set; } + public FileGrowthType AutoFileGrowthType { get; set; } + public double MaxSizeLimitInMb { get; set; } + } + } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs index f8a36751..1d81772a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs @@ -14,7 +14,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public OptionsCollection CompatibilityLevels { get; set; } public OptionsCollection ContainmentTypes { get; set; } public OptionsCollection RecoveryModels { get; set; } - public DatabaseFile[] Files { get; set; } public bool IsAzureDB { get; set; } public bool IsManagedInstance { get; set; } @@ -28,6 +27,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public string[] DscOnOffOptions { get; set; } public string[] DscElevateOptions { get; set; } public string[] DscEnableDisableOptions { get; set; } + public string[] RowDataFileGroupsOptions { get; set; } + public string[] FileStreamFileGroupsOptions { get; set; } + public string[] FileTypesOptions { get; set; } } public class AzureEditionDetails @@ -36,14 +38,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public OptionsCollection EditionOptions { get; set; } } - public class DatabaseFile - { - public string Name { get; set; } - public string Type { get; set; } - public string Path { get; set; } - public string FileGroup { get; set; } - } - public class OptionsCollection { public string[] Options { get; set; } public int DefaultValueIndex { get; set; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs index 41511fa4..d84d5eda 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs @@ -112,7 +112,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility return false; } } - return true; + return true; } finally { @@ -346,5 +346,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility return new string(nameChars); } private static readonly HashSet illegalFilenameCharacters = new HashSet(new char[] { '\\', '/', ':', '*', '?', '"', '<', '>', '|' }); + + + /// + /// Converts path to local path with DirectorySeparatorChar + /// + /// + /// path with local directory separator + public static string ConvertToLocalMachinePath(string filePath) + { + string pathSeparator = Path.DirectorySeparatorChar.ToString(); + string localPath = filePath.Replace("/", pathSeparator); + localPath = localPath.Replace("\\", pathSeparator); + return localPath; + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs index 4ba271a4..5580dbdb 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs @@ -21,6 +21,7 @@ using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; using Microsoft.SqlTools.ServiceLayer.Test.Common; using NUnit.Framework; using static Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper; +using DatabaseFile = Microsoft.SqlTools.ServiceLayer.ObjectManagement.DatabaseFile; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement { @@ -305,6 +306,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).RestrictAccess, Is.EqualTo(testDatabase.RestrictAccess), $"RestrictAccess should match with testdata"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations, Is.Not.Null, $"DatabaseScopedConfigurations is not null"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations.Count, Is.GreaterThan(0), $"DatabaseScopedConfigurations should have at least a+ few properties"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files.Count, Is.EqualTo(2), $"Create database should create two database files"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[0].Type, Is.EqualTo("ROWS Data"), $"Database files first file should be Row type database files"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[1].Type, Is.EqualTo("LOG"), $"Database files first file should be Log type database files"); // cleanup await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn, throwIfNotExist: true); @@ -325,58 +329,116 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement [Test] public async Task VerifyDatabaseScopedConfigurationsTest() { - // setup, drop database if exists. - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", serverType: TestServerType.OnPrem); - using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo)) + using (var testDatabase = await SqlTestDb.CreateNewAsync(serverType: TestServerType.OnPrem, dbNamePrefix: "VerifyDatabaseFilesTest")) { - 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 + var connectionResult = LiveConnectionHelper.InitLiveConnectionInfo(testDatabase.DatabaseName); + using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo)) { - // 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"); + var server = new Server(new ServerConnection(sqlConn)); + + var testDatabaseInfo = ObjectManagementTestUtils.GetTestDatabaseInfo(); + testDatabaseInfo.Name = testDatabase.DatabaseName; + testDatabaseInfo.CollationName = ""; + var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDatabase.DatabaseName); // 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); + var parametersForUpdate = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, testDatabase.DatabaseName, false, SqlObjectType.Database, "", objUrn); + DatabaseViewInfo databaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabaseInfo); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations, Is.Not.Null, $"DatabaseScopedConfigurations is not null"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations.Count, Is.GreaterThan(0), $"DatabaseScopedConfigurations should have at least a+ few properties"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForPrimary, Is.EqualTo("ON"), $"DatabaseScopedConfigurations primary value should match"); Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForSecondary, Is.EqualTo("ON"), $"DatabaseScopedConfigurations secondary value should match"); // Update few database scoped configurations - testDatabase.DatabaseScopedConfigurations = ((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations; - if (testDatabase.DatabaseScopedConfigurations.Length > 0) + testDatabaseInfo.DatabaseScopedConfigurations = ((DatabaseInfo)databaseViewInfo.ObjectInfo).DatabaseScopedConfigurations; + if (testDatabaseInfo.DatabaseScopedConfigurations.Length > 0) { // ACCELERATED_PLAN_FORCING - testDatabase.DatabaseScopedConfigurations[0].ValueForPrimary = "OFF"; - testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary = "OFF"; + testDatabaseInfo.DatabaseScopedConfigurations[0].ValueForPrimary = "OFF"; + testDatabaseInfo.DatabaseScopedConfigurations[0].ValueForSecondary = "OFF"; } - await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabase); - DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase); + await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabaseInfo); + DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabaseInfo); // verify the modified properties - Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForPrimary, Is.EqualTo(testDatabase.DatabaseScopedConfigurations[0].ValueForPrimary), $"DSC updated primary value should match"); - Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForSecondary, Is.EqualTo(testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary), $"DSC updated Secondary value should match"); - - // 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!); + Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForPrimary, Is.EqualTo(testDatabaseInfo.DatabaseScopedConfigurations[0].ValueForPrimary), $"DSC updated primary value should match"); + Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForSecondary, Is.EqualTo(testDatabaseInfo.DatabaseScopedConfigurations[0].ValueForSecondary), $"DSC updated Secondary value should match"); } } } + /// + /// Adding, modifying and deleting database files + /// + /// + [Test] + public async Task VerifyDatabaseFilesTest() + { + using (var testDatabase = await SqlTestDb.CreateNewAsync(serverType: TestServerType.OnPrem, dbNamePrefix: "VerifyDatabaseFilesTest")) + { + var connectionResult = LiveConnectionHelper.InitLiveConnectionInfo(testDatabase.DatabaseName); + using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo)) + { + var testDatabaseInfo = ObjectManagementTestUtils.GetTestDatabaseInfo(); + testDatabaseInfo.Name = testDatabase.DatabaseName; + testDatabaseInfo.CollationName = ""; + var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDatabase.DatabaseName); + + // Get database properties and verify + var parametersForUpdate = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, testDatabase.DatabaseName, false, SqlObjectType.Database, "", objUrn); + DatabaseViewInfo databaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabaseInfo); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files.Count, Is.EqualTo(2), $"Create database should create two database files"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[0].Type, Is.EqualTo("ROWS Data"), $"Database files first file should be Row type database files"); + Assert.That(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[1].Type, Is.EqualTo("LOG"), $"Database files first file should be Log type database files"); + + List databaseFile = new List(); + + // copy exisitng Row data files to the list + databaseFile.Add(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[0]); + databaseFile.Add(((DatabaseInfo)databaseViewInfo.ObjectInfo).Files[1]); + + // Update existing file + databaseFile[0].SizeInMb = 15; // size modified from 8 to 15 + databaseFile[0].AutoFileGrowth = 74; // size modified from 64 to 74 + + // Add new Files of Row and Log types + var testDatabaseFiles = ObjectManagementTestUtils.GetTestDatabaseFiles(); + databaseFile.AddRange(testDatabaseFiles); + + // Attaching the files to the testdatabase + testDatabaseInfo.Files = databaseFile.ToArray(); + + // Validate the result files + await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabaseInfo); + DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabaseInfo); + + // verify the modified properties + Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).Files.Count, Is.EqualTo(3), $"Three files should exists, as we modified one and added one new files"); + var file = ((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).Files.FirstOrDefault(x => x.Name == databaseFile[0].Name); + Assert.That(file, Is.Not.Null, $"Primary file should exists"); + Assert.That(file.SizeInMb, Is.EqualTo(15), $"Files updated value should match"); + Assert.That(file.AutoFileGrowth, Is.EqualTo(74), $"Files updated value should match"); + + // Validate newly created files + file = ((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).Files.FirstOrDefault(x => x.Name == testDatabaseFiles[0].Name); + Assert.That(file, Is.Not.Null, $"New file should be created"); + Assert.That(file, Is.Not.EqualTo(0), $"Newly created file should have an Id"); + + // Deleting newly created file + List newfiles = ((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).Files.ToList(); + var fileIndexTobeRemoved = newfiles.FindIndex(x => x.Name == testDatabaseFiles[0].Name); + newfiles.RemoveAt(fileIndexTobeRemoved); + testDatabaseInfo.Files = newfiles.ToArray(); + + // Validate the result files + await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabaseInfo); + DatabaseViewInfo databaseViewInfoAfterFileDelete = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabaseInfo); + Assert.That(((DatabaseInfo)databaseViewInfoAfterFileDelete.ObjectInfo).Files.Count, Is.EqualTo(2), $"Should have only 2 files as we removed one"); + } + } + } + + [Test] public async Task DetachDatabaseTest() { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs index bb8f938c..65bd577f 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs @@ -6,13 +6,16 @@ #nullable disable using System; +using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.ObjectManagement; using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; using Moq; using Newtonsoft.Json.Linq; +using DatabaseFile = Microsoft.SqlTools.ServiceLayer.ObjectManagement.DatabaseFile; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement { @@ -104,6 +107,26 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement }; } + internal static DatabaseFile[] GetTestDatabaseFiles() + { + List databaseFiles = new List() { + new DatabaseFile() { + Id = 0, + Name = "TestDatabaseName_File1_" + new Random().NextInt64(10000000, 90000000).ToString(), + Type = "LOG", + AutoFileGrowth = 100, + AutoFileGrowthType = FileGrowthType.KB, + FileGroup = "Not Applicable", + FileNameWithExtension = "", + SizeInMb = 10, + IsAutoGrowthEnabled = true, + MaxSizeLimitInMb = -1, + Path = "" + } + }; + return databaseFiles.ToArray(); + } + internal static UserInfo GetTestUserInfo(DatabaseUserType userType, string userName = null, string loginName = null) { return new UserInfo()