mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-23 12:19:09 +00:00
#2284 - ENH: ddl-generator - add optional generation of idx_<platform>.migrations file
This commit is contained in:
@@ -133,6 +133,14 @@ public interface DbMigration {
|
||||
*/
|
||||
void setStrictMode(boolean strictMode);
|
||||
|
||||
/**
|
||||
* Set to include generation of the index migration file.
|
||||
* <p>
|
||||
* When true this generates a {@code idx_<platform>.migrations} file. This can be used by the migration
|
||||
* runner to improve performance of running migrations, especially when no migration changes have occurred.
|
||||
*/
|
||||
void setIncludeIndex(boolean generateIndexFile);
|
||||
|
||||
/**
|
||||
* Set to true to include a generated header comment in the DDL script.
|
||||
*/
|
||||
|
||||
+26
-1
@@ -106,6 +106,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
private boolean addForeignKeySkipCheck;
|
||||
private int lockTimeoutSeconds;
|
||||
protected boolean includeBuiltInPartitioning = true;
|
||||
protected boolean includeIndex;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
@@ -182,6 +183,11 @@ public class DefaultDbMigration implements DbMigration {
|
||||
this.generatePendingDrop = generatePendingDrop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeIndex(boolean includeIndex) {
|
||||
this.includeIndex = includeIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
|
||||
this.includeGeneratedFileComment = includeGeneratedFileComment;
|
||||
@@ -273,7 +279,26 @@ public class DefaultDbMigration implements DbMigration {
|
||||
*/
|
||||
@Override
|
||||
public String generateMigration() throws IOException {
|
||||
return generateMigrationFor(false);
|
||||
final String version = generateMigrationFor(false);
|
||||
if (includeIndex) {
|
||||
generateIndex(version);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@code idx_platform.migrations} file.
|
||||
*/
|
||||
private void generateIndex(String version) throws IOException {
|
||||
final boolean overwrite = version != null;
|
||||
final File topDir = migrationDirectory(false);
|
||||
if (!platforms.isEmpty()) {
|
||||
for (Pair pair : platforms) {
|
||||
new IndexMigration(topDir, pair).generate(overwrite);
|
||||
}
|
||||
} else {
|
||||
new IndexMigration(topDir, databasePlatform).generate(overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.migration.MigrationVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Generate a migration index file.
|
||||
* <p>
|
||||
* This is a file that has all the migrations listed in order with checksum of the file content.
|
||||
*/
|
||||
class IndexMigration {
|
||||
|
||||
private static final String eol = "\n";
|
||||
private final List<Entry> all = new ArrayList<>();
|
||||
private final File topDir;
|
||||
private final DatabasePlatform databasePlatform;
|
||||
private final File indexFile;
|
||||
private final Stack<String> pathStack = new Stack<>();
|
||||
|
||||
IndexMigration(File topDir, DatabasePlatform databasePlatform) {
|
||||
this.topDir = topDir;
|
||||
this.databasePlatform = databasePlatform;
|
||||
this.indexFile = init();
|
||||
}
|
||||
|
||||
IndexMigration(File topDir, DefaultDbMigration.Pair pair) {
|
||||
this.topDir = new File(topDir, pair.prefix);
|
||||
this.databasePlatform = pair.platform;
|
||||
this.indexFile = init();
|
||||
}
|
||||
|
||||
File init() {
|
||||
pathStack.push("");
|
||||
String name = "idx_" + databasePlatform.getPlatform().base().name().toLowerCase() + ".migrations";
|
||||
return new File(topDir, name);
|
||||
}
|
||||
|
||||
void generate(boolean overwrite) throws IOException {
|
||||
if (!overwrite && indexFile.exists()) {
|
||||
return;
|
||||
}
|
||||
readSqlFiles(topDir);
|
||||
generateIndex();
|
||||
}
|
||||
|
||||
private void generateIndex() throws IOException {
|
||||
Collections.sort(all);
|
||||
FileWriter writer = new FileWriter(indexFile);
|
||||
for (Entry entry : all) {
|
||||
writeChecksumPadded(writer, entry.checksum);
|
||||
writer.write(entry.fileName);
|
||||
writer.write(eol);
|
||||
}
|
||||
writer.write(eol);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private void writeChecksumPadded(FileWriter writer, int checksum) throws IOException {
|
||||
final String asStr = String.valueOf(checksum);
|
||||
writer.write(asStr);
|
||||
writer.write(',');
|
||||
int max = 15 - asStr.length();
|
||||
for (int i = 0; i < max; i++) {
|
||||
writer.write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
private void readSqlFiles(File dir) {
|
||||
final File[] files = dir.listFiles();
|
||||
if (files != null && files.length > 0) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
readDirectory(file);
|
||||
}
|
||||
final String lowerName = file.getName().toLowerCase();
|
||||
if (lowerName.endsWith(".sql")) {
|
||||
addEntry(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readDirectory(File dir) {
|
||||
final String current = pathStack.peek();
|
||||
pathStack.push(current + dir.getName() + "/");
|
||||
readSqlFiles(dir);
|
||||
pathStack.pop();
|
||||
}
|
||||
|
||||
private void addEntry(File sqlFile) {
|
||||
final String relativePath = pathStack.peek();
|
||||
final String fileName = sqlFile.getName();
|
||||
final String name = fileName.substring(0, fileName.length() - 4);
|
||||
final MigrationVersion version = MigrationVersion.parse(name);
|
||||
final int checksum = MChecksum.calculate(sqlFile);
|
||||
all.add(new Entry(checksum, version, relativePath + fileName));
|
||||
}
|
||||
|
||||
static class Entry implements Comparable<Entry> {
|
||||
|
||||
private final int checksum;
|
||||
private final String fileName;
|
||||
private final MigrationVersion version;
|
||||
|
||||
Entry(int checksum, MigrationVersion version, String fileName) {
|
||||
this.checksum = checksum;
|
||||
this.version = version;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry other) {
|
||||
return version.compareTo(other.version);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
/**
|
||||
* Calculates the checksum for the given file content.
|
||||
*/
|
||||
class MChecksum {
|
||||
|
||||
/**
|
||||
* Returns the checksum of the file. Agnostic of encoding and new line character.
|
||||
*/
|
||||
static int calculate(File file) {
|
||||
try {
|
||||
final CRC32 crc32 = new CRC32();
|
||||
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
final byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8);
|
||||
crc32.update(lineBytes, 0, lineBytes.length);
|
||||
}
|
||||
return (int) crc32.getValue();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to calculate checksum", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-8
@@ -27,20 +27,22 @@ public class DbMigrationGenerateTest {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DbMigrationGenerateTest.class);
|
||||
|
||||
@Test
|
||||
public void invokeTest() throws IOException {
|
||||
main(null);
|
||||
public static void main(String[] args) throws IOException {
|
||||
run("ebean-ddl-generator/src/test/resources");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
@Test
|
||||
public void invokeTest() throws IOException {
|
||||
run("src/test/resources");
|
||||
}
|
||||
|
||||
logger.info("start");
|
||||
public static void run(String pathToResources) throws IOException {
|
||||
logger.info("start current directory: " + new File(".").getAbsolutePath());
|
||||
|
||||
DefaultDbMigration migration = new DefaultDbMigration();
|
||||
|
||||
migration.setIncludeIndex(true);
|
||||
// We use src/test/resources as output directory (so we see in GIT if files will change)
|
||||
|
||||
migration.setPathToResources("src/test/resources");
|
||||
migration.setPathToResources(pathToResources);
|
||||
migration.setMigrationPath("db/migration");
|
||||
migration.setMigrationPath(null); // use the default for this test
|
||||
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class IndexMigrationTest {
|
||||
|
||||
@Test
|
||||
public void index() throws IOException {
|
||||
File topDir = new File("src/test/resources/dbmigration/index");
|
||||
if (!topDir.exists()) {
|
||||
throw new IllegalStateException("Not expected - dir does not exist " + topDir.getAbsolutePath());
|
||||
}
|
||||
DatabasePlatform pg = new PostgresPlatform();
|
||||
IndexMigration indexMigration = new IndexMigration(topDir, pg);
|
||||
indexMigration.generate(true);
|
||||
|
||||
|
||||
File expected = new File(topDir, "idx_postgres.migrations");
|
||||
assertThat(expected).exists();
|
||||
|
||||
final List<String> expectedLines = Arrays.asList(
|
||||
"-965417868, I__init_1.sql",
|
||||
"907060870, 1.0__hello.sql",
|
||||
"-1938594527, 1.1__foo.sql",
|
||||
"-1960070312, R__view_1.sql");
|
||||
|
||||
final List<String> lines = Files.readAllLines(expected.toPath(), StandardCharsets.UTF_8);
|
||||
assertThat(lines).containsAll(expectedLines);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void index2_withSubDirectories() throws IOException {
|
||||
File topDir = new File("src/test/resources/dbmigration/index2");
|
||||
if (!topDir.exists()) {
|
||||
throw new IllegalStateException("Not expected - dir does not exist " + topDir.getAbsolutePath());
|
||||
}
|
||||
DatabasePlatform pg = new H2Platform();
|
||||
IndexMigration indexMigration = new IndexMigration(topDir, pg);
|
||||
indexMigration.generate(true);
|
||||
|
||||
File expected = new File(topDir, "idx_h2.migrations");
|
||||
assertThat(expected).exists();
|
||||
|
||||
final List<String> expectedLines = Arrays.asList(
|
||||
"-965417868, I__init_1.sql",
|
||||
"-390611389, g1/1.0__a.sql",
|
||||
"1908338681, g1/1.1__b.sql",
|
||||
"-1776543936, g2/2.0__a.sql",
|
||||
"253052666, g2/2.1__2b.sql",
|
||||
"-1960070312, R__view_1.sql");
|
||||
|
||||
final List<String> lines = Files.readAllLines(expected.toPath(), StandardCharsets.UTF_8);
|
||||
assertThat(lines).containsAll(expectedLines);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class MChecksumTest {
|
||||
|
||||
@Test
|
||||
public void calculate() {
|
||||
File file = new File("src/test/resources/dbmigration/index/1.0__hello.sql");
|
||||
assertThat(file).exists();
|
||||
|
||||
assertThat(MChecksum.calculate(file)).isEqualTo(907060870);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
hello
|
||||
@@ -0,0 +1 @@
|
||||
foo
|
||||
@@ -0,0 +1 @@
|
||||
init
|
||||
@@ -0,0 +1 @@
|
||||
Something
|
||||
@@ -0,0 +1 @@
|
||||
init
|
||||
@@ -0,0 +1 @@
|
||||
Something
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
@@ -0,0 +1 @@
|
||||
b
|
||||
@@ -0,0 +1 @@
|
||||
2a
|
||||
@@ -0,0 +1 @@
|
||||
2b
|
||||
Reference in New Issue
Block a user