Add Attach Database functionality to Object Management Service (#2193)

This commit is contained in:
Cory Rivera
2023-08-28 12:24:02 -07:00
committed by GitHub
parent 766f68551e
commit 52773bc26d
9 changed files with 638 additions and 99 deletions

View File

@@ -8,10 +8,13 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Management; using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility;
@@ -71,6 +74,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
serviceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true); serviceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true);
serviceHost.SetRequestHandler(DefaultDatabaseInfoRequest.Type, HandleDefaultDatabaseInfoRequest, true); serviceHost.SetRequestHandler(DefaultDatabaseInfoRequest.Type, HandleDefaultDatabaseInfoRequest, true);
serviceHost.SetRequestHandler(GetDatabaseInfoRequest.Type, HandleGetDatabaseInfoRequest, true); serviceHost.SetRequestHandler(GetDatabaseInfoRequest.Type, HandleGetDatabaseInfoRequest, true);
serviceHost.SetRequestHandler(GetDataFolderRequest.Type, HandleGetDataFolderRequest, true);
serviceHost.SetRequestHandler(GetAssociatedFilesRequest.Type, HandleGetAssociatedFilesRequest, true);
} }
/// <summary> /// <summary>
@@ -153,6 +158,74 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin
}); });
} }
/// <summary>
/// Handle get database info request
/// </summary>
internal static async Task HandleGetDataFolderRequest(
GetDataFolderParams databaseParams,
RequestContext<string> requestContext)
{
Func<Task> requestHandler = async () =>
{
ConnectionInfo connInfo;
AdminService.ConnectionServiceInstance.TryFindConnection(
databaseParams.ConnectionUri,
out connInfo);
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo))
{
// Connection gets disconnected when backup is done
ServerConnection serverConnection = new ServerConnection(sqlConn);
var dataFolder = CommonUtilities.GetDefaultDataFolder(serverConnection);
await requestContext.SendResult(dataFolder);
}
};
Task task = Task.Run(async () => await requestHandler()).ContinueWithOnFaulted(async t =>
{
// Get innermost exception to get original error message
Exception ex = t.Exception;
while (ex.InnerException != null)
{
ex = ex.InnerException;
};
await requestContext.SendError(ex.Message);
});
}
/// <summary>
/// Handle get associated database files request
/// </summary>
internal static async Task HandleGetAssociatedFilesRequest(
GetAssociatedFilesParams databaseParams,
RequestContext<string[]> requestContext)
{
Func<Task> requestHandler = async () =>
{
ConnectionInfo connInfo;
AdminService.ConnectionServiceInstance.TryFindConnection(
databaseParams.ConnectionUri,
out connInfo);
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo))
{
// Connection gets disconnected when backup is done
ServerConnection serverConnection = new ServerConnection(sqlConn);
var files = CommonUtilities.GetAssociatedFilePaths(serverConnection, databaseParams.PrimaryFilePath);
await requestContext.SendResult(files);
}
};
Task task = Task.Run(async () => await requestHandler()).ContinueWithOnFaulted(async t =>
{
// Get innermost exception to get original error message
Exception ex = t.Exception;
while (ex.InnerException != null)
{
ex = ex.InnerException;
};
await requestContext.SendError(ex.Message);
});
}
/// <summary> /// <summary>
/// Return database info for a specific database /// Return database info for a specific database
/// </summary> /// </summary>

View File

@@ -0,0 +1,36 @@
//
// 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 Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Admin.Contracts
{
/// <summary>
/// Params for a get database info request
/// </summar>
public class GetAssociatedFilesParams
{
/// <summary>
/// URI identifier for the connection to the target server.
/// </summary>
public string ConnectionUri { get; set; }
/// <summary>
/// The file path for the primary file that we want to get the associated files for.
/// </summary>
public string PrimaryFilePath { get; set; }
}
/// <summary>
/// Get database info request mapping
/// </summary>
public class GetAssociatedFilesRequest
{
public static readonly
RequestType<GetAssociatedFilesParams, string[]> Type =
RequestType<GetAssociatedFilesParams, string[]>.Create("admin/getassociatedfiles");
}
}

View File

@@ -0,0 +1,32 @@
//
// 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 Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Admin.Contracts
{
/// <summary>
/// Params for a get database info request
/// </summar>
public class GetDataFolderParams
{
/// <summary>
/// URI identifier for the connection to get the server folder info for
/// </summary>
public string ConnectionUri { get; set; }
}
/// <summary>
/// Get database info request mapping
/// </summary>
public class GetDataFolderRequest
{
public static readonly
RequestType<GetDataFolderParams, string> Type =
RequestType<GetDataFolderParams, string>.Create("admin/getdatafolder");
}
}

