Skip to content

Commit 47aaae4

Browse files
authored
feat(backup chain): new query (#97)
* feat(backup-chain): new query ordering
1 parent b9191b6 commit 47aaae4

13 files changed

Lines changed: 346 additions & 266 deletions

src/AgDatabase.cs

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,15 @@ public void Restore(IEnumerable<BackupMetadata> backupOrder, Func<int, TimeSpan>
123123
/// </summary>
124124
public List<BackupMetadata> RecentBackups()
125125
{
126+
// find most recent full backup LSN across all replica servers
127+
var fullBackupLsnBag = new ConcurrentBag<decimal>();
128+
_listener.ForEachAgInstance(s => fullBackupLsnBag.Add(s.Database(Name).MostRecentFullBackupLsn()));
129+
130+
// find all backups in that chain
131+
var databaseBackupLsn = fullBackupLsnBag.Max();
126132
var bag = new ConcurrentBag<BackupMetadata>();
127-
_listener.ForEachAgInstance(s => s.Database(Name).RecentBackups().ForEach(backup => bag.Add(backup)));
133+
_listener.ForEachAgInstance(s => s.Database(Name).BackupChainFromLsn(databaseBackupLsn)
134+
.ForEach(backup => bag.Add(backup)));
128135
return bag.ToList();
129136
}
130137

@@ -170,11 +177,11 @@ public void AddLogin(LoginProperties login)
170177
}
171178
}
172179

173-
private void AddNewSqlLogin(LoginProperties login)
180+
private void AddNewSqlLogin(LoginProperties login)
174181
{
175182
var createdLogin = _listener.Primary.AddLogin(login);
176183
login.Sid = createdLogin.Sid;
177-
Parallel.ForEach(_listener.Secondaries, server => server.AddLogin(login));
184+
Parallel.ForEach(_listener.Secondaries, server => server.AddLogin(login));
178185
}
179186

180187
public IEnumerable<RoleProperties> AssociatedRoles()
@@ -187,6 +194,28 @@ public void AddRole(LoginProperties login, RoleProperties role)
187194
_listener.ForEachAgInstance(server => server.AddRole(login, role));
188195
}
189196

197+
public void ContainsLogin(string loginName)
198+
{
199+
var exceptions = new ConcurrentQueue<Exception>();
200+
201+
_listener.ForEachAgInstance((s, ag) => {
202+
try {
203+
CheckLoginExists(s, ag, loginName);
204+
}
205+
catch(MissingLoginException ex) {
206+
exceptions.Enqueue(ex);
207+
}
208+
catch(MultipleLoginException ex) {
209+
exceptions.Enqueue(ex);
210+
}
211+
catch(MissingSidException ex) {
212+
exceptions.Enqueue(ex);
213+
}
214+
});
215+
216+
if(exceptions.Count > 0) throw new AggregateException(exceptions);
217+
}
218+
190219
/// <summary>
191220
/// IDisposable implemented for our connection to the primary AG database server.
192221
/// </summary>
@@ -241,43 +270,22 @@ public void CheckDBConnections(int connectionTimeout)
241270
{
242271
_listener.ForEachAgInstance(server => server.CheckDBConnection(Name, connectionTimeout));
243272
}
244-
273+
245274
private void CheckLoginExists(Server server, AvailabilityGroup availabilityGroup, string loginName)
246275
{
247276
var matchingLogins = server.Logins.Where(l => l.Name == loginName);
248-
249-
if (matchingLogins.Count() == 0)
250-
throw new MissingLoginException($"Login missing on {server.Name}, {_listener.AvailabilityGroup.Name}, {loginName}");
251277

252-
if (matchingLogins.Count() > 1)
278+
if(matchingLogins.Count() == 0)
279+
throw new
280+
MissingLoginException($"Login missing on {server.Name}, {_listener.AvailabilityGroup.Name}, {loginName}");
281+
282+
if(matchingLogins.Count() > 1)
253283
throw new
254284
MultipleLoginException($"Multiple logins exist on {server.Name}, {_listener.AvailabilityGroup.Name}, {loginName}");
255285

256286
var sid = matchingLogins.First().Sid;
257-
if (sid == null || sid.Length == 0)
287+
if(sid == null || sid.Length == 0)
258288
throw new MissingSidException($"Sid missing on {server.Name}, {_listener.AvailabilityGroup.Name}, {loginName}");
259289
}
260-
261-
public void ContainsLogin(string loginName)
262-
{
263-
var exceptions = new ConcurrentQueue<Exception>();
264-
265-
_listener.ForEachAgInstance((s, ag) => {
266-
try {
267-
CheckLoginExists(s, ag, loginName);
268-
}
269-
catch(MissingLoginException ex) {
270-
exceptions.Enqueue(ex);
271-
}
272-
catch(MultipleLoginException ex) {
273-
exceptions.Enqueue(ex);
274-
}
275-
catch(MissingSidException ex) {
276-
exceptions.Enqueue(ex);
277-
}
278-
});
279-
280-
if(exceptions.Count > 0) throw new AggregateException(exceptions);
281-
}
282290
}
283291
}

