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>();
 }
Ejemplo n.º 2
0
        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;
        }
Ejemplo n.º 3
0
        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;
        }
Ejemplo n.º 4
0
        /// <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));
        }
Ejemplo n.º 5
0
        /// <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));
        }
Ejemplo n.º 6
0
 public ScriptInfo(DbVersion version)
 {
     Version = version;
 }
Ejemplo n.º 7
0
        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());
        }
Ejemplo n.º 8
0
        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());
        }
Ejemplo n.º 9
0
 /// <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);
 }
Ejemplo n.º 10
0
        /// <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}");
            }
        }