mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-18 01:25:41 -05:00
Add Attach Database functionality to Object Management Service (#2193)
This commit is contained in:
@@ -3,9 +3,12 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
@@ -356,7 +359,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
|
||||
testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary = "OFF";
|
||||
}
|
||||
await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabase);
|
||||
DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase);
|
||||
DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase);
|
||||
|
||||
// verify the modified properties
|
||||
Assert.That(((DatabaseInfo)updatedDatabaseViewInfo.ObjectInfo).DatabaseScopedConfigurations[0].ValueForPrimary, Is.EqualTo(testDatabase.DatabaseScopedConfigurations[0].ValueForPrimary), $"DSC updated primary value should match");
|
||||
@@ -500,6 +503,107 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task AttachDatabaseTest(bool generateScript)
|
||||
{
|
||||
using (SqlTestDb testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, nameof(AttachDatabaseTest)))
|
||||
{
|
||||
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", serverType: TestServerType.OnPrem);
|
||||
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo))
|
||||
{
|
||||
var serverConn = new ServerConnection(sqlConn);
|
||||
var server = new Server(serverConn);
|
||||
var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDb.DatabaseName);
|
||||
var database = server.GetSmoObject(objUrn) as Database;
|
||||
|
||||
var originalOwner = database!.Owner;
|
||||
var originalFilePaths = new List<string>();
|
||||
foreach (FileGroup group in database.FileGroups)
|
||||
{
|
||||
foreach (DataFile file in group.Files)
|
||||
{
|
||||
originalFilePaths.Add(file.FileName);
|
||||
}
|
||||
}
|
||||
foreach (LogFile file in database.LogFiles)
|
||||
{
|
||||
originalFilePaths.Add(file.FileName);
|
||||
}
|
||||
|
||||
// Detach database so that we can re-attach it with the database handler method.
|
||||
// Have to set database to single user mode to close active connections before detaching it.
|
||||
database.DatabaseOptions.UserAccess = SqlServer.Management.Smo.DatabaseUserAccess.Single;
|
||||
database.Alter(TerminationClause.RollbackTransactionsImmediately);
|
||||
server.DetachDatabase(testDb.DatabaseName, false);
|
||||
var dbExists = this.DatabaseExists(testDb.DatabaseName, server);
|
||||
Assert.That(dbExists, Is.False, "Database was not correctly detached before doing attach test.");
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new DatabaseHandler(ConnectionService.Instance);
|
||||
var attachParams = new AttachDatabaseRequestParams()
|
||||
{
|
||||
ConnectionUri = connectionResult.ConnectionInfo.OwnerUri,
|
||||
Databases = new DatabaseFileData[]
|
||||
{
|
||||
new DatabaseFileData()
|
||||
{
|
||||
Owner = originalOwner,
|
||||
DatabaseName = testDb.DatabaseName,
|
||||
DatabaseFilePaths = originalFilePaths.ToArray()
|
||||
}
|
||||
},
|
||||
GenerateScript = generateScript
|
||||
};
|
||||
var script = handler.Attach(attachParams);
|
||||
|
||||
if (generateScript)
|
||||
{
|
||||
dbExists = this.DatabaseExists(testDb.DatabaseName, server);
|
||||
Assert.That(dbExists, Is.False, "Should not have attached DB when only generating a script.");
|
||||
|
||||
var queryBuilder = new StringBuilder();
|
||||
queryBuilder.AppendLine("USE [master]");
|
||||
queryBuilder.AppendLine($"CREATE DATABASE [{testDb.DatabaseName}] ON ");
|
||||
|
||||
for (int i = 0; i < originalFilePaths.Count - 1; i++)
|
||||
{
|
||||
var file = originalFilePaths[i];
|
||||
queryBuilder.AppendLine($"( FILENAME = N'{file}' ),");
|
||||
}
|
||||
queryBuilder.AppendLine($"( FILENAME = N'{originalFilePaths[originalFilePaths.Count - 1]}' )");
|
||||
|
||||
queryBuilder.AppendLine(" FOR ATTACH");
|
||||
queryBuilder.AppendLine($"if exists (select name from master.sys.databases sd where name = N'{testDb.DatabaseName}' and SUSER_SNAME(sd.owner_sid) = SUSER_SNAME() ) EXEC [{testDb.DatabaseName}].dbo.sp_changedbowner @loginame=N'{originalOwner}', @map=false");
|
||||
|
||||
Assert.That(script, Is.EqualTo(queryBuilder.ToString()), "Did not get expected attach database script");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(script, Is.Empty, "Should not have generated a script for this Attach operation.");
|
||||
|
||||
server.Databases.Refresh();
|
||||
dbExists = this.DatabaseExists(testDb.DatabaseName, server);
|
||||
Assert.That(dbExists, "Database was not attached successfully");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbExists = this.DatabaseExists(testDb.DatabaseName, server);
|
||||
if (!dbExists)
|
||||
{
|
||||
// Reattach database so it can get dropped during cleanup
|
||||
var fileCollection = new StringCollection();
|
||||
originalFilePaths.ForEach(file => fileCollection.Add(file));
|
||||
server.AttachDatabase(testDb.DatabaseName, fileCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteDatabaseTest()
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
||||
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
|
||||
{
|
||||
public class UtilsTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetDataFolderTest()
|
||||
{
|
||||
using (SqlTestDb testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, nameof(GetDataFolderTest)))
|
||||
{
|
||||
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync(testDb.DatabaseName, serverType: TestServerType.OnPrem);
|
||||
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo))
|
||||
{
|
||||
var serverConn = new ServerConnection(sqlConn);
|
||||
var server = new Server(serverConn);
|
||||
var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDb.DatabaseName);
|
||||
var database = server.GetSmoObject(objUrn) as Database;
|
||||
|
||||
var dataFilePath = database.FileGroups[0].Files[0].FileName;
|
||||
var expectedDataFolder = Path.GetDirectoryName(dataFilePath).ToString();
|
||||
|
||||
var actualDataFolder = CommonUtilities.GetDefaultDataFolder(serverConn);
|
||||
actualDataFolder = Path.TrimEndingDirectorySeparator(actualDataFolder);
|
||||
Assert.That(actualDataFolder, Is.EqualTo(expectedDataFolder).IgnoreCase, "Did not get expected data file folder path.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetAssociatedFilesTest()
|
||||
{
|
||||
using (SqlTestDb testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, nameof(GetAssociatedFilesTest)))
|
||||
{
|
||||
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync(testDb.DatabaseName, serverType: TestServerType.OnPrem);
|
||||
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo))
|
||||
{
|
||||
var serverConn = new ServerConnection(sqlConn);
|
||||
var server = new Server(serverConn);
|
||||
var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDb.DatabaseName);
|
||||
var database = server.GetSmoObject(objUrn) as Database;
|
||||
|
||||
var expectedFilePaths = new List<string>();
|
||||
DataFile primaryFile = null;
|
||||
foreach (FileGroup group in database.FileGroups)
|
||||
{
|
||||
foreach (DataFile file in group.Files)
|
||||
{
|
||||
expectedFilePaths.Add(file.FileName);
|
||||
if (file.IsPrimaryFile)
|
||||
{
|
||||
primaryFile = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (LogFile file in database.LogFiles)
|
||||
{
|
||||
expectedFilePaths.Add(file.FileName);
|
||||
}
|
||||
|
||||
// Detach database so that we don't throw an error when trying to access the primary data file
|
||||
// Have to set database to single user mode to close active connections before detaching it.
|
||||
database.DatabaseOptions.UserAccess = SqlServer.Management.Smo.DatabaseUserAccess.Single;
|
||||
database.Alter(TerminationClause.RollbackTransactionsImmediately);
|
||||
server.DetachDatabase(testDb.DatabaseName, false);
|
||||
try
|
||||
{
|
||||
Assert.That(primaryFile, Is.Not.Null, "Could not find a primary file in the list of database files.");
|
||||
var actualFilePaths = CommonUtilities.GetAssociatedFilePaths(serverConn, primaryFile.FileName);
|
||||
Assert.That(actualFilePaths, Is.EqualTo(expectedFilePaths).IgnoreCase, "The list of associated files did not match the actual files for the database.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Reattach database so it can get dropped during cleanup
|
||||
var fileCollection = new StringCollection();
|
||||
expectedFilePaths.ForEach(file => fileCollection.Add(file));
|
||||
server.AttachDatabase(testDb.DatabaseName, fileCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ThrowErrorWhenDatabaseExistsTest()
|
||||
{
|
||||
using (SqlTestDb testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, nameof(ThrowErrorWhenDatabaseExistsTest)))
|
||||
{
|
||||
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync(testDb.DatabaseName, serverType: TestServerType.OnPrem);
|
||||
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo))
|
||||
{
|
||||
var serverConn = new ServerConnection(sqlConn);
|
||||
var server = new Server(serverConn);
|
||||
var objUrn = ObjectManagementTestUtils.GetDatabaseURN(testDb.DatabaseName);
|
||||
var database = server.GetSmoObject(objUrn) as Database;
|
||||
|
||||
DataFile primaryFile = null;
|
||||
foreach (FileGroup group in database.FileGroups)
|
||||
{
|
||||
foreach (DataFile file in group.Files)
|
||||
{
|
||||
if (file.IsPrimaryFile)
|
||||
{
|
||||
primaryFile = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(
|
||||
() => CommonUtilities.GetAssociatedFilePaths(serverConn, primaryFile.FileName),
|
||||
Throws.Exception,
|
||||
"Should throw an error when trying to open a database file that's already in use."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user