Flyway does not provide any in build features to perform the same.
However I have done this for my project and i think so far this is the best we can do.
- Make Flyway versions as actual timestamp so it does not matter which git branch you create it will be always unique and in order.
- Write a program to generate this version and ask all your developers
to use this file in order to get there next version which
need to be used.
Below is a sample I use currently
package com.demo;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class GenerateFileVersion {
public static void main (String... args){
GenerateFileVersion f=new GenerateFileVersion();
f.fileVersion();
}
private String trimOrPad(String str, int length, char padChar) {
String result;
if (str == null) {
result = "";
} else {
result = str;
}
if (result.length() > length) {
return result.substring(0, length);
} else {
while (result.length() < length) {
result = padChar+result;
}
return result;
}
}
private String fileVersion(){
Date date = new Date();
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CST6CDT"));
calendar.setTime(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
StringBuffer sb=new StringBuffer();
sb.append(calendar.get(Calendar.YEAR)%100);
sb.append(".");
sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.MONTH)+1),2,'0'));
sb.append(".");
sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.DATE)),2,'0'));
sb.append(".");
sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.HOUR_OF_DAY)),2,'0'));
sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.MINUTE)),2,'0'));
sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.SECOND)),2,'0'));
System.out.println("Choose Your Next File Name From below list...");
int i=0;
for(ENVIRONMENT env: ENVIRONMENT.values()){
System.out.println("Next File Name for Making DDL Change : "+"V"+sb.toString()+this.trimOrPad(String.valueOf(i++),2,'0')+"__"+env.toString()+"_DDL.sql");
System.out.println("Next File Name for Making DML Change : "+"V"+sb.toString()+this.trimOrPad(String.valueOf(i++),2,'0')+"__"+env.toString()+"_DML.sql");
}
return sb.toString();
}
private enum ENVIRONMENT{COMMON(1),LOCAL(9),STAGE(4),MTF(5),PERF(7),PROD(2);
private int value;
private ENVIRONMENT(int value) { this.value = value; }
}
}
You can also add more features to your project to make sure all developers do not do any mistake you can add below program as junit test case so that build fails if there are any files does not follow flyway versioning standard.
Sample is below
package com.demo;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootRestApplicationTests {
@Test
public void checkDuplicateVersion() {
System.out.println("Starting checkDuplicateVersion");
List<ClassLoader> classLoadersList = new LinkedList<>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new ResourcesScanner())
.setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("local"))));
Reflections reflections1 = new Reflections(new ConfigurationBuilder()
.setScanners(new ResourcesScanner())
.setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("common"))));
Stream<String> stream1= reflections.getStore().get("ResourcesScanner").keys().stream();
Stream<String> stream2= reflections1.getStore().get("ResourcesScanner").keys().stream();
Stream<String> resultingStream=Stream.of(stream1,stream2).flatMap(i -> i);
//resultingStream.forEach(System.out::println);
List<String> existingFileVersions=
resultingStream
.filter(f -> f.startsWith("V"))
.filter(f -> f.endsWith(".sql"))
//.forEach(System.out::println);
.map(n -> n.split("__")[0].substring(1))
//.forEach(System.out::println);
.collect(Collectors.toList());
Set<String> duplicateVersion=existingFileVersions.stream().filter(i -> Collections.frequency(existingFileVersions, i) >1)
.collect(Collectors.toSet());
duplicateVersion.forEach( i -> System.out.println("Duplicate Version found "+i));
Assert.assertEquals(0,duplicateVersion.size());
}
@Test
public void checkFlywayFileNamingStandard(){
System.out.println("Starting checkFlywayFileNamingStandard");
List<ClassLoader> classLoadersList = new LinkedList<>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new ResourcesScanner())
.setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("local"))));
Reflections reflections1 = new Reflections(new ConfigurationBuilder()
.setScanners(new ResourcesScanner())
.setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("common"))));
Stream<String> stream1= reflections.getStore().get("ResourcesScanner").keys().stream();
Stream<String> stream2= reflections1.getStore().get("ResourcesScanner").keys().stream();
Stream<String> resultingStream=Stream.of(stream1,stream2).flatMap(i -> i);
//resultingStream.forEach(System.out::println);
resultingStream
.filter(f -> f.endsWith(".sql"))
.forEach(n -> {
if(!n.split("__")[0].toUpperCase().startsWith("V")){
System.out.println("File starts with " + n + " Does not start with Letter V or v. Please fix it.");
Assert.fail();
}
for(String s : n.split("__")[0].substring(1).split("\\.")){
try {
//System.out.println(n);
Integer.valueOf(s);
}catch(Exception e){
//e.printStackTrace();
System.out.println("File starting with "+ n + " does not match flyway standard");
System.out.println("Flyway standard is V{version}__{description}.sql");
Assert.fail();
}
}
});
}
}
For more details you can see my project here