public async Task <IReadOnlyCollection <SelectedModuleMigrations> > SelectMigrationsAsync(DatabaseMigrationSpecifier[] modules, string[] tags) { try { var queuedMigrations = new List <DatabaseMigrationSpecifier>(); var result = new List <SelectedModuleMigrations>(); history = await migrationProvider.GetMigrationHistoryAsync(); // select migrations for modules, repeatable and unversioned last var sortedModules = modules.OrderBy(x => IsRepeatableMigration(x) ? 1 : 0); foreach (var module in sortedModules) { var migrations = await DoSelectMigrationsAsync(module, tags, queuedMigrations); if (migrations.Count > 0) { result.Add(new SelectedModuleMigrations(module, migrations)); } } return(result); } finally { history = null; } }
public async Task SelectMigrationsAsync_WithHistory() { migrationProvider.GetMigrationHistoryAsync() .Returns(new[] { new FakeDatabaseMigrationRecord() { ModuleName = "appModule1", Version = DatabaseVersion.Parse("1.0.1") }, new FakeDatabaseMigrationRecord() { ModuleName = "appModule1", Version = DatabaseVersion.Parse("1.0.0") }, new FakeDatabaseMigrationRecord() { ModuleName = "appModule2", Version = DatabaseVersion.Parse("1.0.2") } }); migrationRegistry.Migrations.Returns(new List <IDatabaseMigration>() { new FakeDatabaseMigration() { ModuleName = "appModule1", Version = DatabaseVersion.Parse("1.0.2") }, new FakeDatabaseMigration() { ModuleName = "appModule1", Version = DatabaseVersion.Parse("1.0.1") }, new FakeDatabaseMigration() { ModuleName = "appModule1", Version = DatabaseVersion.Parse("1.0.0") } }); var migrations = await sut.SelectMigrationsAsync( new[] { new DatabaseMigrationSpecifier("appModule1", null) }, new string[0]); migrations.Should().HaveCount(1); migrations.First().Specifier.Should().Be(new DatabaseMigrationSpecifier("appModule1", null)); migrations.First().Migrations.Should().Equal( migrationRegistry.Migrations.ElementAt(0)); }
private async Task <IReadOnlyCollection <IDatabaseMigration> > DoSelectMigrationsAsync(string moduleName, string[] tags, DatabaseVersion targetVersion, List <DatabaseMigrationSpecifier> queuedMigrations) { IEnumerable <IDatabaseMigration> moduleMigrations = migrationRegistry.Migrations .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .Where(x => x.Tags.All(tagGroup => tags.Any(tagGroup.Contains))); if (targetVersion != null) { moduleMigrations = moduleMigrations.Where(x => x.Version.CompareTo(targetVersion) <= 0); } moduleMigrations = moduleMigrations .OrderBy(x => x.Version) .ToArray(); if (targetVersion != null) { if (!Equals(moduleMigrations.LastOrDefault()?.Version, targetVersion)) { throw new DatabaseMigrationException($"Cannot select database migrations for module '{moduleName}': migration for required version {targetVersion} was not found"); } } else if (!moduleMigrations.Any()) { throw new DatabaseMigrationException($"Cannot select database migrations for module '{moduleName}': no version for specified module were found"); } List <IDatabaseMigration> result = new List <IDatabaseMigration>(); if (targetVersion == null && moduleMigrations.Last().IsRepeatable) { if (!queuedMigrations.Any(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase))) { history = history ?? await migrationProvider.GetMigrationHistoryAsync(); string lastMigrationChecksum = history .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .OrderByDescending(x => x.TimeApplied) .Select(x => x.Checksum).FirstOrDefault(); var migration = moduleMigrations.Last(); if (lastMigrationChecksum != migration.Checksum) { result.Add(migration); } } } else { var version = queuedMigrations .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .OrderByDescending(x => x.Version) .Select(x => x.Version) .FirstOrDefault(); if (version == null) { history = history ?? await migrationProvider.GetMigrationHistoryAsync(); var lastMigration = history .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .OrderByDescending(x => x.Version).FirstOrDefault(); version = lastMigration?.Version; } if (version == null) { var baseline = moduleMigrations.FirstOrDefault(x => x.IsBaseline); if (baseline != null) { result.Add(baseline); result.AddRange(moduleMigrations .Where(x => !x.IsRepeatable && x.Version.CompareTo(baseline.Version) > 0)); } else { result.AddRange(moduleMigrations.Where(x => !x.IsRepeatable)); } result.AddRange(moduleMigrations.Where(x => x.IsRepeatable)); } else { result.AddRange(moduleMigrations .Where(x => !x.IsRepeatable && !x.IsBaseline && x.Version.CompareTo(version) > 0)); if (result.Count > 0) { result.AddRange(moduleMigrations.Where(x => x.IsRepeatable)); } } } foreach (var migration in result) { queuedMigrations.Add(new DatabaseMigrationSpecifier(migration.ModuleName, migration.Version)); } await AddRequiredDependenciesAsync(result, queuedMigrations, tags); return(result); }
private async Task <IReadOnlyCollection <IDatabaseMigration> > DoSelectMigrationsAsync(string moduleName, string[] tags, DatabaseVersion targetVersion, List <DatabaseMigrationSpecifier> queuedMigrations) { IEnumerable <IDatabaseMigration> moduleMigrations = migrationRegistry.Migrations .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .Where(x => x.Tags.All(tagGroup => tags.Any(tagGroup.Contains))); if (targetVersion != null) { moduleMigrations = moduleMigrations.Where(x => x.Version.CompareTo(targetVersion) <= 0); } moduleMigrations = moduleMigrations .OrderBy(x => x.Version) .ToArray(); if (targetVersion != null) { if (moduleMigrations.Any() && moduleMigrations.Last().IsRepeatable) { throw new DatabaseMigrationException($"Cannot select database migrations for module '{moduleName}' version {targetVersion} because it is a repeatable migration module, which means it is versioned only by checksums"); } } else if (!moduleMigrations.Any()) { // TODO maybe return without errors if there are migrations for this module with different tags? throw new DatabaseMigrationException($"Cannot select database migrations for module '{moduleName}': no migrations for specified module were found"); } List <IDatabaseMigration> result = new List <IDatabaseMigration>(); if (moduleMigrations.Any() && moduleMigrations.Last().IsRepeatable) // repeatable-migration module { if (!queuedMigrations.Any(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase))) { history = history ?? await migrationProvider.GetMigrationHistoryAsync(); string lastMigrationChecksum = history .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .OrderByDescending(x => x.TimeApplied) .Select(x => x.Checksum).FirstOrDefault(); var migration = moduleMigrations.Last(); if (lastMigrationChecksum != migration.Checksum) { result.Add(migration); } } } else { var version = queuedMigrations .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .OrderByDescending(x => x.Version) .Select(x => x.Version) .FirstOrDefault(); if (version == null) { history = history ?? await migrationProvider.GetMigrationHistoryAsync(); var lastMigration = history .Where(x => string.Equals(x.ModuleName, moduleName, StringComparison.InvariantCultureIgnoreCase)) .OrderByDescending(x => x.Version).FirstOrDefault(); version = lastMigration?.Version; } if (version == null) { var baseline = moduleMigrations.FirstOrDefault(x => x.IsBaseline); if (baseline != null) { result.Add(baseline); result.AddRange(moduleMigrations .Where(x => x.Version.CompareTo(baseline.Version) > 0)); } else { result.AddRange(moduleMigrations); } } else { result.AddRange(moduleMigrations .Where(x => !x.IsBaseline && x.Version.CompareTo(version) > 0)); } if (targetVersion != null) { if ((version == null || version.CompareTo(targetVersion) < 0) && (!result.Any() || !Equals(result.LastOrDefault()?.Version, targetVersion))) { throw new DatabaseMigrationException($"Cannot select database migrations for module '{moduleName}': migration for required version {targetVersion} from {version?.ToString() ?? "nothing"} was not found"); } } else { var maxVersion = moduleMigrations.Select(x => x.Version).Max(); if ((!result.Any() && (version == null || version.CompareTo(maxVersion) < 0)) || (result.Any() && !result.Last().Version.Equals(maxVersion))) { throw new DatabaseMigrationException($"Cannot select database migrations for module '{moduleName}': migration path to required version 'latest' ({maxVersion}) from {version?.ToString() ?? "nothing"} was not found"); } } } foreach (var migration in result) { queuedMigrations.Add(new DatabaseMigrationSpecifier(migration.ModuleName, migration.Version)); } await AddRequiredDependenciesAsync(result, queuedMigrations, tags); return(result); }