@Test public void failed() { StringLogCreator logCreator = new StringLogCreator(); LogFactory.setLogCreator(logCreator); try { Flyway flyway = new Flyway(); flyway.setDataSource("jdbc:h2:mem:flyway_failed;DB_CLOSE_DELAY=-1", "sa", ""); flyway.setLocations("migration/failed"); flyway.migrate(); fail(); } catch (FlywayException e) { System.out.println(logCreator.getOutput()); } finally { LogFactory.setLogCreator(null); } }
@Override public void execute() throws BuildException { LogFactory.setLogCreator(new AntLogCreator(getProject())); log = LogFactory.getLog(getClass()); prepareClassPath(); try { Flyway flyway = new Flyway(); flyway.setDataSource(createDataSource()); String schemasValue = useValueIfPropertyNotSet(schemas, "schemas"); if (schemasValue != null) { flyway.setSchemas(StringUtils.tokenizeToStringArray(schemasValue, ",")); } String tableValue = useValueIfPropertyNotSet(table, "table"); if (tableValue != null) { flyway.setTable(tableValue); } String initialVersionValue = useValueIfPropertyNotSet(initialVersion, "initialVersion"); if (initialVersionValue != null) { flyway.setInitialVersion(initialVersionValue); } String initialDescriptionValue = useValueIfPropertyNotSet(initialDescription, "initialDescription"); if (initialDescriptionValue != null) { flyway.setInitialDescription(initialDescriptionValue); } String initVersionValue = useValueIfPropertyNotSet(initVersion, "initVersion"); if (initVersionValue != null) { flyway.setInitVersion(initVersionValue); } String initDescriptionValue = useValueIfPropertyNotSet(initDescription, "initDescription"); if (initDescriptionValue != null) { flyway.setInitDescription(initDescriptionValue); } doExecute(flyway); } catch (Exception e) { throw new BuildException("Flyway Error: " + e.toString(), ExceptionUtils.getRootCause(e)); } }
/** Supports reading and writing to the metadata table. */ public class MetaDataTableImpl implements MetaDataTable { private static final Log LOG = LogFactory.getLog(MetaDataTableImpl.class); /** Flag indicating whether the upgrade has already been executed. */ private boolean upgraded; /** Database-specific functionality. */ private final DbSupport dbSupport; /** The metadata table used by flyway. */ private final Table table; /** The migration resolver. */ private final MigrationResolver migrationResolver; /** JdbcTemplate with ddl manipulation access to the database. */ private final JdbcTemplate jdbcTemplate; /** * Creates a new instance of the metadata table support. * * @param dbSupport Database-specific functionality. * @param table The metadata table used by flyway. * @param migrationResolver For resolving available migrations. */ public MetaDataTableImpl(DbSupport dbSupport, Table table, MigrationResolver migrationResolver) { this.jdbcTemplate = dbSupport.getJdbcTemplate(); this.dbSupport = dbSupport; this.table = table; this.migrationResolver = migrationResolver; } /** Creates the metatable if it doesn't exist, upgrades it if it does. */ private void createIfNotExists() { if (table.existsNoQuotes() || table.exists()) { if (!upgraded) { new MetaDataTableTo20FormatUpgrader(dbSupport, table, migrationResolver).upgrade(); new MetaDataTableTo202FormatUpgrader(dbSupport, table).upgrade(); upgraded = true; } return; } LOG.info("Creating Metadata table: " + table); final String source = new ClassPathResource(dbSupport.getScriptLocation() + "createMetaDataTable.sql") .loadAsString("UTF-8"); Map<String, String> placeholders = new HashMap<String, String>(); placeholders.put("schema", table.getSchema().getName()); placeholders.put("table", table.getName()); final String sourceNoPlaceholders = new PlaceholderReplacer(placeholders, "${", "}").replacePlaceholders(source); SqlScript sqlScript = new SqlScript(sourceNoPlaceholders, dbSupport); sqlScript.execute(jdbcTemplate); LOG.debug("Metadata table " + table + " created."); } public void lock() { createIfNotExists(); table.lock(); } public void addAppliedMigration(AppliedMigration appliedMigration) { createIfNotExists(); MigrationVersion version = appliedMigration.getVersion(); try { int versionRank = calculateVersionRank(version); jdbcTemplate.update( "UPDATE " + table + " SET " + dbSupport.quote("version_rank") + " = " + dbSupport.quote("version_rank") + " + 1 WHERE " + dbSupport.quote("version_rank") + " >= ?", versionRank); jdbcTemplate.update( "INSERT INTO " + table + " (" + dbSupport.quote("version_rank") + "," + dbSupport.quote("installed_rank") + "," + dbSupport.quote("version") + "," + dbSupport.quote("description") + "," + dbSupport.quote("type") + "," + dbSupport.quote("script") + "," + dbSupport.quote("checksum") + "," + dbSupport.quote("installed_by") + "," + dbSupport.quote("execution_time") + "," + dbSupport.quote("success") + ")" + " VALUES (?, ?, ?, ?, ?, ?, ?, " + dbSupport.getCurrentUserFunction() + ", ?, ?)", versionRank, calculateInstalledRank(), version.toString(), appliedMigration.getDescription(), appliedMigration.getType().name(), appliedMigration.getScript(), appliedMigration.getChecksum(), appliedMigration.getExecutionTime(), appliedMigration.isSuccess()); LOG.debug("MetaData table " + table + " successfully updated to reflect changes"); } catch (SQLException e) { throw new FlywayException( "Unable to insert row for version '" + version + "' in metadata table " + table, e); } } /** * Calculates the installed rank for the new migration to be inserted. * * @return The installed rank. */ private int calculateInstalledRank() throws SQLException { int currentMax = jdbcTemplate.queryForInt( "SELECT MAX(" + dbSupport.quote("installed_rank") + ")" + " FROM " + table); return currentMax + 1; } /** * Calculate the rank for this new version about to be inserted. * * @param version The version to calculated for. * @return The rank. */ private int calculateVersionRank(MigrationVersion version) throws SQLException { List<String> versions = jdbcTemplate.queryForStringList("select " + dbSupport.quote("version") + " from " + table); List<MigrationVersion> migrationVersions = new ArrayList<MigrationVersion>(); for (String versionStr : versions) { migrationVersions.add(new MigrationVersion(versionStr)); } Collections.sort(migrationVersions); for (int i = 0; i < migrationVersions.size(); i++) { if (version.compareTo(migrationVersions.get(i)) < 0) { return i + 1; } } return migrationVersions.size() + 1; } public List<AppliedMigration> allAppliedMigrations() { if (!table.existsNoQuotes() && !table.exists()) { return new ArrayList<AppliedMigration>(); } createIfNotExists(); String query = "SELECT " + dbSupport.quote("version_rank") + "," + dbSupport.quote("installed_rank") + "," + dbSupport.quote("version") + "," + dbSupport.quote("description") + "," + dbSupport.quote("type") + "," + dbSupport.quote("script") + "," + dbSupport.quote("checksum") + "," + dbSupport.quote("installed_on") + "," + dbSupport.quote("installed_by") + "," + dbSupport.quote("execution_time") + "," + dbSupport.quote("success") + " FROM " + table + " ORDER BY " + dbSupport.quote("version_rank"); try { return jdbcTemplate.query( query, new RowMapper<AppliedMigration>() { public AppliedMigration mapRow(final ResultSet rs) throws SQLException { return new AppliedMigration( rs.getInt("version_rank"), rs.getInt("installed_rank"), new MigrationVersion(rs.getString("version")), rs.getString("description"), MigrationType.valueOf(rs.getString("type")), rs.getString("script"), toInteger((Number) rs.getObject("checksum")), rs.getTimestamp("installed_on"), rs.getString("installed_by"), toInteger((Number) rs.getObject("execution_time")), rs.getBoolean("success")); } }); } catch (SQLException e) { throw new FlywayException( "Error while retrieving the list of applied migrations from metadata table " + table, e); } } /** * Converts this number into an Integer. * * @param number The Number to convert. * @return The matching Integer. */ private Integer toInteger(Number number) { if (number == null) { return null; } return number.intValue(); } public MigrationVersion getCurrentSchemaVersion() { if (!table.existsNoQuotes() && !table.exists()) { return MigrationVersion.EMPTY; } try { if (jdbcTemplate.queryForInt("SELECT COUNT(*) FROM " + table) == 0) { return MigrationVersion.EMPTY; } } catch (SQLException e) { throw new FlywayException( "Error checking if the metadata table " + table + " has at least one row", e); } createIfNotExists(); // Determine the version associated with the highest version_rank String query = "SELECT t1." + dbSupport.quote("version") + " FROM " + table + " t1" + " LEFT OUTER JOIN " + table + " t2 ON" + " (t1." + dbSupport.quote("version") + " = t2." + dbSupport.quote("version") + " AND t1." + dbSupport.quote("version_rank") + " < t2." + dbSupport.quote("version_rank") + ")" + " WHERE t2." + dbSupport.quote("version") + " IS NULL"; try { String version = jdbcTemplate.queryForString(query); return new MigrationVersion(version); } catch (SQLException e) { throw new FlywayException( "Error determining current schema version from metadata table " + table, e); } } public void init(final MigrationVersion initVersion, final String initDescription) { addAppliedMigration( new AppliedMigration( initVersion, initDescription, MigrationType.INIT, initDescription, null, 0, true)); } public void repair() { if (!table.existsNoQuotes() && !table.exists()) { LOG.info( "Repair of metadata table " + table + " not necessary. No failed migration detected."); return; } createIfNotExists(); try { int failedCount = jdbcTemplate.queryForInt( "SELECT COUNT(*) FROM " + table + " WHERE " + dbSupport.quote("success") + "=" + dbSupport.getBooleanFalse()); if (failedCount == 0) { LOG.info( "Repair of metadata table " + table + " not necessary. No failed migration detected."); return; } } catch (SQLException e) { throw new FlywayException( "Unable to check the metadata table " + table + " for failed migrations", e); } StopWatch stopWatch = new StopWatch(); stopWatch.start(); try { jdbcTemplate.execute( "DELETE FROM " + table + " WHERE " + dbSupport.quote("success") + " = " + dbSupport.getBooleanFalse()); } catch (SQLException e) { throw new FlywayException("Unable to repair metadata table " + table, e); } stopWatch.stop(); LOG.info( "Metadata table " + table + " successfully repaired (execution time " + TimeFormat.format(stopWatch.getTotalTimeMillis()) + ")."); LOG.info("Manual cleanup of the remaining effects the failed migration may still be required."); } public void addSchemasMarker(final Schema[] schemas) { createIfNotExists(); addAppliedMigration( new AppliedMigration( new MigrationVersion("0"), "<< Flyway Schema Creation >>", MigrationType.SCHEMA, StringUtils.arrayToCommaDelimitedString(schemas), null, 0, true)); } public boolean hasSchemasMarker() { if (!table.existsNoQuotes() && !table.exists()) { return false; } createIfNotExists(); try { int count = jdbcTemplate.queryForInt( "SELECT COUNT(*) FROM " + table + " WHERE " + dbSupport.quote("type") + "='SCHEMA'"); return count > 0; } catch (SQLException e) { throw new FlywayException( "Unable to check whether the metadata table " + table + " has a schema marker migration", e); } } public boolean hasInitMarker() { if (!table.existsNoQuotes() && !table.exists()) { return false; } createIfNotExists(); try { int count = jdbcTemplate.queryForInt( "SELECT COUNT(*) FROM " + table + " WHERE " + dbSupport.quote("type") + "='INIT'"); return count > 0; } catch (SQLException e) { throw new FlywayException( "Unable to check whether the metadata table " + table + " has an init marker migration", e); } } public boolean hasAppliedMigrations() { if (!table.existsNoQuotes() && !table.exists()) { return false; } createIfNotExists(); try { int count = jdbcTemplate.queryForInt( "SELECT COUNT(*) FROM " + table + " WHERE " + dbSupport.quote("type") + " NOT IN ('SCHEMA', 'INIT')"); return count > 0; } catch (SQLException e) { throw new FlywayException( "Unable to check whether the metadata table " + table + " has applied migrations", e); } } @Override public String toString() { return table.toString(); } }
/** Oracle implementation of Schema. */ public class OracleSchema extends Schema { private static final Log LOG = LogFactory.getLog(OracleSchema.class); /** * Creates a new Oracle schema. * * @param jdbcTemplate The Jdbc Template for communicating with the DB. * @param dbSupport The database-specific support. * @param name The name of the schema. */ public OracleSchema(JdbcTemplate jdbcTemplate, DbSupport dbSupport, String name) { super(jdbcTemplate, dbSupport, name); } @Override protected boolean doExists() throws SQLException { return jdbcTemplate.queryForInt("SELECT COUNT(*) FROM all_users WHERE username=?", name) > 0; } @Override protected boolean doEmpty() throws SQLException { return jdbcTemplate.queryForInt("SELECT count(*) FROM all_objects WHERE owner = ?", name) == 0; } @Override protected void doCreate() throws SQLException { jdbcTemplate.execute("CREATE USER " + dbSupport.quote(name) + " IDENTIFIED BY flyway"); jdbcTemplate.execute("GRANT RESOURCE TO " + dbSupport.quote(name)); } @Override protected void doDrop() throws SQLException { jdbcTemplate.execute("DROP USER " + dbSupport.quote(name) + " CASCADE"); } @Override protected void doClean() throws SQLException { if ("SYSTEM".equals(name.toUpperCase())) { throw new FlywayException( "Clean not supported on Oracle for user 'SYSTEM'! You should NEVER add your own objects to the SYSTEM schema!"); } jdbcTemplate.execute("PURGE RECYCLEBIN"); for (String statement : generateDropStatementsForSpatialExtensions()) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForQueueTables()) { // for dropping queue tables, a special grant is required: // GRANT EXECUTE ON DBMS_AQADM TO flyway; jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("SEQUENCE", "")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("FUNCTION", "")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("MATERIALIZED VIEW", "PRESERVE TABLE")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("PACKAGE", "")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("PROCEDURE", "")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("SYNONYM", "")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("TRIGGER", "")) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("VIEW", "CASCADE CONSTRAINTS")) { jdbcTemplate.execute(statement); } for (Table table : allTables()) { table.drop(); } for (String statement : generateDropStatementsForXmlTables()) { jdbcTemplate.execute(statement); } for (String statement : generateDropStatementsForObjectType("TYPE", "FORCE")) { jdbcTemplate.execute(statement); } } /** * Generates the drop statements for all xml tables. * * @return The complete drop statements, ready to execute. * @throws SQLException when the drop statements could not be generated. */ private List<String> generateDropStatementsForXmlTables() throws SQLException { List<String> dropStatements = new ArrayList<String>(); if (!xmlDBExtensionsAvailable()) { LOG.debug("Oracle XML DB Extensions are not available. No cleaning of XML tables."); return dropStatements; } List<String> objectNames = jdbcTemplate.queryForStringList( "SELECT table_name FROM all_xml_tables WHERE owner = ?", name); for (String objectName : objectNames) { dropStatements.add("DROP TABLE " + dbSupport.quote(name, objectName) + " PURGE"); } return dropStatements; } /** * Checks whether Oracle XML DB extensions are available or not. * * @return {@code true} if they are available, {@code false} if not. * @throws SQLException when checking availability of the extensions failed. */ private boolean xmlDBExtensionsAvailable() throws SQLException { return (jdbcTemplate.queryForInt("SELECT COUNT(*) FROM all_users WHERE username = '******'") > 0) && (jdbcTemplate.queryForInt( "SELECT COUNT(*) FROM all_views WHERE view_name = 'RESOURCE_VIEW'") > 0); } /** * Generates the drop statements for all database objects of this type. * * @param objectType The type of database object to drop. * @param extraArguments The extra arguments to add to the drop statement. * @return The complete drop statements, ready to execute. * @throws SQLException when the drop statements could not be generated. */ private List<String> generateDropStatementsForObjectType(String objectType, String extraArguments) throws SQLException { String query = "SELECT object_name FROM all_objects WHERE object_type = ? AND owner = ?" // Ignore Spatial Index Sequences as they get dropped automatically when the index gets // dropped. + " AND object_name NOT LIKE 'MDRS_%$'"; List<String> objectNames = jdbcTemplate.queryForStringList(query, objectType, name); List<String> dropStatements = new ArrayList<String>(); for (String objectName : objectNames) { dropStatements.add( "DROP " + objectType + " " + dbSupport.quote(name, objectName) + " " + extraArguments); } return dropStatements; } /** * Generates the drop statements for Oracle Spatial Extensions-related database objects. * * @return The complete drop statements, ready to execute. * @throws SQLException when the drop statements could not be generated. */ private List<String> generateDropStatementsForSpatialExtensions() throws SQLException { List<String> statements = new ArrayList<String>(); if (!spatialExtensionsAvailable()) { LOG.debug( "Oracle Spatial Extensions are not available. No cleaning of MDSYS tables and views."); return statements; } if (!dbSupport.getCurrentSchema().getName().equalsIgnoreCase(name)) { int count = jdbcTemplate.queryForInt( "SELECT COUNT (*) FROM all_sdo_geom_metadata WHERE owner=?", name); count += jdbcTemplate.queryForInt( "SELECT COUNT (*) FROM all_sdo_index_info WHERE sdo_index_owner=?", name); if (count > 0) { LOG.warn( "Unable to clean Oracle Spatial objects for schema '" + name + "' as they do not belong to the default schema for this connection!"); } return statements; } statements.add("DELETE FROM mdsys.user_sdo_geom_metadata"); List<String> indexNames = jdbcTemplate.queryForStringList("select INDEX_NAME from USER_SDO_INDEX_INFO"); for (String indexName : indexNames) { statements.add("DROP INDEX \"" + indexName + "\""); } return statements; } /** * Generates the drop statements for queue tables. * * @return The complete drop statements, ready to execute. * @throws SQLException when the drop statements could not be generated. */ private List<String> generateDropStatementsForQueueTables() throws SQLException { List<String> statements = new ArrayList<String>(); List<String> queueTblNames = jdbcTemplate.queryForStringList("select QUEUE_TABLE from USER_QUEUE_TABLES"); for (String queueTblName : queueTblNames) { statements.add( "begin DBMS_AQADM.drop_queue_table (queue_table=> '" + queueTblName + "', FORCE => TRUE); end;"); } return statements; } /** * Checks whether Oracle Spatial extensions are available or not. * * @return {@code true} if they are available, {@code false} if not. * @throws SQLException when checking availability of the spatial extensions failed. */ private boolean spatialExtensionsAvailable() throws SQLException { return jdbcTemplate.queryForInt( "SELECT COUNT(*) FROM all_views WHERE owner = 'MDSYS' AND view_name = 'USER_SDO_GEOM_METADATA'") > 0; } @Override protected Table[] doAllTables() throws SQLException { List<String> tableNames = jdbcTemplate.queryForStringList( "SELECT table_name FROM all_tables WHERE owner = ?" // Ignore Recycle bin objects + " AND table_name NOT LIKE 'BIN$%'" // Ignore Spatial Index Tables as they get dropped automatically when the index gets // dropped. + " AND table_name NOT LIKE 'MDRT_%$'" // Ignore Materialized View Logs + " AND table_name NOT LIKE 'MLOG$%' AND table_name NOT LIKE 'RUPD$%'" // Ignore Oracle Text Index Tables + " AND table_name NOT LIKE 'DR$%'" // Ignore Index Organized Tables + " AND table_name NOT LIKE 'SYS_IOT_OVER_%'" // Ignore Nested Tables + " AND nested != 'YES'" // Ignore Nested Tables + " AND secondary != 'Y'", name); Table[] tables = new Table[tableNames.size()]; for (int i = 0; i < tableNames.size(); i++) { tables[i] = new OracleTable(jdbcTemplate, dbSupport, this, tableNames.get(i)); } return tables; } @Override public Table getTable(String tableName) { return new OracleTable(jdbcTemplate, dbSupport, this, tableName); } }
/** * Main workflow for migrating the database. * * @author Axel Fontaine */ public class DbMigrate { private static final Log LOG = LogFactory.getLog(DbMigrate.class); /** The target version of the migration. */ private final MigrationVersion target; /** Database-specific functionality. */ private final DbSupport dbSupport; /** The database metadata table. */ private final MetaDataTable metaDataTable; /** The schema containing the metadata table. */ private final Schema schema; /** The migration resolver. */ private final MigrationResolver migrationResolver; /** The connection to use. */ private final Connection connectionMetaDataTable; /** The connection to use to perform the actual database migrations. */ private final Connection connectionUserObjects; /** Flag whether to ignore failed future migrations or not. */ private final boolean ignoreFailedFutureMigration; /** * Allows migrations to be run "out of order". * * <p>If you already have versions 1 and 3 applied, and now a version 2 is found, it will be * applied too instead of being ignored. * * <p>(default: {@code false}) */ private boolean outOfOrder; /** * Creates a new database migrator. * * @param connectionMetaDataTable The connection to use. * @param connectionUserObjects The connection to use to perform the actual database migrations. * @param dbSupport Database-specific functionality. * @param metaDataTable The database metadata table. * @param migrationResolver The migration resolver. * @param target The target version of the migration. * @param ignoreFailedFutureMigration Flag whether to ignore failed future migrations or not. * @param outOfOrder Allows migrations to be run "out of order". */ public DbMigrate( Connection connectionMetaDataTable, Connection connectionUserObjects, DbSupport dbSupport, MetaDataTable metaDataTable, Schema schema, MigrationResolver migrationResolver, MigrationVersion target, boolean ignoreFailedFutureMigration, boolean outOfOrder) { this.connectionMetaDataTable = connectionMetaDataTable; this.connectionUserObjects = connectionUserObjects; this.dbSupport = dbSupport; this.metaDataTable = metaDataTable; this.schema = schema; this.migrationResolver = migrationResolver; this.target = target; this.ignoreFailedFutureMigration = ignoreFailedFutureMigration; this.outOfOrder = outOfOrder; } /** * Starts the actual migration. * * @return The number of successfully applied migrations. * @throws FlywayException when migration failed. */ public int migrate() throws FlywayException { StopWatch stopWatch = new StopWatch(); stopWatch.start(); int migrationSuccessCount = 0; while (true) { final boolean firstRun = migrationSuccessCount == 0; MigrationVersion result = new TransactionTemplate(connectionMetaDataTable, false) .execute( new TransactionCallback<MigrationVersion>() { public MigrationVersion doInTransaction() { metaDataTable.lock(); MigrationInfoServiceImpl infoService = new MigrationInfoServiceImpl( migrationResolver, metaDataTable, target, outOfOrder); infoService.refresh(); MigrationVersion currentSchemaVersion = MigrationVersion.EMPTY; if (infoService.current() != null) { currentSchemaVersion = infoService.current().getVersion(); } if (firstRun) { LOG.info( "Current version of schema " + schema + ": " + currentSchemaVersion); if (outOfOrder) { LOG.warn( "outOfOrder mode is active. Migration of schema " + schema + " may not be reproducible."); } } MigrationInfo[] future = infoService.future(); if (future.length > 0) { MigrationInfo[] resolved = infoService.resolved(); if (resolved.length == 0) { LOG.warn( "Schema " + schema + " has version " + currentSchemaVersion + ", but no migration could be resolved in the configured locations !"); } else { LOG.warn( "Schema " + schema + " has a version (" + currentSchemaVersion + ") that is newer than the latest available migration (" + resolved[resolved.length - 1].getVersion() + ") !"); } } MigrationInfo[] failed = infoService.failed(); if (failed.length > 0) { if ((failed.length == 1) && (failed[0].getState() == MigrationState.FUTURE_FAILED) && ignoreFailedFutureMigration) { LOG.warn( "Schema " + schema + " contains a failed future migration to version " + failed[0].getVersion() + " !"); } else { throw new FlywayException( "Schema " + schema + " contains a failed migration to version " + failed[0].getVersion() + " !"); } } MigrationInfoImpl[] pendingMigrations = infoService.pending(); if (pendingMigrations.length == 0) { return null; } boolean isOutOfOrder = pendingMigrations[0].getVersion().compareTo(currentSchemaVersion) < 0; return applyMigration( pendingMigrations[0].getResolvedMigration(), isOutOfOrder); } }); if (result == null) { // No further migrations available break; } migrationSuccessCount++; } stopWatch.stop(); logSummary(migrationSuccessCount, stopWatch.getTotalTimeMillis()); return migrationSuccessCount; } /** * Logs the summary of this migration run. * * @param migrationSuccessCount The number of successfully applied migrations. * @param executionTime The total time taken to perform this migration run (in ms). */ private void logSummary(int migrationSuccessCount, long executionTime) { if (migrationSuccessCount == 0) { LOG.info("Schema " + schema + " is up to date. No migration necessary."); return; } if (migrationSuccessCount == 1) { LOG.info( "Successfully applied 1 migration to schema " + schema + " (execution time " + TimeFormat.format(executionTime) + ")."); } else { LOG.info( "Successfully applied " + migrationSuccessCount + " migrations to schema " + schema + " (execution time " + TimeFormat.format(executionTime) + ")."); } } /** * Applies this migration to the database. The migration state and the execution time are updated * accordingly. * * @param migration The migration to apply. * @param isOutOfOrder If this migration is being applied out of order. * @return The result of the migration. */ private MigrationVersion applyMigration(final ResolvedMigration migration, boolean isOutOfOrder) { MigrationVersion version = migration.getVersion(); if (isOutOfOrder) { LOG.info("Migrating schema " + schema + " to version " + version + " (out of order)"); } else { LOG.info("Migrating schema " + schema + " to version " + version); } StopWatch stopWatch = new StopWatch(); stopWatch.start(); try { new TransactionTemplate(connectionUserObjects) .execute( new TransactionCallback<Void>() { public Void doInTransaction() { migration.getExecutor().execute(connectionUserObjects); return null; } }); LOG.debug( "Successfully completed and committed migration of schema " + schema + " to version " + version); } catch (FlywayException e) { String failedMsg = "Migration of schema " + schema + " to version " + version + " failed!"; if (dbSupport.supportsDdlTransactions()) { LOG.error(failedMsg + " Changes successfully rolled back."); } else { LOG.error(failedMsg + " Please restore backups and roll back database and code!"); stopWatch.stop(); int executionTime = (int) stopWatch.getTotalTimeMillis(); AppliedMigration appliedMigration = new AppliedMigration( version, migration.getDescription(), migration.getType(), migration.getScript(), migration.getChecksum(), executionTime, false); metaDataTable.addAppliedMigration(appliedMigration); } throw e; } stopWatch.stop(); int executionTime = (int) stopWatch.getTotalTimeMillis(); AppliedMigration appliedMigration = new AppliedMigration( version, migration.getDescription(), migration.getType(), migration.getScript(), migration.getChecksum(), executionTime, true); metaDataTable.addAppliedMigration(appliedMigration); return version; } }