public DowngradableScriptMigration( ILogger?migrationLogger, IDbProvider dbProvider, DbVersion version, ICollection <ScriptMigrationBatch> upScripts, ICollection <ScriptMigrationBatch>?downScripts, string?comment, bool isTransactionRequired = true) : base(migrationLogger, dbProvider, version, upScripts, comment, isTransactionRequired) { DownScripts = downScripts?.ToArray() ?? Array.Empty <ScriptMigrationBatch>(); }
public ScriptMigration( IDbProvider dbProvider, DbVersion version, string upScript, string downScript, string comment) { _dbProvider = dbProvider ?? throw new ArgumentNullException(nameof(dbProvider)); if (String.IsNullOrWhiteSpace(upScript)) { throw new ArgumentException(nameof(upScript)); } Version = version; UpScript = upScript; DownScript = downScript; Comment = comment; }
public ScriptMigration( ILogger?migrationLogger, IDbProvider dbProvider, DbVersion version, ICollection <ScriptMigrationBatch> upScripts, string?comment, bool isTransactionRequired = true) { MigrationLogger = migrationLogger; DbProvider = dbProvider ?? throw new ArgumentNullException(nameof(dbProvider)); if (upScripts == null || upScripts.Count == 0) { throw new ArgumentException(nameof(upScripts)); } Version = version; UpScripts = upScripts.ToArray(); Comment = comment; IsTransactionRequired = isTransactionRequired; }
/// <summary> /// Creates script migration. Replace variables placeholders with real values /// </summary> /// <param name="dbVersion"></param> /// <param name="scriptInfo"></param> /// <param name="dbProvider"></param> /// <param name="variables"></param> /// <param name="migrationLogger"></param> /// <returns></returns> private IMigration CreateScriptMigration( DbVersion dbVersion, ScriptInfo scriptInfo, IDbProvider dbProvider, IReadOnlyDictionary <string, string> variables, ILogger?migrationLogger) { var upScript = scriptInfo.UpScript; var downScript = scriptInfo.DownScript; foreach (var keyValuePair in variables) { foreach (var batch in upScript) { batch.Script = batch.Script.Replace(keyValuePair.Key, keyValuePair.Value); } foreach (var batch in downScript) { batch.Script = batch.Script.Replace(keyValuePair.Key, keyValuePair.Value); } } return(downScript.Count > 0 ? new DowngradableScriptMigration( migrationLogger, dbProvider, dbVersion, upScript, downScript, scriptInfo.Comment, scriptInfo.Options.IsTransactionRequired) : new ScriptMigration( migrationLogger, dbProvider, dbVersion, upScript, scriptInfo.Comment, scriptInfo.Options.IsTransactionRequired)); }
/// <summary> /// Creates script migration. Replace variables placeholders with real values /// </summary> /// <param name="dbVersion"></param> /// <param name="scriptInfo"></param> /// <param name="dbProvider"></param> /// <param name="variables"></param> /// <returns></returns> private IMigration CreateScriptMigration( DbVersion dbVersion, ScriptInfo scriptInfo, IDbProvider dbProvider, IReadOnlyDictionary <string, string> variables) { var upScript = scriptInfo.UpScript; var downScript = scriptInfo.DownScript; foreach (var keyValuePair in variables) { upScript = upScript.Replace(keyValuePair.Key, keyValuePair.Value); downScript = downScript?.Replace(keyValuePair.Key, keyValuePair.Value); } return(new ScriptMigration( dbProvider, dbVersion, upScript, downScript, scriptInfo.Comment)); }
public ScriptInfo(DbVersion version) { Version = version; }
private ICollection <IMigration> GetMigrations( IEnumerable <string> fileNames, Func <string, string> sqlScriptReadFunc, IDbProvider dbProvider, string prefix, IReadOnlyDictionary <string, string> variables) { if (fileNames == null) { throw new ArgumentNullException(nameof(fileNames)); } if (sqlScriptReadFunc == null) { throw new ArgumentNullException(nameof(sqlScriptReadFunc)); } if (dbProvider == null) { throw new ArgumentNullException(nameof(dbProvider)); } var scripts = new Dictionary <DbVersion, ScriptInfo>(); var regex = String.IsNullOrWhiteSpace(prefix) ? new Regex($"[./]{MigrationConstants.MigrationFileNamePattern}", RegexOptions.IgnoreCase) : new Regex($"{prefix}[-.]{MigrationConstants.MigrationFileNamePattern}", RegexOptions.IgnoreCase); foreach (var fileName in fileNames) { var match = regex.Match(fileName); if (!match.Success) { continue; } var majorVersion = match.Groups[1]; var minorVersion = match.Groups[2]; var version = new DbVersion(int.Parse(majorVersion.Value), int.Parse(minorVersion.Value)); if (!scripts.ContainsKey(version)) { scripts[version] = new ScriptInfo(version); } var scriptInfo = scripts[version]; var script = sqlScriptReadFunc.Invoke(fileName); if (match.Groups[4].Success) { if (!String.IsNullOrWhiteSpace(scriptInfo.DownScript)) { throw new InvalidOperationException( $"There is more than one downgrade script with version {version}"); } scriptInfo.DownScript = script; } else { if (!String.IsNullOrWhiteSpace(scriptInfo.UpScript)) { throw new InvalidOperationException( $"There is more than one upgrade script with version {version}"); } scriptInfo.UpScript = script; var comment = match.Groups[7]; scriptInfo.Comment = comment.Success ? comment.Value : null; } } return(scripts .Select(scriptInfo => CreateScriptMigration( scriptInfo.Key, scriptInfo.Value, dbProvider, variables)) .ToList()); }
private ICollection <IMigration> GetMigrations( IEnumerable <string> fileNames, Func <string, string> sqlScriptReadFunc, IDbProvider dbProvider, string?prefix, IReadOnlyDictionary <string, string> variables, ILogger?migrationLogger) { if (fileNames == null) { throw new ArgumentNullException(nameof(fileNames)); } if (sqlScriptReadFunc == null) { throw new ArgumentNullException(nameof(sqlScriptReadFunc)); } if (dbProvider == null) { throw new ArgumentNullException(nameof(dbProvider)); } var scripts = new Dictionary <DbVersion, ScriptInfo>(); var regex = String.IsNullOrWhiteSpace(prefix) ? new Regex($"{MigrationConstants.MigrationFileNamePattern}", RegexOptions.IgnoreCase) : new Regex($"{prefix}{MigrationConstants.MigrationFileNamePattern}", RegexOptions.IgnoreCase); foreach (var fileName in fileNames) { var match = regex.Match(fileName); if (!match.Success) { // it's normal that some files doesn't match a regex, but we log, it's safer migrationLogger?.LogDebug($"\"{fileName}\" has incorrect name for script migration."); continue; } if (!DbVersion.TryParse(match.Groups[1].Value, out var version)) { migrationLogger?.LogWarning($"\"{fileName}\" has incorrect version migration."); continue; } if (!scripts.ContainsKey(version)) { scripts[version] = new ScriptInfo(version); } var scriptInfo = scripts[version]; var script = sqlScriptReadFunc.Invoke(fileName); // extract options for current migration scriptInfo.Options = ExtractMigrationOptions(script); // split into batches var batches = new List <ScriptMigrationBatch>(); var batchNameRegex = new Regex(@"--\s*BATCH:\s*(.*)\s*\n(.*)", RegexOptions.IgnoreCase); var batchIndex = 0; // Use positive lookahead to split script into batches. foreach (var batch in Regex.Split(script, @"(?=--\s*BATCH:)")) { if (String.IsNullOrWhiteSpace(batch)) { continue; } var batchNameMatch = batchNameRegex.Match(batch); batches.Add(new ScriptMigrationBatch( batchIndex++, batchNameMatch.Success ? batchNameMatch.Groups[1].Value : null, batch)); } if (match.Groups[6].Success) { if (scriptInfo.DownScript.Count > 0) { throw new InvalidOperationException( $"There is more than one downgrade script with version {version}"); } scriptInfo.DownScript.AddRange(batches); } else { if (scriptInfo.UpScript.Count > 0) { throw new InvalidOperationException( $"There is more than one upgrade script with version {version}"); } scriptInfo.UpScript.AddRange(batches); } var comment = match.Groups[9]; scriptInfo.Comment = comment.Success ? comment.Value : null; } return(scripts .Select(scriptInfo => CreateScriptMigration( scriptInfo.Key, scriptInfo.Value, dbProvider, variables, migrationLogger)) .ToArray()); }
/// <summary> /// Setup target version of migration /// </summary> /// <param name="targetDbVersion">Target database version</param> /// <returns></returns> /// <remarks> /// If <paramref name="targetDbVersion"></paramref> is not specified, migrator will upgrade database to the most newest migration, provided by <see cref="IMigrationsProvider"/> /// If <paramref name="targetDbVersion"></paramref> is specified, migrator will upgrade or downgrade database depending on the current DB version and the specified /// </remarks> public MigratorBuilder SetUpTargetVersion(DbVersion targetDbVersion) { _targetVersion = targetDbVersion; return(this); }
/// <summary> /// Downgrade database to specific version /// </summary> /// <param name="actualVersion"></param> /// <param name="targetVersion"></param> /// <returns></returns> /// <exception cref="MigrationException"></exception> private async Task DowngradeAsync(DbVersion actualVersion, DbVersion targetVersion) { var desiredMigrations = _migrations .Where(x => x.Version <= actualVersion && x.Version >= targetVersion) .OrderByDescending(x => x.Version) .ToList(); if (desiredMigrations.Count == 0 || desiredMigrations.Count == 1) { return; } var lastMigrationVersion = new DbVersion(0, 0); var currentDbVersion = actualVersion; for (var i = 0; i < desiredMigrations.Count - 1; ++i) { var targetLocalVersion = desiredMigrations[i + 1].Version; var migration = desiredMigrations[i]; if (!IsMigrationAllowed(DbVersion.GetDifference(currentDbVersion, targetLocalVersion), _downgradePolicy)) { throw new MigrationException(MigrationError.PolicyError, $"Policy restrict downgrade to {targetLocalVersion}. Migration comment: {migration.Comment}"); } // sometimes transactions fails without reopening connection //todo fix it later await _dbProvider.CloseConnectionAsync(); await _dbProvider.OpenConnectionAsync(); using (var transaction = _dbProvider.BeginTransaction()) { try { _logger?.LogInformation( $"Downgrade to {desiredMigrations[i + 1].Version} (DB {_dbProvider.DbName})..."); await desiredMigrations[i].DowngradeAsync(transaction); await _dbProvider.UpdateCurrentDbVersionAsync(targetLocalVersion); lastMigrationVersion = targetLocalVersion; currentDbVersion = targetLocalVersion; // Commit transaction if all commands succeed, transaction will auto-rollback // when disposed if either commands fails transaction.Commit(); _logger?.LogInformation( $"Downgrade to {targetLocalVersion} (DB {_dbProvider.DbName}) completed."); } catch (Exception e) { _logger?.LogError(e, $"Error while downgrade to {migration.Version}: {e.Message}"); throw; } } } if (lastMigrationVersion != targetVersion) { throw new MigrationException( MigrationError.MigratingError, $"Can not downgrade database to version {targetVersion}. Last executed migration is {lastMigrationVersion}"); } }