View File

@@ -14,6 +14,7 @@ using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
using SMO = Microsoft.SqlServer.Management.Smo; using SMO = Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Management; using Microsoft.SqlTools.ServiceLayer.Management;
using System.IO;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
@@ -35,13 +36,13 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
Database, Database,
Files Files
} }
/// <summary> /// <summary>
/// Backup set type /// Backup set type
/// </summary> /// </summary>
public enum BackupsetType public enum BackupsetType
{ {
BackupsetDatabase, BackupsetDatabase,
BackupsetLog, BackupsetLog,
BackupsetDifferential, BackupsetDifferential,
BackupsetFiles BackupsetFiles
@@ -101,7 +102,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
else else
{ {
return (RestoreItemLocation+RestoreItemDeviceType.ToString() + IsLogicalDevice.ToString()).GetHashCode(); return (RestoreItemLocation + RestoreItemDeviceType.ToString() + IsLogicalDevice.ToString()).GetHashCode();
} }
} }
} }
@@ -136,17 +137,17 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// <param name="dataContainer"></param> /// <param name="dataContainer"></param>
/// <param name="sqlConnection"></param> /// <param name="sqlConnection"></param>
public CommonUtilities(CDataContainer dataContainer, ServerConnection sqlConnection) public CommonUtilities(CDataContainer dataContainer, ServerConnection sqlConnection)
{ {
this.dataContainer = dataContainer; this.dataContainer = dataContainer;
this.sqlConnection = sqlConnection; this.sqlConnection = sqlConnection;
this.excludedDatabases = new ArrayList(); this.excludedDatabases = new ArrayList();
this.excludedDatabases.Add("master"); this.excludedDatabases.Add("master");
this.excludedDatabases.Add("tempdb"); this.excludedDatabases.Add("tempdb");
} }
public int GetServerVersion() public int GetServerVersion()
{ {
return this.dataContainer.Server.Information.Version.Major; return this.dataContainer.Server.Information.Version.Major;
} }
/// <summary> /// <summary>
@@ -162,7 +163,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
else if (String.Compare(stringDeviceType, RestoreConstants.Url, StringComparison.OrdinalIgnoreCase) == 0) else if (String.Compare(stringDeviceType, RestoreConstants.Url, StringComparison.OrdinalIgnoreCase) == 0)
{ {
return DeviceType.Url; return DeviceType.Url;
} }
else else
{ {
return DeviceType.LogicalDevice; return DeviceType.LogicalDevice;
@@ -188,10 +189,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return DeviceType.LogicalDevice; return DeviceType.LogicalDevice;
} }
} }
public BackupDeviceType GetPhisycalDeviceTypeOfLogicalDevice(string deviceName) public BackupDeviceType GetPhisycalDeviceTypeOfLogicalDevice(string deviceName)
{ {
Enumerator enumerator = new Enumerator(); Enumerator enumerator = new Enumerator();
Request request = new Request(); Request request = new Request();
DataSet dataset = new DataSet(); DataSet dataset = new DataSet();
dataset.Locale = System.Globalization.CultureInfo.InvariantCulture; dataset.Locale = System.Globalization.CultureInfo.InvariantCulture;
@@ -199,16 +200,16 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
dataset = enumerator.Process(this.sqlConnection, request); dataset = enumerator.Process(this.sqlConnection, request);
if (dataset.Tables[0].Rows.Count > 0) if (dataset.Tables[0].Rows.Count > 0)
{ {
BackupDeviceType controllerType = (BackupDeviceType)(Convert.ToInt16(dataset.Tables[0].Rows[0]["BackupDeviceType"], System.Globalization.CultureInfo.InvariantCulture)); BackupDeviceType controllerType = (BackupDeviceType)(Convert.ToInt16(dataset.Tables[0].Rows[0]["BackupDeviceType"], System.Globalization.CultureInfo.InvariantCulture));
return controllerType; return controllerType;
} }
else else
{ {
throw new Exception("Unexpected error"); throw new Exception("Unexpected error");
} }
} }
public bool ServerHasTapes() public bool ServerHasTapes()
{ {
try try
@@ -221,12 +222,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
ds = en.Process(this.sqlConnection, req); ds = en.Process(this.sqlConnection, req);
if (ds.Tables[0].Rows.Count > 0) if (ds.Tables[0].Rows.Count > 0)
{ {
return true; return true;
} }
return false; return false;
} }
catch(Exception) catch (Exception)
{ {
return false; return false;
} }
@@ -241,18 +242,18 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
DataSet ds = new DataSet(); DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
req.Urn = "Server/BackupDevice"; req.Urn = "Server/BackupDevice";
ds = en.Process(this.sqlConnection,req); ds = en.Process(this.sqlConnection, req);
if (ds.Tables[0].Rows.Count > 0) if (ds.Tables[0].Rows.Count > 0)
{ {
return true; return true;
} }
return false; return false;
} }
catch(Exception) catch (Exception)
{ {
return false; return false;
} }
} }
/// <summary> /// <summary>
@@ -265,8 +266,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
char[] result = name.ToCharArray(); char[] result = name.ToCharArray();
string illegalCharacters = "\\/:*?\"<>|"; string illegalCharacters = "\\/:*?\"<>|";
int resultLength = result.GetLength(0); int resultLength = result.GetLength(0);
int illegalLength = illegalCharacters.Length; int illegalLength = illegalCharacters.Length;
for (int resultIndex = 0; resultIndex < resultLength; resultIndex++) for (int resultIndex = 0; resultIndex < resultLength; resultIndex++)
{ {
@@ -278,17 +279,17 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
} }
} }
return new string(result); return new string(result);
} }
public RecoveryModel GetRecoveryModel(string databaseName) public RecoveryModel GetRecoveryModel(string databaseName)
{ {
Enumerator en = null; Enumerator en = null;
DataSet ds = new DataSet(); DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
Request req = new Request(); Request req = new Request();
RecoveryModel recoveryModel = RecoveryModel.Simple; RecoveryModel recoveryModel = RecoveryModel.Simple;
en = new Enumerator(); en = new Enumerator();
req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Option"; req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Option";
@@ -297,9 +298,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
ds = en.Process(this.sqlConnection, req); ds = en.Process(this.sqlConnection, req);
if (ds.Tables[0].Rows.Count > 0) if (ds.Tables[0].Rows.Count > 0)
{ {
recoveryModel = (RecoveryModel)(ds.Tables[0].Rows[0]["RecoveryModel"]); recoveryModel = (RecoveryModel)(ds.Tables[0].Rows[0]["RecoveryModel"]);
} }
return recoveryModel; return recoveryModel;
} }
@@ -319,7 +320,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
recoveryModelString = BackupConstants.RecoveryModelBulk; recoveryModelString = BackupConstants.RecoveryModelBulk;
} }
return recoveryModelString; return recoveryModelString;
} }
@@ -342,6 +343,69 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return backupFolder; return backupFolder;
} }
public static string GetDefaultDataFolder(ServerConnection connection)
{
string dataFolder = string.Empty;
Enumerator en = null;
DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
Request req = new Request();
en = new Enumerator();
req.Urn = "Server/Setting";
ds = en.Process(connection, req);
if (ds.Tables[0].Rows.Count > 0)
{
dataFolder = Convert.ToString(ds.Tables[0].Rows[0]["DefaultFile"], System.Globalization.CultureInfo.InvariantCulture);
}
return dataFolder;
}
public static string[] GetAssociatedFilePaths(ServerConnection connection, string primaryFilePath)
{
var databaseFiles = new List<string>();
var enumerator = new Enumerator();
var req = new Request()
{
Urn = $"Server/PrimaryFile[@Name='{Urn.EscapeString(primaryFilePath)}']/File",
Fields = new string[] { "IsFile", "FileName" }
};
var dataTable = (DataTable)enumerator.Process(connection, req);
foreach (DataRow currentRow in dataTable.Rows)
{
var primaryFolder = Path.GetDirectoryName(primaryFilePath);
var originalPath = (string)currentRow["FileName"];
var originalFileName = Path.GetFileName(originalPath);
var filePath = Path.Join(primaryFolder, originalFileName);
// Check if file exists with the constructed path.
// If it's an XI (XStore Integration) path, then assume it exists, otherwise retrieve info for the file to check if it exists.
var exists = true;
var isXIPath = PathWrapper.IsXIPath(primaryFilePath);
if (!isXIPath)
{
var request = new Request()
{
Urn = string.Format(System.Globalization.CultureInfo.CurrentCulture, "Server/File[@FullName='{0}']", Urn.EscapeString(filePath)),
Fields = new string[] { "IsFile" }
};
DataTable data = (new Enumerator()).Process(connection, request);
// If the enumerator could find the file, then it exists
exists = data?.Rows.Count > 0;
}
if (exists)
{
databaseFiles.Add(filePath);
}
}
return databaseFiles.ToArray();
}
public int GetMediaRetentionValue() public int GetMediaRetentionValue()
{ {
int afterDays = 0; int afterDays = 0;
@@ -353,29 +417,29 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
req.Urn = "Server/Configuration"; req.Urn = "Server/Configuration";
ds = en.Process(this.sqlConnection, req); ds = en.Process(this.sqlConnection, req);
for (int i = 0 ; i < ds.Tables[0].Rows.Count; i++) for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{ {
if (Convert.ToString(ds.Tables[0].Rows[i]["Name"], System.Globalization.CultureInfo.InvariantCulture) == "media retention") if (Convert.ToString(ds.Tables[0].Rows[i]["Name"], System.Globalization.CultureInfo.InvariantCulture) == "media retention")
{ {
afterDays = Convert.ToInt32(ds.Tables[0].Rows[i]["RunValue"], System.Globalization.CultureInfo.InvariantCulture); afterDays = Convert.ToInt32(ds.Tables[0].Rows[i]["RunValue"], System.Globalization.CultureInfo.InvariantCulture);
break; break;
} }
} }
return afterDays; return afterDays;
} }
catch (Exception) catch (Exception)
{ {
return afterDays; return afterDays;
} }
} }
public string GetMediaNameFromBackupSetId(int backupSetId) public string GetMediaNameFromBackupSetId(int backupSetId)
{ {
Enumerator en = null; Enumerator en = null;
DataSet ds = new DataSet(); DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
Request req = new Request(); Request req = new Request();
int mediaId = -1; int mediaId = -1;
string mediaName = string.Empty; string mediaName = string.Empty;
@@ -402,7 +466,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
catch (Exception) catch (Exception)
{ {
} }
return mediaName; return mediaName;
} }
public string GetFileType(string type) public string GetFileType(string type)
@@ -429,8 +493,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return result; return result;
} }
// TODO: This is implemented as internal property in SMO. // TODO: This is implemented as internal property in SMO.
public bool IsLocalPrimaryReplica(string databaseName) public bool IsLocalPrimaryReplica(string databaseName)
@@ -450,7 +514,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
return !string.IsNullOrEmpty(server.Databases[databaseName].AvailabilityGroupName); return !string.IsNullOrEmpty(server.Databases[databaseName].AvailabilityGroupName);
} }
/// <summary> /// <summary>
/// Returns whether mirroring is enabled on a database or not /// Returns whether mirroring is enabled on a database or not
/// </summary>> /// </summary>>
@@ -509,19 +573,19 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return result; return result;
} }
public bool IsDatabaseOnServer(string databaseName) public bool IsDatabaseOnServer(string databaseName)
{ {
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
DataSet ds = new DataSet(); DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
Request req = new Request(); Request req = new Request();
req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']"; req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']";
req.Fields = new string[1]; req.Fields = new string[1];
req.Fields[0] = "Name"; req.Fields[0] = "Name";
ds = en.Process(sqlConnection, req); ds = en.Process(sqlConnection, req);
return (ds.Tables[0].Rows.Count > 0) ? true : false; return (ds.Tables[0].Rows.Count > 0) ? true : false;
} }
@@ -617,7 +681,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
case BackupSetType.Differential: case BackupSetType.Differential:
backupType = RestoreConstants.TypeDifferential; backupType = RestoreConstants.TypeDifferential;
backupComponent = RestoreConstants.ComponentDatabase; backupComponent = RestoreConstants.ComponentDatabase;
break; break;
case BackupSetType.FileOrFileGroup: case BackupSetType.FileOrFileGroup:
backupType = RestoreConstants.TypeFilegroup; backupType = RestoreConstants.TypeFilegroup;
backupComponent = RestoreConstants.ComponentFile; backupComponent = RestoreConstants.ComponentFile;
@@ -636,9 +700,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
break; break;
} }
} }
public void GetBackupSetTypeAndComponent(string strType, ref string backupType, ref string backupComponent) public void GetBackupSetTypeAndComponent(string strType, ref string backupType, ref string backupComponent)
{ {
string type = strType.ToUpperInvariant(); string type = strType.ToUpperInvariant();
if (type == "D") if (type == "D")
@@ -682,7 +746,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
} }
} }
} }
} }
public void GetFileType(string backupType, string tempFileType, ref string fileType) public void GetFileType(string backupType, string tempFileType, ref string fileType)
@@ -694,20 +758,23 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
switch (type) switch (type)
{ {
case "D": fileType = RestoreConstants.Data; case "D":
fileType = RestoreConstants.Data;
break; break;
case "S": fileType = RestoreConstants.FileStream; case "S":
fileType = RestoreConstants.FileStream;
break; break;
default: fileType = RestoreConstants.NotKnown; default:
fileType = RestoreConstants.NotKnown;
break; break;
} }
} }
} }
public BackupsetType GetBackupsetTypeFromBackupTypesOnDevice(int type) public BackupsetType GetBackupsetTypeFromBackupTypesOnDevice(int type)
{ {
BackupsetType Result = BackupsetType.BackupsetDatabase; BackupsetType Result = BackupsetType.BackupsetDatabase;
switch(type) switch (type)
{ {
case 1: case 1:
Result = BackupsetType.BackupsetDatabase; Result = BackupsetType.BackupsetDatabase;
@@ -728,11 +795,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return Result; return Result;
} }
public BackupsetType GetBackupsetTypeFromBackupTypesOnHistory(string type) public BackupsetType GetBackupsetTypeFromBackupTypesOnHistory(string type)
{ {
BackupsetType result = BackupsetType.BackupsetDatabase; BackupsetType result = BackupsetType.BackupsetDatabase;
switch(type) switch (type)
{ {
case "D": case "D":
result = BackupsetType.BackupsetDatabase; result = BackupsetType.BackupsetDatabase;
@@ -752,7 +819,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
return result; return result;
} }
public DataSet GetBackupSetFiles(int backupsetId) public DataSet GetBackupSetFiles(int backupsetId)
{ {
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
@@ -760,7 +827,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
DataSet backupsetfiles = new DataSet(); DataSet backupsetfiles = new DataSet();
backupsetfiles.Locale = System.Globalization.CultureInfo.InvariantCulture; backupsetfiles.Locale = System.Globalization.CultureInfo.InvariantCulture;
if(backupsetId > 0) if (backupsetId > 0)
{ {
req.Urn = "Server/BackupSet[@ID='" + Urn.EscapeString(Convert.ToString(backupsetId, System.Globalization.CultureInfo.InvariantCulture)) + "']/File"; req.Urn = "Server/BackupSet[@ID='" + Urn.EscapeString(Convert.ToString(backupsetId, System.Globalization.CultureInfo.InvariantCulture)) + "']/File";
} }
@@ -772,9 +839,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return backupsetfiles; return backupsetfiles;
} }
public DataSet GetBackupSetById(int backupsetId) public DataSet GetBackupSetById(int backupsetId)
{ {
SqlExecutionModes executionMode = this.sqlConnection.SqlExecutionModes; SqlExecutionModes executionMode = this.sqlConnection.SqlExecutionModes;
this.sqlConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql; this.sqlConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql;
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
@@ -788,47 +855,47 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.sqlConnection.SqlExecutionModes = executionMode; this.sqlConnection.SqlExecutionModes = executionMode;
return backupset; return backupset;
} }
public ArrayList GetBackupSetPhysicalSources(int backupsetId) public ArrayList GetBackupSetPhysicalSources(int backupsetId)
{ {
SqlExecutionModes executionMode = this.sqlConnection.SqlExecutionModes; SqlExecutionModes executionMode = this.sqlConnection.SqlExecutionModes;
this.sqlConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql; this.sqlConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql;
ArrayList sources = new ArrayList(); ArrayList sources = new ArrayList();
DataSet backupSet = GetBackupSetById(backupsetId); DataSet backupSet = GetBackupSetById(backupsetId);
if(backupSet.Tables[0].Rows.Count == 1) if (backupSet.Tables[0].Rows.Count == 1)
{ {
string mediaSetID = Convert.ToString(backupSet.Tables[0].Rows[0]["MediaSetId"], System.Globalization.CultureInfo.InvariantCulture); string mediaSetID = Convert.ToString(backupSet.Tables[0].Rows[0]["MediaSetId"], System.Globalization.CultureInfo.InvariantCulture);
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
Request req = new Request(); Request req = new Request();
DataSet mediafamily = new DataSet(); DataSet mediafamily = new DataSet();
mediafamily.Locale = System.Globalization.CultureInfo.InvariantCulture; mediafamily.Locale = System.Globalization.CultureInfo.InvariantCulture;
req.Urn = "Server/BackupMediaSet[@ID='"+Urn.EscapeString(mediaSetID)+"']/MediaFamily"; req.Urn = "Server/BackupMediaSet[@ID='" + Urn.EscapeString(mediaSetID) + "']/MediaFamily";
mediafamily = en.Process(this.sqlConnection, req); mediafamily = en.Process(this.sqlConnection, req);
if (mediafamily.Tables[0].Rows.Count > 0) if (mediafamily.Tables[0].Rows.Count > 0)
{ {
for (int j = 0 ; j < mediafamily.Tables[0].Rows.Count; j ++) for (int j = 0; j < mediafamily.Tables[0].Rows.Count; j++)
{ {
RestoreItemSource itemSource = new RestoreItemSource(); RestoreItemSource itemSource = new RestoreItemSource();
itemSource.RestoreItemLocation = Convert.ToString(mediafamily.Tables[0].Rows[j]["PhysicalDeviceName"], System.Globalization.CultureInfo.InvariantCulture); itemSource.RestoreItemLocation = Convert.ToString(mediafamily.Tables[0].Rows[j]["PhysicalDeviceName"], System.Globalization.CultureInfo.InvariantCulture);
BackupDeviceType backupDeviceType = (BackupDeviceType)Enum.Parse(typeof(BackupDeviceType), mediafamily.Tables[0].Rows[j]["BackupDeviceType"].ToString()); BackupDeviceType backupDeviceType = (BackupDeviceType)Enum.Parse(typeof(BackupDeviceType), mediafamily.Tables[0].Rows[j]["BackupDeviceType"].ToString());
if (BackupDeviceType.Disk == backupDeviceType) if (BackupDeviceType.Disk == backupDeviceType)
{ {
itemSource.RestoreItemDeviceType = DeviceType.File; itemSource.RestoreItemDeviceType = DeviceType.File;
} }
else if (BackupDeviceType.Url == backupDeviceType) else if (BackupDeviceType.Url == backupDeviceType)
{ {
itemSource.RestoreItemDeviceType = DeviceType.Url; itemSource.RestoreItemDeviceType = DeviceType.Url;
} }
else else
{ {
itemSource.RestoreItemDeviceType = DeviceType.Tape; itemSource.RestoreItemDeviceType = DeviceType.Tape;
} }
sources.Add(itemSource); sources.Add(itemSource);
} }
} }
} }
@@ -836,11 +903,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.sqlConnection.SqlExecutionModes = executionMode; this.sqlConnection.SqlExecutionModes = executionMode;
return sources; return sources;
} }
public RestoreActionType GetRestoreTaskFromBackupSetType(BackupsetType type) public RestoreActionType GetRestoreTaskFromBackupSetType(BackupsetType type)
{ {
RestoreActionType result = RestoreActionType.Database; RestoreActionType result = RestoreActionType.Database;
switch (type) switch (type)
{ {
case BackupsetType.BackupsetDatabase: case BackupsetType.BackupsetDatabase:
@@ -861,16 +928,16 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
return result; return result;
} }
public int GetLatestBackup(string databaseName, string backupSetName) public int GetLatestBackup(string databaseName, string backupSetName)
{ {
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
Request req = new Request(); Request req = new Request();
DataSet backupSets = new DataSet(); DataSet backupSets = new DataSet();
backupSets.Locale = System.Globalization.CultureInfo.InvariantCulture; backupSets.Locale = System.Globalization.CultureInfo.InvariantCulture;
OrderBy orderByBackupDate; OrderBy orderByBackupDate;
req.Urn = "Server/BackupSet[@Name='"+Urn.EscapeString(backupSetName)+"' and @DatabaseName='"+ Urn.EscapeString(databaseName)+"']"; req.Urn = "Server/BackupSet[@Name='" + Urn.EscapeString(backupSetName) + "' and @DatabaseName='" + Urn.EscapeString(databaseName) + "']";
req.OrderByList = new OrderBy[1]; req.OrderByList = new OrderBy[1];
orderByBackupDate = new OrderBy("BackupFinishDate", OrderBy.Direction.Desc); orderByBackupDate = new OrderBy("BackupFinishDate", OrderBy.Direction.Desc);
req.OrderByList[0] = orderByBackupDate; req.OrderByList[0] = orderByBackupDate;
@@ -885,7 +952,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return -1; return -1;
} }
} }
public List<RestoreItemSource> GetLatestBackupLocations(string databaseName) public List<RestoreItemSource> GetLatestBackupLocations(string databaseName)
{ {
List<RestoreItemSource> latestLocations = new List<RestoreItemSource>(); List<RestoreItemSource> latestLocations = new List<RestoreItemSource>();
@@ -944,20 +1011,20 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
/// LPU doesn't have rights to enumerate msdb.backupset /// LPU doesn't have rights to enumerate msdb.backupset
catch (Exception) catch (Exception)
{ {
} }
return latestLocations; return latestLocations;
} }
public string GetDefaultDatabaseForLogin(string loginName) public string GetDefaultDatabaseForLogin(string loginName)
{ {
string defaultDatabase = string.Empty; string defaultDatabase = string.Empty;
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
DataSet ds = new DataSet(); DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
Request req = new Request(); Request req = new Request();
req.Urn = "Server/Login[@Name='"+Urn.EscapeString(loginName)+"']"; req.Urn = "Server/Login[@Name='" + Urn.EscapeString(loginName) + "']";
req.Fields = new string[1]; req.Fields = new string[1];
req.Fields[0] = "DefaultDatabase"; req.Fields[0] = "DefaultDatabase";
ds = en.Process(this.sqlConnection, req); ds = en.Process(this.sqlConnection, req);
@@ -991,26 +1058,26 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
public ArrayList IsPhysicalPathInLogicalDevice(string physicalPath) public ArrayList IsPhysicalPathInLogicalDevice(string physicalPath)
{ {
Enumerator en = new Enumerator(); Enumerator en = new Enumerator();
DataSet ds = new DataSet(); DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
Request req = new Request(); Request req = new Request();
ArrayList result = null; ArrayList result = null;
int count = 0; int count = 0;
req.Urn = "Server/BackupDevice[@PhysicalLocation='" +Urn.EscapeString(physicalPath)+ "']"; req.Urn = "Server/BackupDevice[@PhysicalLocation='" + Urn.EscapeString(physicalPath) + "']";
ds = en.Process(this.sqlConnection, req); ds = en.Process(this.sqlConnection, req);
count = ds.Tables[0].Rows.Count; count = ds.Tables[0].Rows.Count;
if (count > 0) if (count > 0)
{ {
result = new ArrayList(count); result = new ArrayList(count);
for(int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
result.Add(Convert.ToString(ds.Tables[0].Rows[0]["Name"], System.Globalization.CultureInfo.InvariantCulture)); result.Add(Convert.ToString(ds.Tables[0].Rows[0]["Name"], System.Globalization.CultureInfo.InvariantCulture));
} }
} }
return result; return result;
} }
@@ -1025,7 +1092,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return System.Environment.MachineName; return System.Environment.MachineName;
} }
string machineName = sqlServerName; string machineName = sqlServerName;
if (sqlServerName.Trim().Length != 0) if (sqlServerName.Trim().Length != 0)
{ {
// [0] = machine, [1] = instance // [0] = machine, [1] = instance

View File

@@ -0,0 +1,29 @@
//
// 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 Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class DatabaseFileData
{
public string DatabaseName { get; set; }
public string[] DatabaseFilePaths { get; set; }
public string Owner { get; set; }
}
public class AttachDatabaseRequestParams
{
public string ConnectionUri { get; set; }
public DatabaseFileData[] Databases { get; set; }
public bool GenerateScript { get; set; }
}
public class AttachDatabaseRequest
{
public static readonly RequestType<AttachDatabaseRequestParams, string> Type = RequestType<AttachDatabaseRequestParams, string>.Create("objectManagement/attachDatabase");
}
}

View File

@@ -70,6 +70,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
this.serviceHost.SetRequestHandler(DisposeViewRequest.Type, HandleDisposeViewRequest, true); this.serviceHost.SetRequestHandler(DisposeViewRequest.Type, HandleDisposeViewRequest, true);
this.serviceHost.SetRequestHandler(SearchRequest.Type, HandleSearchRequest, true); this.serviceHost.SetRequestHandler(SearchRequest.Type, HandleSearchRequest, true);
this.serviceHost.SetRequestHandler(DetachDatabaseRequest.Type, HandleDetachDatabaseRequest, true); this.serviceHost.SetRequestHandler(DetachDatabaseRequest.Type, HandleDetachDatabaseRequest, true);
this.serviceHost.SetRequestHandler(AttachDatabaseRequest.Type, HandleAttachDatabaseRequest, true);
this.serviceHost.SetRequestHandler(DropDatabaseRequest.Type, HandleDropDatabaseRequest, true); this.serviceHost.SetRequestHandler(DropDatabaseRequest.Type, HandleDropDatabaseRequest, true);
} }
@@ -155,7 +156,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
} }
SearchableObjectTypeDescription desc = SearchableObjectTypeDescription.GetDescription(searchableObjectType); SearchableObjectTypeDescription desc = SearchableObjectTypeDescription.GetDescription(searchableObjectType);
if (desc.IsDatabaseObject) if (desc.IsDatabaseObject)
{ {
if (!string.IsNullOrEmpty(requestParams.Schema)) if (!string.IsNullOrEmpty(requestParams.Schema))
@@ -207,6 +208,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
await requestContext.SendResult(sqlScript); await requestContext.SendResult(sqlScript);
} }
internal async Task HandleAttachDatabaseRequest(AttachDatabaseRequestParams requestParams, RequestContext<string> requestContext)
{
var handler = this.GetObjectTypeHandler(SqlObjectType.Database) as DatabaseHandler;
var sqlScript = handler.Attach(requestParams);
await requestContext.SendResult(sqlScript);
}
internal async Task HandleDropDatabaseRequest(DropDatabaseRequestParams requestParams, RequestContext<string> requestContext) internal async Task HandleDropDatabaseRequest(DropDatabaseRequestParams requestParams, RequestContext<string> requestContext)
{ {
var handler = this.GetObjectTypeHandler(SqlObjectType.Database) as DatabaseHandler; var handler = this.GetObjectTypeHandler(SqlObjectType.Database) as DatabaseHandler;

View File

@@ -20,6 +20,7 @@ using Microsoft.SqlTools.Utility;
using System.Text; using System.Text;
using System.IO; using System.IO;
using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using System.Collections.Specialized;
using Microsoft.SqlTools.SqlCore.Utility; using Microsoft.SqlTools.SqlCore.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
@@ -393,6 +394,60 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
return builder.ToString(); return builder.ToString();
} }
public string Attach(AttachDatabaseRequestParams attachParams)
{
var sqlScript = string.Empty;
ConnectionInfo connectionInfo = this.GetConnectionInfo(attachParams.ConnectionUri);
using (var dataContainer = CreateDatabaseDataContainer(attachParams.ConnectionUri, null, true, null))
{
var server = dataContainer.Server!;
var originalExecuteMode = server.ConnectionContext.SqlExecutionModes;
if (attachParams.GenerateScript)
{
server.ConnectionContext.SqlExecutionModes = SqlExecutionModes.CaptureSql;
server.ConnectionContext.CapturedSql.Clear();
}
try
{
foreach (var database in attachParams.Databases)
{
var fileCollection = new StringCollection();
fileCollection.AddRange(database.DatabaseFilePaths);
if (database.Owner != SR.general_default)
{
server.AttachDatabase(database.DatabaseName, fileCollection, database.Owner);
}
else
{
server.AttachDatabase(database.DatabaseName, fileCollection);
}
}
if (attachParams.GenerateScript)
{
var builder = new StringBuilder();
var capturedText = server.ConnectionContext.CapturedSql.Text;
foreach (var entry in capturedText)
{
if (entry != null)
{
builder.AppendLine(entry);
}
}
sqlScript = builder.ToString();
}
}
finally
{
if (attachParams.GenerateScript)
{
server.ConnectionContext.SqlExecutionModes = originalExecuteMode;
}
dataContainer.ServerConnection.Disconnect();
}
}
return sqlScript;
}
/// <summary> /// <summary>
/// Used to drop the specified database /// Used to drop the specified database
/// </summary> /// </summary>

View File

@@ -3,9 +3,12 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // 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.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Common;
@@ -356,7 +359,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary = "OFF"; testDatabase.DatabaseScopedConfigurations[0].ValueForSecondary = "OFF";
} }
await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabase); await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testDatabase);
DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase); DatabaseViewInfo updatedDatabaseViewInfo = await ObjectManagementTestUtils.GetDatabaseObject(parametersForUpdate, testDatabase);
// verify the modified properties // 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].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] [Test]
public async Task DeleteDatabaseTest() public async Task DeleteDatabaseTest()
{ {

View File

@@ -0,0 +1,135 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#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."
);
}
}
}
}
}