We had the same problem. I.e., call a bunch of scripts always before and after every migration. E.g., deleting and creating materialized view, granting permissions to tables.
These scripts do not change from migration to migration, but they need to be executed.
So I took the org.flywaydb.core.internal.callback.SqlScriptFlywayCallback
callback class and adapted it for multiple files.
I tried to stay in the philosophy of flyway
and use the following pattern.
Files starting with am__
or AM__
are an after migration script, those with bi__
are for before info, and so on.
I sort the scripts, so that they are executed in the correct order.
public class MultipleScriptPerCallback extends BaseFlywayCallback {
private static final Log LOG = LogFactory.getLog(SqlScriptFlywayCallback.class);
private static final String DELIMITER = "__";
private static final String BEFORE_CLEAN = "bc";
private static final String AFTER_CLEAN = "ac";
private static final String BEFORE_MIGRATE = "bm";
private static final String AFTER_MIGRATE = "am";
private static final String BEFORE_EACH_MIGRATE = "bem";
private static final String AFTER_EACH_MIGRATE = "aem";
private static final String BEFORE_VALIDATE = "bv";
private static final String AFTER_VALIDATE = "av";
private static final String BEFORE_BASELINE = "bb";
private static final String AFTER_BASELINE = "ab";
private static final String BEFORE_REPAIR = "br";
private static final String AFTER_REPAIR = "ar";
private static final String BEFORE_INFO = "bi";
private static final String AFTER_INFO = "ai";
private static final List<String> ALL_CALLBACKS = Arrays.asList(BEFORE_CLEAN, AFTER_CLEAN, BEFORE_MIGRATE, BEFORE_EACH_MIGRATE,
AFTER_EACH_MIGRATE, AFTER_MIGRATE, BEFORE_VALIDATE, AFTER_VALIDATE, BEFORE_BASELINE, AFTER_BASELINE, BEFORE_REPAIR,
AFTER_REPAIR, BEFORE_INFO, AFTER_INFO);
private Map<String, List<SqlScript>> scripts;
@Override
public void setFlywayConfiguration(FlywayConfiguration flywayConfiguration) {
super.setFlywayConfiguration(flywayConfiguration);
if (scripts == null) {
scripts = registerScripts(flywayConfiguration);
}
}
private Map<String, List<SqlScript>> registerScripts(FlywayConfiguration flywayConfiguration) {
Map<String, List<SqlScript>> scripts = new HashMap<>();
for (String callback : ALL_CALLBACKS) {
scripts.put(callback, new ArrayList<SqlScript>());
}
LOG.debug(String.format("%s - Scanning for Multiple SQL callbacks ...", getClass().getSimpleName()));
Locations locations = new Locations(flywayConfiguration.getLocations());
Scanner scanner = new Scanner(flywayConfiguration.getClassLoader());
String sqlMigrationSuffix = flywayConfiguration.getSqlMigrationSuffix();
DbSupport dbSupport = dbSupport(flywayConfiguration);
PlaceholderReplacer placeholderReplacer = createPlaceholderReplacer();
String encoding = flywayConfiguration.getEncoding();
for (Location location : locations.getLocations()) {
Resource[] resources;
try {
resources = scanner.scanForResources(location, "", sqlMigrationSuffix);
} catch (FlywayException e) {
// Ignore missing locations
continue;
}
for (Resource resource : resources) {
String key = extractKeyFromFileName(resource);
if (scripts.keySet().contains(key)) {
LOG.debug(getClass().getSimpleName() + " - found script " + resource.getFilename() + " from location: " + location);
List<SqlScript> sqlScripts = scripts.get(key);
sqlScripts.add(new SqlScript(dbSupport, resource, placeholderReplacer, encoding));
}
}
}
LOG.info(getClass().getSimpleName() + " - scripts registered: " + prettyPrint(scripts));
return scripts;
}
private String prettyPrint(Map<String, List<SqlScript>> scripts) {
StringBuilder prettyPrint = new StringBuilder();
boolean isFirst = true;
for (String key : scripts.keySet()) {
if (!isFirst) {
prettyPrint.append("; ");
}
prettyPrint.append(key).append("=").append("[").append(prettyPrint(scripts.get(key))).append("]");
isFirst = false;
}
return prettyPrint.toString();
}
private String prettyPrint(List<SqlScript> scripts) {
StringBuilder prettyPrint = new StringBuilder();
boolean isFirst = true;
for (SqlScript script : scripts) {
if (!isFirst) {
prettyPrint.append(", ");
}
prettyPrint.append(script.getResource().getFilename());
isFirst = false;
}
return prettyPrint.toString();
}
private String extractKeyFromFileName(Resource resource) {
String filename = resource.getFilename();
eturn filename.substring(0, (!filename.contains(DELIMITER)) ? 0 : filename.indexOf(DELIMITER)).toLowerCase();
}
private DbSupport dbSupport(FlywayConfiguration flywayConfiguration) {
Connection connectionMetaDataTable = JdbcUtils.openConnection(flywayConfiguration.getDataSource());
return DbSupportFactory.createDbSupport(connectionMetaDataTable, true);
}
/**
* @return A new, fully configured, PlaceholderReplacer.
*/
private PlaceholderReplacer createPlaceholderReplacer() {
if (flywayConfiguration.isPlaceholderReplacement()) {
return
new PlaceholderReplacer(flywayConfiguration.getPlaceholders(), flywayConfiguration.getPlaceholderPrefix(),
flywayConfiguration.getPlaceholderSuffix());
}
return PlaceholderReplacer.NO_PLACEHOLDERS;
}
@Override
public void beforeClean(Connection connection) {
execute(BEFORE_CLEAN, connection);
}
@Override
public void afterClean(Connection connection) {
execute(AFTER_CLEAN, connection);
}
@Override
public void beforeMigrate(Connection connection) {
execute(BEFORE_MIGRATE, connection);
}
@Override
public void afterMigrate(Connection connection) {
execute(AFTER_MIGRATE, connection);
}
@Override
public void beforeEachMigrate(Connection connection, MigrationInfo info) {
execute(BEFORE_EACH_MIGRATE, connection);
}
@Override
public void afterEachMigrate(Connection connection, MigrationInfo info) {
execute(AFTER_EACH_MIGRATE, connection);
}
@Override
public void beforeValidate(Connection connection) {
execute(BEFORE_VALIDATE, connection);
}
@Override
public void afterValidate(Connection connection) {
execute(AFTER_VALIDATE, connection);
}
@Override
public void beforeBaseline(Connection connection) {
execute(BEFORE_BASELINE, connection);
}
@Override
public void afterBaseline(Connection connection) {
execute(AFTER_BASELINE, connection);
}
@Override
public void beforeRepair(Connection connection) {
execute(BEFORE_REPAIR, connection);
}
@Override
public void afterRepair(Connection connection) {
execute(AFTER_REPAIR, connection);
}
@Override
public void beforeInfo(Connection connection) {
execute(BEFORE_INFO, connection);
}
@Override
public void afterInfo(Connection connection) {
execute(AFTER_INFO, connection);
}
private void execute(String key, Connection connection) {
List<SqlScript> sqlScripts = scripts.get(key);
LOG.debug(String.format("%s - sqlscript: %s for key: %s", getClass().getSimpleName(), sqlScripts, key));
Collections.sort(sqlScripts, new SqlScriptLexicalComparator());
for (SqlScript script : sqlScripts) {
executeScript(key, connection, script);
}
}
//Not private for testing
void executeScript(String key, Connection connection, SqlScript script) {
LOG.info(String.format("%s - Executing SQL callback: %s : %s", getClass().getSimpleName(), key,
script.getResource().getFilename()));
script.execute(new JdbcTemplate(connection, 0));
}
//Not private for testing
static final class SqlScriptLexicalComparator implements Comparator<SqlScript> {
@Override
public int compare(SqlScript o1, SqlScript o2) {
return Collator.getInstance().compare(o1.getResource().getFilename(), o2.getResource().getFilename());
}
}
}