예제 #1
0
        ///<inheritdoc/>
        public override void RunSqlScripts(
            IDbConnection connection,
            IDbTransaction transaction,
            NonTransactionalContext nonTransactionalContext,
            string version,
            string workingPath,
            string scriptDirectory,
            string metaSchemaName,
            string metaTableName,
            List <KeyValuePair <string, string> > tokenKeyPairs = null,
            int?commandTimeout          = null,
            string environmentCode      = null,
            string appliedByTool        = null,
            string appliedByToolVersion = null
            )
        {
            //extract and filter out scripts when environment code is used
            var sqlScriptFiles = _directoryService.GetFiles(scriptDirectory, "*.sql").ToList();

            sqlScriptFiles = _directoryService.FilterFiles(workingPath, environmentCode, sqlScriptFiles).ToList();
            _traceService.Info($"Found {sqlScriptFiles.Count} script files on {workingPath}" + (sqlScriptFiles.Count > 0 ? Environment.NewLine : string.Empty) +
                               $"{string.Join(Environment.NewLine, sqlScriptFiles.Select(s => "  + " + new FileInfo(s).Name))}");

            //execute all script files in the version folder
            sqlScriptFiles.Sort();
            sqlScriptFiles
            .ForEach(scriptFile =>
            {
                var sqlStatementRaw = _fileService.ReadAllText(scriptFile);
                var sqlStatements   = _dataService.BreakStatements(sqlStatementRaw)
                                      .Where(s => !string.IsNullOrWhiteSpace(s))
                                      .ToList();
                ;
                sqlStatements.ForEach(sqlStatement =>
                {
                    try
                    {
                        sqlStatement = _tokenReplacementService.Replace(tokenKeyPairs, sqlStatement);
                        _traceService.Debug($"Executing sql statement as part of : {scriptFile}");
                        _configurationDataService.ExecuteSql(
                            connection: connection,
                            commandText: sqlStatement,
                            transaction: transaction,
                            commandTimeout: commandTimeout,
                            traceService: _traceService);
                    }
                    catch (Exception)
                    {
                        _traceService.Error($"Failed to execute sql statements in script file {scriptFile}.{Environment.NewLine}" +
                                            $"The failing statement starts here --------------------------{Environment.NewLine}" +
                                            $"{sqlStatement} {Environment.NewLine}" +
                                            $"The failing statement ends here --------------------------");
                        throw;
                    }
                });

                _traceService.Info($"Executed script file {scriptFile}.");
            });
        }
 public abstract void RunSqlScripts(
     IDbConnection connection,
     IDbTransaction transaction,
     NonTransactionalContext nonTransactionalContext,
     string version,
     string workingPath,
     string scriptDirectory,
     string metaSchemaName,
     string metaTableName,
     List <KeyValuePair <string, string> > tokenKeyPairs = null,
     int?commandTimeout          = null,
     string environmentCode      = null,
     string appliedByTool        = null,
     string appliedByToolVersion = null
     );
 public abstract void RunVersionScripts(
     IDbConnection connection,
     IDbTransaction transaction,
     List <string> dbVersions,
     string workingPath,
     string targetVersion,
     NonTransactionalContext nonTransactionalContext,
     List <KeyValuePair <string, string> > tokenKeyPairs = null,
     string bulkSeparator        = null,
     string metaschemaName       = null,
     string metaTableName        = null,
     int?commandTimeout          = null,
     int?bulkBatchSize           = null,
     string appliedByTool        = null,
     string appliedByToolVersion = null,
     string environmentCode      = null
     );
