diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs
index ea0d3757f7..e381656151 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs
@@ -35,6 +35,11 @@ public class RestorePlanDetailInfo
///
public object DefaultValue { get; set; }
+ ///
+ /// Error message if the current value is not valid
+ ///
+ public object ErrorMessage { get; set; }
+
internal static RestorePlanDetailInfo Create(string name, object currentValue, bool isReadOnly = false, bool isVisible = true, object defaultValue = null)
{
return new RestorePlanDetailInfo
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs
index 7c3d51bdaa..816161e769 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs
@@ -15,6 +15,8 @@
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.Utility;
+using System.Collections.Concurrent;
+using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{
@@ -24,6 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
public class RestoreDatabaseHelper
{
public const string LastBackupTaken = "lastBackupTaken";
+ private ConcurrentDictionary sessions = new ConcurrentDictionary();
///
/// Create a backup task for execution and cancellation
@@ -169,6 +172,16 @@ public RestorePlanResponse CreateRestorePlanResponse(RestoreDatabaseTaskDataObje
{
response.SessionId = restoreDataObject.SessionId;
response.DatabaseName = restoreDataObject.TargetDatabase;
+ response.PlanDetails.Add(RestoreOptionsHelper.TargetDatabaseName, RestorePlanDetailInfo.Create(
+ name: RestoreOptionsHelper.TargetDatabaseName,
+ currentValue: restoreDataObject.TargetDatabase,
+ isReadOnly: !CanChangeTargetDatabase(restoreDataObject)));
+ response.PlanDetails.Add(RestoreOptionsHelper.SourceDatabaseName, RestorePlanDetailInfo.Create(
+ name: RestoreOptionsHelper.SourceDatabaseName,
+ currentValue: restoreDataObject.RestorePlanner.DatabaseName));
+ response.PlanDetails.Add(RestoreOptionsHelper.ReadHeaderFromMedia, RestorePlanDetailInfo.Create(
+ name: RestoreOptionsHelper.ReadHeaderFromMedia,
+ currentValue: restoreDataObject.RestorePlanner.ReadHeaderFromMedia));
response.DbFiles = restoreDataObject.DbFiles.Select(x => new RestoreDatabaseFileInfo
{
FileType = x.DbFileType,
@@ -183,10 +196,11 @@ public RestorePlanResponse CreateRestorePlanResponse(RestoreDatabaseTaskDataObje
response.ErrorMessage = SR.RestoreNotSupported;
}
- response.PlanDetails.Add(LastBackupTaken, RestorePlanDetailInfo.Create(LastBackupTaken, restoreDataObject.GetLastBackupTaken()));
+ response.PlanDetails.Add(LastBackupTaken,
+ RestorePlanDetailInfo.Create(name: LastBackupTaken, currentValue: restoreDataObject.GetLastBackupTaken(), isReadOnly: true));
response.BackupSetsToRestore = restoreDataObject.GetSelectedBakupSets();
- var dbNames = restoreDataObject.GetSourceDbNames();
+ var dbNames = restoreDataObject.GetPossibleTargerDbNames();
response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray();
RestoreOptionsHelper.AddOptions(response, restoreDataObject);
@@ -241,10 +255,15 @@ private static bool CanRestore(RestoreDatabaseTaskDataObject restoreDataObject)
public RestoreDatabaseTaskDataObject CreateRestoreDatabaseTaskDataObject(RestoreParams restoreParams)
{
RestoreDatabaseTaskDataObject restoreTaskObject = null;
- restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName);
string sessionId = string.IsNullOrWhiteSpace(restoreParams.SessionId) ? Guid.NewGuid().ToString() : restoreParams.SessionId;
+ if (!sessions.TryGetValue(sessionId, out restoreTaskObject))
+ {
+ restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName);
+ }
restoreTaskObject.SessionId = sessionId;
restoreTaskObject.RestoreParams = restoreParams;
+ restoreTaskObject.TargetDatabase = restoreParams.TargetDatabaseName;
+ restoreTaskObject.RestorePlanner.DatabaseName = restoreParams.TargetDatabaseName;
return restoreTaskObject;
}
@@ -303,13 +322,26 @@ private void UpdateRestorePlan(RestoreDatabaseTaskDataObject restoreDataObject)
{
restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.RestoreParams.SourceDatabaseName;
}
- restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName;
- RestoreOptionsHelper.UpdateOptionsInPlan(restoreDataObject);
+ if (CanChangeTargetDatabase(restoreDataObject))
+ {
+ restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName;
+ }
+ else
+ {
+ restoreDataObject.TargetDatabase = restoreDataObject.Server.ConnectionContext.DatabaseName;
+ }
+
+
restoreDataObject.UpdateRestorePlan();
}
+ private bool CanChangeTargetDatabase(RestoreDatabaseTaskDataObject restoreDataObject)
+ {
+ return DatabaseUtils.IsSystemDatabaseConnection(restoreDataObject.Server.ConnectionContext.DatabaseName);
+ }
+
///
/// Executes the restore operation
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs
index 59e6da0c73..44a2e8de1c 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs
@@ -26,15 +26,15 @@ public interface IRestoreDatabaseTaskDataObject
RestoreOptions RestoreOptions { get; }
- string GetDefaultStandbyFile(string databaseName);
- bool IsTailLogBackupPossible(string databaseName);
- bool IsTailLogBackupWithNoRecoveryPossible(string databaseName);
+ string DefaultStandbyFile { get; }
+ bool IsTailLogBackupPossible { get; }
+ bool IsTailLogBackupWithNoRecoveryPossible { get; }
bool TailLogWithNoRecovery { get; set; }
string TailLogBackupFile { get; set; }
- string GetDefaultTailLogbackupFile(string databaseName);
+ string DefaultTailLogbackupFile { get; }
RestorePlan RestorePlan { get; }
@@ -54,6 +54,8 @@ public class RestoreDatabaseTaskDataObject : IRestoreDatabaseTaskDataObject
private DatabaseRestorePlanner restorePlanner;
private string tailLogBackupFile;
private BackupSetsFilterInfo backupSetsFilterInfo = new BackupSetsFilterInfo();
+ private bool? isTailLogBackupPossible = false;
+ private bool? isTailLogBackupWithNoRecoveryPossible = false;
public RestoreDatabaseTaskDataObject(Server server, String databaseName)
{
@@ -119,7 +121,7 @@ public bool IsValid
/// Database names includes in the restore plan
///
///
- public List GetSourceDbNames()
+ public List GetPossibleTargerDbNames()
{
return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName);
}
@@ -169,7 +171,7 @@ public void AddFiles(string filePaths)
}
}
- var itemsToRemove = this.RestorePlanner.BackupMediaList.Where(x => !files.Contains(x.Name));
+ var itemsToRemove = this.RestorePlanner.BackupMediaList.Where(x => !files.Contains(x.Name)).ToList();
foreach (var item in itemsToRemove)
{
this.RestorePlanner.BackupMediaList.Remove(item);
@@ -349,7 +351,7 @@ public string DataFilesFolder
}
else
{
- this.dataFilesFolder = PathWrapper.GetDirectoryName(value);
+ this.dataFilesFolder = PathWrapper.GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext));
}
if (string.IsNullOrEmpty(this.dataFilesFolder))
{
@@ -397,7 +399,7 @@ public string LogFilesFolder
}
else
{
- this.logFilesFolder = PathWrapper.GetDirectoryName(value);
+ this.logFilesFolder = PathWrapper.GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext));
}
if (string.IsNullOrEmpty(this.logFilesFolder))
{
@@ -419,32 +421,44 @@ public string LogFilesFolder
///
/// true if [is tail log backup possible]; otherwise, false.
///
- public bool IsTailLogBackupPossible(string databaseName)
+ public bool IsTailLogBackupPossible
{
- if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName))
- {
- return false;
- }
-
- Database db = this.Server.Databases[databaseName];
- if (db == null)
- {
- return false;
- }
- else
+ get
{
- db.Refresh();
- }
+ if (!isTailLogBackupPossible.HasValue)
+ {
+ if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName))
+ {
+ isTailLogBackupPossible = false;
+ }
+ else
+ {
+ Database db = this.Server.Databases[this.RestorePlanner.DatabaseName];
+ if (db == null)
+ {
+ isTailLogBackupPossible = false;
+ }
+ else
+ {
+ db.Refresh();
+ if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode)
+ {
+ isTailLogBackupPossible = false;
+ }
+ else if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged)
+ {
+ isTailLogBackupPossible = true;
+ }
+ else
+ {
+ isTailLogBackupPossible = false;
+ }
+ }
+ }
+ }
- if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode)
- {
- return false;
+ return isTailLogBackupPossible.Value;
}
- if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged)
- {
- return true;
- }
- return false;
}
///
@@ -453,37 +467,56 @@ public bool IsTailLogBackupPossible(string databaseName)
///
/// true if [is tail log backup with NORECOVERY possible]; otherwise, false.
///
- public bool IsTailLogBackupWithNoRecoveryPossible(string databaseName)
+ public bool IsTailLogBackupWithNoRecoveryPossible
{
- if (!IsTailLogBackupPossible(databaseName))
- {
- return false;
- }
-
- Database db = this.Server.Databases[databaseName];
- if (db == null)
- {
- return false;
- }
- if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName))
- {
- return false;
- }
- if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled)
+ get
{
- return false;
+ if (!isTailLogBackupWithNoRecoveryPossible.HasValue)
+ {
+ string databaseName = this.RestorePlanner.DatabaseName;
+ if (!IsTailLogBackupPossible)
+ {
+ isTailLogBackupWithNoRecoveryPossible = false;
+ }
+ else
+ {
+ Database db = this.Server.Databases[databaseName];
+ if (db == null)
+ {
+ isTailLogBackupWithNoRecoveryPossible = false;
+ }
+ else if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName))
+ {
+ isTailLogBackupWithNoRecoveryPossible = false;
+ }
+ else if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled)
+ {
+ isTailLogBackupWithNoRecoveryPossible = false;
+ }
+ else
+ {
+ isTailLogBackupWithNoRecoveryPossible = true;
+ }
+ }
+ }
+ return isTailLogBackupWithNoRecoveryPossible.Value;
}
- return true;
}
- public string GetDefaultStandbyFile(string databaseName)
+ public string DefaultStandbyFile
{
- return Util.GetDefaultStandbyFile(databaseName);
+ get
+ {
+ return Util.GetDefaultStandbyFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty);
+ }
}
- public string GetDefaultTailLogbackupFile(string databaseName)
+ public string DefaultTailLogbackupFile
{
- return Util.GetDefaultTailLogbackupFile(databaseName);
+ get
+ {
+ return Util.GetDefaultTailLogbackupFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty);
+ }
}
///
@@ -605,7 +638,7 @@ public string DefaultDbName
{
get
{
- var dbNames = GetSourceDbNames();
+ var dbNames = GetPossibleTargerDbNames();
string dbName = dbNames.FirstOrDefault();
return dbName;
}
@@ -663,10 +696,6 @@ public IEnumerable GetBackupSetInfo()
foreach (Restore restore in RestorePlan.RestoreOperations)
{
BackupSetInfo backupSetInfo = BackupSetInfo.Create(restore, Server);
- if (this.backupSetsFilterInfo.IsBackupSetSelected(restore.BackupSet))
- {
-
- }
result.Add(backupSetInfo);
}
@@ -761,11 +790,21 @@ internal RestorePlan CreateRestorePlan(DatabaseRestorePlanner planner, RestoreOp
return ret;
}
+ private void ResetOptions()
+ {
+ isTailLogBackupPossible = null;
+ isTailLogBackupWithNoRecoveryPossible = null;
+ bool isTailLogBackupPossibleValue = IsTailLogBackupPossible;
+ bool isTailLogBackupWithNoRecoveryPossibleValue = isTailLogBackupPossibleValue;
+ }
+
///
/// Updates restore plan
///
public void UpdateRestorePlan()
{
+
+ ResetOptions();
this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated.
//Clear any existing exceptions as new plan is getting recreated.
this.CreateOrUpdateRestorePlanException = null;
@@ -781,6 +820,7 @@ public void UpdateRestorePlan()
{
this.RestorePlan = this.CreateRestorePlan(this.RestorePlanner, this.RestoreOptions);
this.Util.AddCredentialNameForUrlBackupSet(this.restorePlan, this.CredentialName);
+ RestoreOptionsHelper.UpdateOptionsInPlan(this);
if (this.ActiveException == null)
{
this.dbFiles = this.GetDbFiles();
@@ -979,6 +1019,7 @@ public void UpdateSelectedBackupSets()
// If the collection client sent is null, select everything; otherwise select the items that are selected in client
bool backupSetSelected = selectedBackupSetsFromClient == null || selectedBackupSetsFromClient.Any(x => BackUpSetGuidEqualsId(backupSet, x));
+
if (backupSetSelected)
{
AddBackupSetsToSelected(index, index);
@@ -1016,6 +1057,22 @@ public void UpdateSelectedBackupSets()
{
break;
}
+
+ //If the second item is not selected and it's a diff backup
+ if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential)
+ {
+ if (this.Server.Version.Major < 9 ||
+ (this.RestorePlan.RestoreOperations.Count >= 3 &&
+ BackupSet.IsBackupSetsInSequence(this.RestorePlan.RestoreOperations[0].BackupSet, this.RestorePlan.RestoreOperations[2].BackupSet)))
+ {
+ // only the item at index 1 won't be selected
+ }
+ else
+ {
+ // nothing after index 1 should be selected
+ break;
+ }
+ }
}
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs
new file mode 100644
index 0000000000..fbf31a6ac3
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs
@@ -0,0 +1,525 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Management.Smo;
+using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+using Microsoft.SqlTools.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
+{
+ ///
+ /// A factory class to create restore option info
+ ///
+ public class RestoreOptionFactory
+ {
+ private static RestoreOptionFactory instance = new RestoreOptionFactory();
+
+ Dictionary optionBuilders = new Dictionary();
+
+ ///
+ /// Singleton instance
+ ///
+ public static RestoreOptionFactory Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ ///
+ /// Create option info using the current values
+ ///
+ /// Option name
+ /// Restore task object
+ ///
+ public RestorePlanDetailInfo CreateOptionInfo(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject)
+ {
+ if(optionBuilders.ContainsKey(optionKey))
+ {
+ return Create(optionKey, restoreDataObject, optionBuilders[optionKey]);
+ }
+ else
+ {
+ Logger.Write(LogLevel.Warning, $"cannot find restore option builder for {optionKey}");
+ return null;
+ }
+ }
+
+ ///
+ /// Update the option info by validating the option
+ ///
+ ///
+ ///
+ ///
+ public void UpdateOption(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject, RestorePlanDetailInfo optionInfo)
+ {
+ if (optionBuilders.ContainsKey(optionKey))
+ {
+ var builder = optionBuilders[optionKey];
+ var currentValue = builder.CurrentValueFunction(restoreDataObject);
+ var defaultValue = builder.DefaultValueFunction(restoreDataObject);
+ var validateResult = builder.ValidateFunction(restoreDataObject, currentValue, defaultValue);
+ optionInfo.IsReadOnly = validateResult.IsReadOnly;
+ }
+ else
+ {
+ Logger.Write(LogLevel.Warning, $"cannot find restore option builder for {optionKey}");
+ }
+ }
+
+ ///
+ /// Set the option value if restore tak object using the values in the restore request
+ ///
+ ///
+ ///
+ public void SetValue(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject)
+ {
+ if(restoreDataObject != null)
+ {
+ if (optionBuilders.ContainsKey(optionKey))
+ {
+ var builder = optionBuilders[optionKey];
+ if (restoreDataObject.RestoreParams != null && restoreDataObject.RestoreParams.Options.ContainsKey(optionKey))
+ {
+ try
+ {
+ var value = restoreDataObject.RestoreParams.GetOptionValue