src/BackupChain.cs

Lines changed: 61 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
namespace AgDatabaseMove
2-
{
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using Exceptions;
6-
using SmoFacade;
7-
8-
1+
namespace AgDatabaseMove
2+
{
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using Exceptions;
6+
using SmoFacade;
7+
8+
99
public interface IBackupChain
1010
{
1111
IEnumerable<BackupMetadata> OrderedBackups { get; }
@@ -14,11 +14,11 @@ public interface IBackupChain
1414
/// <summary>
1515
/// Encapsulates the logic for determining the order to apply recent backups.
1616
/// </summary>
17-
public class BackupChain : IBackupChain
18-
{
19-
private readonly IList<BackupMetadata> _orderedBackups;
20-
21-
// This also handles any striped backups
17+
public class BackupChain : IBackupChain
18+
{
19+
private readonly IList<BackupMetadata> _orderedBackups;
20+
21+
// This also handles any striped backups
2222
private BackupChain(IList<BackupMetadata> recentBackups)
2323
{
2424
if(recentBackups == null || recentBackups.Count == 0)
@@ -49,52 +49,56 @@ public BackupChain(IAgDatabase agDatabase) : this(agDatabase.RecentBackups()) {
4949
/// <summary>
5050
/// Initializes a backup chain from a stand alone database that is not part of an AG.
5151
/// </summary>
52-
public BackupChain(Database database) : this(database.RecentBackups()) { }
52+
public BackupChain(Database database) : this(database.MostRecentBackupChain()) { }
5353

5454
/// <summary>
5555
/// Backups ordered to have a full restore chain.
5656
/// </summary>
57-
public IEnumerable<BackupMetadata> OrderedBackups => _orderedBackups;
58-
59-
private static IEnumerable<BackupMetadata> MostRecentFullBackup(IEnumerable<BackupMetadata> backups)
60-
{
61-
var fullBackupsOrdered = backups
62-
.Where(b => b.BackupType == BackupFileTools.BackupType.Full)
63-
.OrderByDescending(d => d.CheckpointLsn).ToList();
64-
65-
if(!fullBackupsOrdered.Any()) throw new BackupChainException("Could not find any full backups");
66-
67-
var targetCheckpointLsn = fullBackupsOrdered.First().CheckpointLsn;
68-
// get all the stripes of this backup
69-
return fullBackupsOrdered.Where(fullBackup => fullBackup.CheckpointLsn == targetCheckpointLsn);
70-
}
71-
72-
private static IEnumerable<BackupMetadata> MostRecentDiffBackup(IEnumerable<BackupMetadata> backups,
73-
BackupMetadata lastFullBackup)
74-
{
75-
var diffBackupsOrdered = backups
76-
.Where(b => b.BackupType == BackupFileTools.BackupType.Diff &&
77-
b.DatabaseBackupLsn == lastFullBackup.CheckpointLsn)
78-
.OrderByDescending(b => b.LastLsn).ToList();
79-
80-
if(!diffBackupsOrdered.Any()) return new List<BackupMetadata>();
81-
var targetLastLsn = diffBackupsOrdered.First().LastLsn;
82-
// get all the stripes of this backup
83-
return diffBackupsOrdered.Where(diffBackup => diffBackup.LastLsn == targetLastLsn);
84-
}
85-
86-
private static IEnumerable<BackupMetadata> NextLogBackup(IEnumerable<BackupMetadata> backups,
87-
BackupMetadata prevBackup)
88-
{
89-
// also gets all the stripes of the next backup
90-
return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Log &&
91-
prevBackup.LastLsn >= b.FirstLsn && prevBackup.LastLsn + 1 < b.LastLsn);
92-
}
93-
94-
private static bool IsValidFilePath(BackupMetadata meta)
95-
{
96-
var path = meta.PhysicalDeviceName;
97-
return BackupFileTools.IsValidFileUrl(path) || BackupFileTools.IsValidFilePath(path);
98-
}
99-
}
57+
public IEnumerable<BackupMetadata> OrderedBackups => _orderedBackups;
58+
59+
private static IEnumerable<BackupMetadata> MostRecentFullBackup(IEnumerable<BackupMetadata> backups)
60+
{
61+
var fullBackupsOrdered = backups
62+
.Where(b => b.BackupType == BackupFileTools.BackupType.Full)
63+
.OrderByDescending(d => d.CheckpointLsn).ToList();
64+
65+
if(!fullBackupsOrdered.Any())
66+
throw new BackupChainException("Could not find any full backups");
67+
68+
var targetCheckpointLsn = fullBackupsOrdered.First().CheckpointLsn;
69+
// get all the stripes of this backup
70+
return fullBackupsOrdered.Where(fullBackup => fullBackup.CheckpointLsn == targetCheckpointLsn);
71+
}
72+
73+
private static IEnumerable<BackupMetadata> MostRecentDiffBackup(IEnumerable<BackupMetadata> backups,
74+
BackupMetadata lastFullBackup)
75+
{
76+
var diffBackupsOrdered = backups
77+
.Where(b =>
78+
b.BackupType == BackupFileTools.BackupType.Diff &&
79+
b.DatabaseBackupLsn == lastFullBackup.CheckpointLsn)
80+
.OrderByDescending(b => b.LastLsn).ToList();
81+
82+
if(!diffBackupsOrdered.Any())
83+
return new List<BackupMetadata>();
84+
85+
var targetLastLsn = diffBackupsOrdered.First().LastLsn;
86+
// get all the stripes of this backup
87+
return diffBackupsOrdered.Where(diffBackup => diffBackup.LastLsn == targetLastLsn);
88+
}
89+
90+
private static IEnumerable<BackupMetadata> NextLogBackup(IEnumerable<BackupMetadata> backups,
91+
BackupMetadata prevBackup)
92+
{
93+
// also gets all the stripes of the next backup
94+
return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Log &&
95+
prevBackup.LastLsn >= b.FirstLsn && prevBackup.LastLsn + 1 < b.LastLsn);
96+
}
97+
98+
private static bool IsValidFilePath(BackupMetadata meta)
99+
{
100+
var path = meta.PhysicalDeviceName;
101+
return BackupFileTools.IsValidFileUrl(path) || BackupFileTools.IsValidFilePath(path);
102+
}
103+
}
100104
}
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
namespace AgDatabaseMove.Exceptions
2-
{
1+
namespace AgDatabaseMove.Exceptions
2+
{
33
using System;
44

55

66
/// <summary>
77
/// A base exception for the AgDatabaseMove library
88
/// </summary>
9-
public class AgDatabaseMoveException : Exception
10-
{
11-
public AgDatabaseMoveException() { }
12-
13-
public AgDatabaseMoveException(string message) : base(message) { }
14-
15-
public AgDatabaseMoveException(string message, Exception innerException) : base(message, innerException) { }
16-
}
9+
public class AgDatabaseMoveException : Exception
10+
{
11+
public AgDatabaseMoveException() { }
12+
13+
public AgDatabaseMoveException(string message) : base(message) { }
14+
15+
public AgDatabaseMoveException(string message, Exception innerException) : base(message, innerException) { }
16+
}
1717
}

src/Exceptions/AgJoinException.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
namespace AgDatabaseMove.Exceptions
2-
{
3-
public class AgJoinException : AgDatabaseMoveException
4-
{
5-
public AgJoinException(string message) : base(message) { }
6-
}
1+
namespace AgDatabaseMove.Exceptions
2+
{
3+
public class AgJoinException : AgDatabaseMoveException
4+
{
5+
public AgJoinException(string message) : base(message) { }
6+
}
77
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
namespace AgDatabaseMove.Exceptions
2-
{
3-
public class BackupChainException : AgDatabaseMoveException
4-
{
5-
public BackupChainException(string message) : base(message) { }
6-
}
1+
namespace AgDatabaseMove.Exceptions
2+
{
3+
public class BackupChainException : AgDatabaseMoveException
4+
{
5+
public BackupChainException(string message) : base(message) { }
6+
}
77
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
namespace AgDatabaseMove.Exceptions
2-
{
3-
public class InvalidBackupException : AgDatabaseMoveException
4-
{
5-
public InvalidBackupException(string message) : base(message) { }
6-
}
1+
namespace AgDatabaseMove.Exceptions
2+
{
3+
public class InvalidBackupException : AgDatabaseMoveException
4+
{
5+
public InvalidBackupException(string message) : base(message) { }
6+
}
77
}

src/Exceptions/MissingLoginException.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace AgDatabaseMove.Exceptions
22
{
3-
43
public class MissingLoginException : AgDatabaseMoveException
54
{
65
public MissingLoginException(string message) : base(message) { }

src/Exceptions/MissingSidException.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace AgDatabaseMove.Exceptions
22
{
3-
43
public class MissingSidException : AgDatabaseMoveException
54
{
65
public MissingSidException(string message) : base(message) { }

src/Exceptions/MultipleLoginException.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace AgDatabaseMove.Exceptions
22
{
3-
43
public class MultipleLoginException : AgDatabaseMoveException
54
{
65
public MultipleLoginException(string message) : base(message) { }

0 commit comments

Comments
 (0)