예제 #4
0
        private void RunSqlScripts(
            IDbConnection connection,
            IDbTransaction transaction,
            NonTransactionalContext nonTransactionalContext,
            string version,
            string workingPath,
            string scriptDirectory,
            string schemaName,
            string tableName,
            List <KeyValuePair <string, string> > tokenKeyPairs = null,
            int?commandTimeout          = null,
            string environmentCode      = null,
            string appliedByTool        = null,
            string appliedByToolVersion = null
            )
        {
            string currentScriptFile = null;

            try
            {
                //TODO: Filter out scripts when environment code is used
                var sqlScriptFiles = _directoryService.GetFiles(scriptDirectory, "*.sql").ToList();
                sqlScriptFiles = _directoryService.FilterFiles(workingPath, environmentCode, sqlScriptFiles).ToList();
                _traceService.Info($"Found the {sqlScriptFiles.Count} script files on {scriptDirectory}");
                _traceService.Info($"{string.Join(@"\r\n\t", sqlScriptFiles.Select(s => new FileInfo(s).Name))}");

                //execute all script files in the version folder
                sqlScriptFiles.Sort();
                sqlScriptFiles
                .ForEach(scriptFile =>
                {
                    currentScriptFile = scriptFile;

                    //In case the non-transactional failure is resolved, skip scripts
                    if (nonTransactionalContext?.ResolvingOption == NonTransactionalResolvingOption.ContinueAfterFailure &&
                        !nonTransactionalContext.IsFailedScriptPathMatched)
                    {
                        //Set failed script file as matched
                        if (string.Equals(scriptFile, nonTransactionalContext.FailedScriptPath, StringComparison.InvariantCultureIgnoreCase))
                        {
                            nonTransactionalContext.SetFailedScriptPathMatch();
                        }
                        _traceService.Info($"Skipping script file {scriptFile} ...");
                    }
                    else //otherwise execute them
                    {
                        var sqlStatementRaw = _fileService.ReadAllText(scriptFile);
                        var sqlStatements   = _dataService.BreakStatements(sqlStatementRaw)
                                              .Where(s => !string.IsNullOrWhiteSpace(s))
                                              .ToList();
                        ;
                        sqlStatements.ForEach(sqlStatement =>
                        {
                            sqlStatement = _tokenReplacementService.Replace(tokenKeyPairs, sqlStatement);

                            _traceService.Debug($"Executing sql statement as part of : {scriptFile}{Environment.NewLine}{sqlStatement}");
                            _configurationDataService.ExecuteSql(
                                connection: connection,
                                commandText: sqlStatement,
                                transaction: transaction,
                                commandTimeout: commandTimeout,
                                traceService: _traceService);
                        });

                        _traceService.Info($"Executed script file {scriptFile}.");
                    }
                });
            }
            catch (Exception exc)
            {
                //Try parse the known sql error
                string sqlError;
                if (!_dataService.TryParseErrorFromException(exc, out sqlError))
                {
                    //if not sucesfull, use the whole exception
                    sqlError = exc.ToString();
                }

                //in case scripts are not executed within transaction, mark version as failed in database
                if (transaction == null)
                {
                    //update db version to failed
                    _configurationDataService.InsertVersion(connection, transaction, version,
                                                            schemaName,
                                                            tableName,
                                                            commandTimeout: commandTimeout,
                                                            appliedByTool: appliedByTool,
                                                            appliedByToolVersion: appliedByToolVersion,
                                                            currentScriptFile,
                                                            sqlError);

                    _traceService.Error(@$ "Migration of " "{version}" " version was not running in transaction and has failed when executing of script file " "{currentScriptFile}" " with following error:
{sqlError}
{ManualResolvingAfterFailureMessage}");
                }
                else
                {
                    _traceService.Error(@$ "Migration of " "{version}" " version was running in transaction and has failed when executing of script file " "{currentScriptFile}" " with following error:
{sqlError}
");
                }

                throw exc;
            }
        }
예제 #5
0
        private void RunVersionScripts(
            IDbConnection connection,
            IDbTransaction transaction,
            List <string> dbVersions,
            string workingPath,
            string targetVersion,
            NonTransactionalContext nonTransactionalContext,
            List <KeyValuePair <string, string> > tokenKeyPairs = null,
            string delimiter            = null,
            string schemaName           = null,
            string tableName            = null,
            int?commandTimeout          = null,
            int?batchSize               = null,
            string appliedByTool        = null,
            string appliedByToolVersion = null,
            string environmentCode      = null
            )
        {
            //excludes all versions already executed
            var versionDirectories = _directoryService.GetDirectories(workingPath, "v*.*")
                                     .Where(v => !dbVersions.Contains(new DirectoryInfo(v).Name))
                                     .ToList();

            //exclude all versions greater than the target version
            if (!string.IsNullOrEmpty(targetVersion))
            {
                versionDirectories.RemoveAll(v =>
                {
                    var cv = new LocalVersion(new DirectoryInfo(v).Name);
                    var tv = new LocalVersion(targetVersion);

                    return(string.Compare(cv.SemVersion, tv.SemVersion) == 1);
                });
            }

            //execute all sql scripts in the version folders
            if (versionDirectories.Any())
            {
                versionDirectories.Sort();
                versionDirectories.ForEach(versionDirectory =>
                {
                    try
                    {
                        //run scripts in all sub-directories
                        List <string> scriptSubDirectories = null;

                        //check for transaction directory
                        bool isExplicitTransactionDefined = false;
                        string transactionDirectory       = Path.Combine(versionDirectory, "_transaction");
                        if (_directoryService.Exists(transactionDirectory))
                        {
                            if (_dataService.IsAtomicDDLSupported)
                            {
                                throw new YuniqlMigrationException(@$ "The version directory " "{versionDirectory}" " can't contain " "_transaction" " subdirectory for selected target platform, because the whole migration is already running in single transaction.");
                            }
                            if (_directoryService.GetDirectories(versionDirectory, "*").Count() > 1)
                            {
                                throw new YuniqlMigrationException(@$ "The version directory " "{versionDirectory}" " containing " "_transaction" " subdirectory can't contain other subdirectories");
                            }
                            else if (_directoryService.GetFiles(versionDirectory, "*.*").Count() > 0)
                            {
                                throw new YuniqlMigrationException(@$ "The version directory " "{versionDirectory}" " containing " "_transaction" " subdirectory can't contain files");
                            }

                            isExplicitTransactionDefined = true;
                            scriptSubDirectories         = _directoryService.GetAllDirectories(transactionDirectory, "*").ToList();
                        }
                        else
                        {
                            scriptSubDirectories = _directoryService.GetAllDirectories(versionDirectory, "*").ToList();
                        }

                        if (isExplicitTransactionDefined)
                        {
                            string versionName = new DirectoryInfo(versionDirectory).Name;

                            //run scripts within a single transaction
                            _traceService.Info(@$ "The " "_transaction" " directory has been detected and therefore " "{versionName}" " version scripts will run in single transaction. The rollback will not be reliable in case the version scripts contain commands causing implicit commit (e.g. DDL)!");

                            using (var transaction = connection.BeginTransaction())
                            {
                                try
                                {
                                    RunVersionScriptsInternal(transaction, scriptSubDirectories, transactionDirectory, versionDirectory, schemaName, tableName);

                                    transaction.Commit();

                                    _traceService.Info(@$ "Target database has been commited after running " "{versionName}" " version scripts.");
                                }
                                catch (Exception)
                                {
                                    _traceService.Error(@$ "Target database will be rolled back to the state before running " "{versionName}" " version scripts.");
                                    transaction.Rollback();
                                    throw;
                                }
                            }
                        }
                        else //run scripts without transaction
                        {
                            RunVersionScriptsInternal(transaction, scriptSubDirectories, versionDirectory, versionDirectory, schemaName, tableName);
                        }
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                });
            }
            else
            {
                var connectionInfo = _dataService.GetConnectionInfo();
                _traceService.Info($"Target database is updated. No migration step executed at {connectionInfo.Database} on {connectionInfo.DataSource}.");
            }

            void RunVersionScriptsInternal(IDbTransaction transaction, List <string> scriptSubDirectories, string scriptDirectory, string versionDirectory, string schemaName, string tableName)
            {
                try
                {
                    string versionName = new DirectoryInfo(versionDirectory).Name;

                    scriptSubDirectories.Sort();
                    scriptSubDirectories.ForEach(scriptSubDirectory =>
                    {
                        //run all scripts in the current version folder
                        RunSqlScripts(connection, transaction, nonTransactionalContext, versionName, workingPath, scriptSubDirectory, schemaName, tableName, tokenKeyPairs, commandTimeout, environmentCode, appliedByTool, appliedByToolVersion);

                        //import csv files into tables of the the same filename as the csv
                        RunBulkImport(connection, transaction, workingPath, scriptSubDirectory, delimiter, batchSize, commandTimeout, environmentCode);
                    });

                    //run all scripts in the current version folder
                    RunSqlScripts(connection, transaction, nonTransactionalContext, versionName, workingPath, scriptDirectory, schemaName, tableName, tokenKeyPairs, commandTimeout, environmentCode, appliedByTool, appliedByToolVersion);

                    //import csv files into tables of the the same filename as the csv
                    RunBulkImport(connection, transaction, workingPath, scriptDirectory, delimiter, batchSize, commandTimeout, environmentCode);

                    //update db version
                    _configurationDataService.InsertVersion(connection, transaction, versionName,
                                                            schemaName: schemaName,
                                                            tableName: tableName,
                                                            commandTimeout: commandTimeout,
                                                            appliedByTool: appliedByTool,
                                                            appliedByToolVersion: appliedByToolVersion);

                    _traceService.Info($"Completed migration to version {versionDirectory}");
                }
                finally
                {
                    //Clear nontransactional context to ensure it is not applied on next version
                    nonTransactionalContext = null;
                }
            }
        }
예제 #6
0
        /// <summary>
        /// Runs migrations by executing alls scripts in the workspace directory.
        /// When CSV files are present also run bulk import operations to target database table having same file name.
        /// </summary>
        /// <param name="workingPath">The directory path to migration project.</param>
        /// <param name="targetVersion">The maximum version to run to. When NULL, runs migration to the latest version found in the workspace path.</param>
        /// <param name="autoCreateDatabase">When TRUE, creates the database in the target host.</param>
        /// <param name="tokenKeyPairs">Token kev/value pairs to replace tokens in script files.</param>
        /// <param name="verifyOnly">When TRUE, runs the migration in uncommitted mode. No changes are committed to target database. When NULL, runs migration in atomic mode.</param>
        /// <param name="delimiter">Delimeter character in the CSV bulk import files. When NULL, uses comma.</param>
        /// <param name="schemaName">Schema name for schema versions table. When empty, uses the default schema in the target data platform. </param>
        /// <param name="tableName">Table name for schema versions table. When empty, uses __yuniqldbversion.</param>
        /// <param name="commandTimeout">Command timeout in seconds. When NULL, it uses default provider command timeout.</param>
        /// <param name="batchSize">Batch rows to processed when performing bulk import. When NULL, it uses default provider batch size.</param>
        /// <param name="appliedByTool">The source that initiates the migration. This can be yuniql-cli, yuniql-aspnetcore or yuniql-azdevops.</param>
        /// <param name="appliedByToolVersion">The version of the source that initiates the migration.</param>
        /// <param name="environmentCode">Environment code for environment-aware scripts.</param>
        /// <param name="nonTransactionalResolvingOption">The non-transactional resolving option.</param>
        public void Run(
            string workingPath,
            string targetVersion    = null,
            bool?autoCreateDatabase = false,
            List <KeyValuePair <string, string> > tokenKeyPairs = null,
            bool?verifyOnly             = false,
            string delimiter            = null,
            string schemaName           = null,
            string tableName            = null,
            int?commandTimeout          = null,
            int?batchSize               = null,
            string appliedByTool        = null,
            string appliedByToolVersion = null,
            string environmentCode      = null,
            NonTransactionalResolvingOption?nonTransactionalResolvingOption = null
            )
        {
            if (_dataService.IsAtomicDDLSupported && nonTransactionalResolvingOption != null)
            {
                throw new NotSupportedException(@$ "The non-transactional failure resolving option " "{nonTransactionalResolvingOption}" " is not available for this platform.");
            }

            //validate workspace structure
            _localVersionService.Validate(workingPath);

            //when uncomitted run is not supported, fail migration and throw exceptions
            if (verifyOnly.HasValue && verifyOnly == true && !_dataService.IsAtomicDDLSupported)
            {
                throw new NotSupportedException("Yuniql.Verify is not supported in the target platform. " +
                                                "The feature requires support for atomic DDL operations. " +
                                                "An atomic DDL operations ensures creation of tables, views and other objects and data are rolledback in case of error. " +
                                                "For more information see https://yuniql.io/docs/.");
            }

            //when no target version specified, we use the latest local version
            if (string.IsNullOrEmpty(targetVersion))
            {
                targetVersion = _localVersionService.GetLatestVersion(workingPath);
                _traceService.Info($"No explicit target version requested. We'll use latest available locally {targetVersion} on {workingPath}.");
            }

            var connectionInfo       = _dataService.GetConnectionInfo();
            var targetDatabaseName   = connectionInfo.Database;
            var targetDatabaseServer = connectionInfo.DataSource;

            //we try to auto-create the database, we need this to be outside of the transaction scope
            //in an event of failure, users have to manually drop the auto-created database!
            //we only check if the db exists when --auto-create-db is true
            if (autoCreateDatabase.HasValue && autoCreateDatabase == true)
            {
                var targetDatabaseExists = _configurationDataService.IsDatabaseExists();
                if (!targetDatabaseExists)
                {
                    _traceService.Info($"Target database does not exist. Creating database {targetDatabaseName} on {targetDatabaseServer}.");
                    _configurationDataService.CreateDatabase();
                    _traceService.Info($"Created database {targetDatabaseName} on {targetDatabaseServer}.");
                }
            }

            //check if database has been pre-configured to support migration and setup when its not
            var targetDatabaseConfigured = _configurationDataService.IsDatabaseConfigured(schemaName, tableName);

            if (!targetDatabaseConfigured)
            {
                //create custom schema when user supplied and only if platform supports it
                if (_dataService.IsSchemaSupported && null != schemaName && !_dataService.SchemaName.Equals(schemaName))
                {
                    _traceService.Info($"Target schema does not exist. Creating schema {schemaName} on {targetDatabaseName} on {targetDatabaseServer}.");
                    _configurationDataService.CreateSchema(schemaName);
                    _traceService.Info($"Created schema {schemaName} on {targetDatabaseName} on {targetDatabaseServer}.");
                }

                //create empty versions tracking table
                _traceService.Info($"Target database {targetDatabaseName} on {targetDatabaseServer} not yet configured for migration.");
                _configurationDataService.ConfigureDatabase(schemaName, tableName);
                _traceService.Info($"Configured database migration support for {targetDatabaseName} on {targetDatabaseServer}.");
            }
            else
            {
                bool databaseupdated = _configurationDataService.UpdateDatabaseConfiguration();

                if (databaseupdated)
                {
                    _traceService.Info($"The configuration of migration has been updated for {targetDatabaseName} on {targetDatabaseServer}.");
                }
                else
                {
                    _traceService.Debug($"The configuration of migration is up to date for {targetDatabaseName} on {targetDatabaseServer}.");
                }
            }

            IList <DbVersion> allVersions = _configurationDataService.GetAllVersions(schemaName, tableName);

            NonTransactionalContext nonTransactionalContext = null;

            //Check for failed not transactional versions
            if (!_dataService.IsAtomicDDLSupported)
            {
                DbVersion failedDbVersion = allVersions.Where(x => x.StatusId == StatusId.Failed).FirstOrDefault();

                if (failedDbVersion != null)
                {
                    if (nonTransactionalResolvingOption == null)
                    {
                        _traceService.Error(@$ "Previous migration of " "{failedDbVersion.Version}" " version was not running in transaction and has failed when executing of script " "{failedDbVersion.FailedScriptPath}" " with following error:
{failedDbVersion.FailedScriptError}
{ManualResolvingAfterFailureMessage}");

                        //Program should exit with non zero exit code
                        throw new InvalidOperationException();
                    }

                    _traceService.Info($@"The non-transactional failure resolving option ""{nonTransactionalResolvingOption}"" was used. Version scripts already applied by previous migration run will be skipped.");

                    nonTransactionalContext = new NonTransactionalContext(failedDbVersion, nonTransactionalResolvingOption.Value);
                }
                else
                {
                    if (nonTransactionalResolvingOption != null)
                    {
                        _traceService.Error(@$ "The non-transactional failure resolving option " "{nonTransactionalResolvingOption}" " is available only if previous migration has failed.");

                        //Program should exit with non zero exit code
                        throw new InvalidOperationException();
                    }
                }
            }

            var dbVersions = _configurationDataService.GetAllAppliedVersions(schemaName, tableName)

                             .Select(dv => dv.Version)
                             .OrderBy(v => v)
                             .ToList();

            //checks if target database already runs the latest version and skips work if it already is
            var targeDatabaseLatest = IsTargetDatabaseLatest(targetVersion, schemaName, tableName);

            if (!targeDatabaseLatest)
            {
                //create a shared open connection to entire migration run
                using (var connection = _dataService.CreateConnection())
                {
                    connection.Open();

                    //enclose all executions in a single transaction in case platform supports it
                    if (_dataService.IsAtomicDDLSupported)
                    {
                        _traceService.Debug(@$ "Target platform fully supports transactions. Migration will run in single transaction.");
                        using (var transaction = connection.BeginTransaction())
                        {
                            try
                            {
                                RunInternal(connection, transaction);

                                //when true, the execution is an uncommitted transaction
                                //and only for purpose of testing if all can go well when it run to the target environment
                                if (verifyOnly.HasValue && verifyOnly == true)
                                {
                                    transaction.Rollback();
                                }
                                else
                                {
                                    transaction.Commit();
                                }
                            }
                            catch (Exception)
                            {
                                _traceService.Error("Target database will be rolled back to its previous state.");
                                transaction.Rollback();
                                throw;
                            }
                        }
                    }
                    else //otherwise don't use transactions
                    {
                        _traceService.Info(@$ "Target platform doesn't reliably support transactions for all commands. Migration will not run in single transaction. Any failure during the migration will require manual resolution.");

                        try
                        {
                            RunInternal(connection, null);
                        }
                        catch (Exception)
                        {
                            _traceService.Error($"Migration was not running in transaction, therefore roll back of target database to the state before the migration is not possible.");
                            throw;
                        }
                    }
                }
            }

            else //runs all scripts files inside _draft on every yuniql run regardless of state of target database
            {
                //enclose all executions in a single transaction
                using (var connection = _dataService.CreateConnection())
                {
                    connection.Open();

                    //enclose all executions in a single transaction in case platform supports it
                    if (_dataService.IsAtomicDDLSupported)
                    {
                        _traceService.Debug(@$ "Target platform fully supports transactions. Migration will run in single transaction.");
                        using (var transaction = connection.BeginTransaction())
                        {
                            try
                            {
                                RunDraftInternal(connection, transaction);
                                transaction.Commit();
                            }
                            catch (Exception)
                            {
                                transaction.Rollback();
                                throw;
                            }
                        }
                    }
                    else //otherwise don't use transactions
                    {
                        try
                        {
                            _traceService.Info($"Target platform doesn't reliably support transactions for all commands. " +
                                               $"Migration will not run in single transaction. " +
                                               $"Any failure during the migration can prevent automatic completing of migration.");
                            RunDraftInternal(connection, null);
                        }
                        catch (Exception)
                        {
                            _traceService.Error("Migration was not running in transaction, therefore roll back of target database to its previous state is not possible. " +
                                                "Migration need to be completed manually! Running of Yuniql again, might cause that some scripts will be executed twice!");
                            throw;
                        }
                    }
                    _traceService.Info($"Target database runs the latest version already. Scripts in _pre, _draft and _post are executed.");
                }
            }

            //local method
            void RunInternal(IDbConnection connection, IDbTransaction transaction)
            {
                //check if database has been pre-configured and execute init scripts
                if (!targetDatabaseConfigured)
                {
                    //runs all scripts in the _init folder
                    RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_init"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                    _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_init")}");
                }

                //checks if target database already runs the latest version and skips work if it already is
                //runs all scripts in the _pre folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_pre"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_pre")}");

                //runs all scripts int the vxx.xx folders and subfolders
                RunVersionScripts(connection, transaction, dbVersions, workingPath, targetVersion, nonTransactionalContext, tokenKeyPairs, delimiter: delimiter, schemaName: schemaName, tableName: tableName, commandTimeout: commandTimeout, batchSize: batchSize, appliedByTool: appliedByTool, appliedByToolVersion: appliedByToolVersion, environmentCode: environmentCode);

                //runs all scripts in the _draft folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_draft"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_draft")}");

                //runs all scripts in the _post folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_post"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_post")}");
            }

            void RunDraftInternal(IDbConnection connection, IDbTransaction transaction)
            {
                //runs all scripts in the _pre folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_pre"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_pre")}");

                //runs all scripts in the _draft folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_draft"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_draft")}");

                //runs all scripts in the _post folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_post"), tokenKeyPairs, delimiter: delimiter, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_post")}");
            }
        }
예제 #7
0
        ///<inheritdoc/>
        public override void RunVersionScripts(
            IDbConnection connection,
            IDbTransaction transaction,
            List <string> dbVersions,
            string workingPath,
            string targetVersion,
            NonTransactionalContext nonTransactionalContext,
            List <KeyValuePair <string, string> > tokenKeyPairs = null,
            string bulkSeparator        = null,
            string metaSchemaName       = null,
            string metaTableName        = null,
            int?commandTimeout          = null,
            int?bulkBatchSize           = null,
            string appliedByTool        = null,
            string appliedByToolVersion = null,
            string environmentCode      = null
            )
        {
            //excludes all versions already executed
            var versionDirectories = _directoryService.GetDirectories(workingPath, "v*.*")
                                     .Where(v => !dbVersions.Contains(new DirectoryInfo(v).Name))
                                     .ToList();

            //exclude all versions greater than the target version
            if (!string.IsNullOrEmpty(targetVersion))
            {
                versionDirectories.RemoveAll(v =>
                {
                    var cv = new LocalVersion(new DirectoryInfo(v).Name);
                    var tv = new LocalVersion(targetVersion);

                    return(string.Compare(cv.SemVersion, tv.SemVersion) == 1);
                });
            }

            //execute all sql scripts in the version folders
            if (versionDirectories.Any())
            {
                versionDirectories.Sort();
                versionDirectories.ForEach(versionDirectory =>
                {
                    var versionName = new DirectoryInfo(versionDirectory).Name;

                    //run scripts in all sub-directories
                    var scriptSubDirectories = _directoryService.GetAllDirectories(versionDirectory, "*").ToList();
                    scriptSubDirectories.Sort();
                    scriptSubDirectories.ForEach(scriptSubDirectory =>
                    {
                        //run all scripts in the current version folder
                        RunSqlScripts(connection, transaction, nonTransactionalContext, versionName, workingPath, scriptSubDirectory, metaSchemaName, metaTableName, tokenKeyPairs, commandTimeout, environmentCode);

                        //import csv files into tables of the the same filename as the csv
                        RunBulkImport(connection, transaction, workingPath, scriptSubDirectory, bulkSeparator, bulkBatchSize, commandTimeout, environmentCode);
                    });

                    //run all scripts in the current version folder
                    RunSqlScripts(connection, transaction, nonTransactionalContext, versionName, workingPath, versionDirectory, metaSchemaName, metaTableName, tokenKeyPairs, commandTimeout, environmentCode);

                    //import csv files into tables of the the same filename as the csv
                    RunBulkImport(connection, transaction, workingPath, versionDirectory, bulkSeparator, bulkBatchSize, commandTimeout, environmentCode);

                    //update db version
                    _configurationDataService.InsertVersion(connection, transaction, versionName,
                                                            metaSchemaName: metaSchemaName,
                                                            metaTableName: metaTableName,
                                                            commandTimeout: commandTimeout,
                                                            appliedByTool: appliedByTool,
                                                            appliedByToolVersion: appliedByToolVersion);

                    _traceService.Info($"Completed migration to version {versionDirectory}");
                });
            }
            else
            {
                var connectionInfo = _dataService.GetConnectionInfo();
                _traceService.Info($"Target database is updated. No migration step executed at {connectionInfo.Database} on {connectionInfo.DataSource}.");
            }
        }
예제 #8
0
        /// <inheritdoc />
        public override void Run(
            string workingPath,
            string targetVersion    = null,
            bool?autoCreateDatabase = false,
            List <KeyValuePair <string, string> > tokenKeyPairs = null,
            bool?verifyOnly             = false,
            string bulkSeparator        = null,
            string metaSchemaName       = null,
            string metaTableName        = null,
            int?commandTimeout          = null,
            int?bulkBatchSize           = null,
            string appliedByTool        = null,
            string appliedByToolVersion = null,
            string environmentCode      = null,
            NonTransactionalResolvingOption?nonTransactionalResolvingOption = null,
            bool noTransaction = false
            )
        {
            if (_dataService.IsAtomicDDLSupported && nonTransactionalResolvingOption != null)
            {
                throw new NotSupportedException(@$ "The non-transactional failure resolving option " "{nonTransactionalResolvingOption}" " is not available for this platform.");
            }

            //check the workspace structure if required directories are present
            _localVersionService.Validate(workingPath);

            //when uncomitted run is not supported, fail migration, throw exceptions and return error exit code
            if (verifyOnly.HasValue && verifyOnly == true && !_dataService.IsAtomicDDLSupported)
            {
                throw new NotSupportedException("Yuniql.Verify is not supported in the target platform. " +
                                                "The feature requires support for atomic DDL operations. " +
                                                "An atomic DDL operations ensures creation of tables, views and other objects and data are rolledback in case of error. " +
                                                "For more information see https://yuniql.io/docs/.");
            }

            //when no target version specified, we use the latest local version
            if (string.IsNullOrEmpty(targetVersion))
            {
                targetVersion = _localVersionService.GetLatestVersion(workingPath);
                _traceService.Info($"No explicit target version requested. We'll use latest available locally {targetVersion} on {workingPath}.");
            }

            var connectionInfo       = _dataService.GetConnectionInfo();
            var targetDatabaseName   = connectionInfo.Database;
            var targetDatabaseServer = connectionInfo.DataSource;

            //we try to auto-create the database, we need this to be outside of the transaction scope
            //in an event of failure, users have to manually drop the auto-created database!
            //we only check if the db exists when --auto-create-db is true
            if (autoCreateDatabase.HasValue && autoCreateDatabase == true)
            {
                var targetDatabaseExists = _configurationDataService.IsDatabaseExists();
                if (!targetDatabaseExists)
                {
                    _traceService.Info($"Target database does not exist. Creating database {targetDatabaseName} on {targetDatabaseServer}.");
                    _configurationDataService.CreateDatabase();
                    _traceService.Info($"Created database {targetDatabaseName} on {targetDatabaseServer}.");
                }
            }

            //check if database has been pre-configured to support migration and setup when its not
            var targetDatabaseConfigured = _configurationDataService.IsDatabaseConfigured(metaSchemaName, metaTableName);

            if (!targetDatabaseConfigured)
            {
                //create custom schema when user supplied and only if platform supports it
                if (_dataService.IsSchemaSupported && null != metaSchemaName && !_dataService.SchemaName.Equals(metaSchemaName))
                {
                    _traceService.Info($"Target schema does not exist. Creating schema {metaSchemaName} on {targetDatabaseName} on {targetDatabaseServer}.");
                    _configurationDataService.CreateSchema(metaSchemaName);
                    _traceService.Info($"Created schema {metaSchemaName} on {targetDatabaseName} on {targetDatabaseServer}.");
                }

                //create empty versions tracking table
                _traceService.Info($"Target database {targetDatabaseName} on {targetDatabaseServer} not yet configured for migration.");
                _configurationDataService.ConfigureDatabase(metaSchemaName, metaTableName);
                _traceService.Info($"Configured database migration support for {targetDatabaseName} on {targetDatabaseServer}.");
            }

            var targetDatabaseUpdated = _configurationDataService.UpdateDatabaseConfiguration(metaSchemaName, metaTableName);

            if (targetDatabaseUpdated)
            {
                _traceService.Info($"The configuration of migration has been updated for {targetDatabaseName} on {targetDatabaseServer}.");
            }
            else
            {
                _traceService.Debug($"The configuration of migration is up to date for {targetDatabaseName} on {targetDatabaseServer}.");
            }

            NonTransactionalContext nonTransactionalContext = null;

            //check for presence of failed no-transactional versions from previous runs
            var allVersions   = _configurationDataService.GetAllVersions(metaSchemaName, metaTableName);
            var failedVersion = allVersions.Where(x => x.Status == Status.Failed).FirstOrDefault();

            if (failedVersion != null)
            {
                //check if user had issue resolving option such as continue on failure
                if (nonTransactionalResolvingOption == null)
                {
                    //program should exit with non zero exit code
                    var message = @$ "Previous migration of " "{failedVersion.Version}" " version was not running in transaction and has failed when executing of script " "{failedVersion.FailedScriptPath}" " with following error: {failedVersion.FailedScriptError} {MESSAGES.ManualResolvingAfterFailureMessage}";
                    _traceService.Error(message);
                    throw new InvalidOperationException(message);
                }

                _traceService.Info($@"The non-transactional failure resolving option ""{nonTransactionalResolvingOption}"" was used. Version scripts already applied by previous migration run will be skipped.");
                nonTransactionalContext = new NonTransactionalContext(failedVersion, nonTransactionalResolvingOption.Value);
            }
            else
            {
                if (nonTransactionalResolvingOption != null)
                {
                    //program should exit with non zero exit code
                    _traceService.Error(@$ "The non-transactional failure resolving option " "{nonTransactionalResolvingOption}" " is available only if previous migration has failed.");
                    throw new InvalidOperationException();
                }
            }

            var appliedVersions = _configurationDataService.GetAllAppliedVersions(metaSchemaName, metaTableName)
                                  .Select(dv => dv.Version)
                                  .OrderBy(v => v)
                                  .ToList();

            //checks if target database already runs the latest version and skips work if it already is
            var targeDatabaseLatest = IsTargetDatabaseLatest(targetVersion, metaSchemaName, metaTableName);

            if (!targeDatabaseLatest)
            {
                //create a shared open connection to entire migration run
                using (var connection = _dataService.CreateConnection())
                {
                    connection.Open();
                    RunAllInternal(connection, null);
                }
            }
            else
            {
                //runs all scripts files inside _draft on every yuniql run regardless of state of target database
                using (var connection = _dataService.CreateConnection())
                {
                    connection.Open();
                    RunDraftInternal(connection, null);
                }
            }

            //local method
            void RunAllInternal(IDbConnection connection, IDbTransaction transaction)
            {
                //check if database has been pre-configured and execute init scripts
                if (!targetDatabaseConfigured)
                {
                    //runs all scripts in the _init folder
                    RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_init"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                    _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_init")}");
                }

                //checks if target database already runs the latest version and skips work if it already is
                //runs all scripts in the _pre folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_pre"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_pre")}");

                //runs all scripts int the vxx.xx folders and subfolders
                RunVersionScripts(connection, transaction, appliedVersions, workingPath, targetVersion, nonTransactionalContext, tokenKeyPairs, bulkSeparator: bulkSeparator, metaSchemaName: metaSchemaName, metaTableName: metaTableName, commandTimeout: commandTimeout, bulkBatchSize: bulkBatchSize, appliedByTool: appliedByTool, appliedByToolVersion: appliedByToolVersion, environmentCode: environmentCode);

                //runs all scripts in the _draft folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_draft"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_draft")}");

                //runs all scripts in the _post folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_post"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_post")}");
            }

            //local method
            void RunDraftInternal(IDbConnection connection, IDbTransaction transaction)
            {
                //runs all scripts in the _pre folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_pre"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_pre")}");

                //runs all scripts in the _draft folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_draft"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_draft")}");

                //runs all scripts in the _post folder and subfolders
                RunNonVersionScripts(connection, transaction, Path.Combine(workingPath, "_post"), tokenKeyPairs, bulkSeparator: bulkSeparator, commandTimeout: commandTimeout, environmentCode: environmentCode);
                _traceService.Info($"Executed script files on {Path.Combine(workingPath, "_post")}");
            }
        }