[Fix] Fixed running intermediary migration scripts for PostgreSQL

This commit is contained in:
Robert von Burg 2019-01-17 11:32:50 +01:00
parent d0751345ff
commit 434dd5a2dc
2 changed files with 117 additions and 33 deletions

View File

@ -15,6 +15,8 @@
*/
package li.strolch.persistence.postgresql.dao.test;
import static java.util.Comparator.comparing;
import static li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler.SCRIPT_PREFIX;
import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.*;
import java.io.File;
@ -49,8 +51,8 @@ public class DbMigrationTest {
@Test
public void shouldCreate() throws Exception {
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(PostgreSqlPersistenceHandler.SCRIPT_PREFIX,
PostgreSqlPersistenceHandler.class, true, true, true);
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(SCRIPT_PREFIX, PostgreSqlPersistenceHandler.class, true,
true, true);
try (Connection con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD)) {
@ -59,7 +61,7 @@ public class DbMigrationTest {
File scriptsD = new File("src/main/resources");
File[] scriptFiles = scriptsD.listFiles(f -> f.getName().endsWith("_initial.sql"));
Arrays.sort(scriptFiles, (f1, f2) -> f1.getName().compareTo(f2.getName()));
Arrays.sort(scriptFiles, comparing(File::getName));
for (File scriptFile : scriptFiles) {
String name = scriptFile.getName();
@ -84,28 +86,20 @@ public class DbMigrationTest {
@Test
public void shouldMigrate() throws Exception {
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(PostgreSqlPersistenceHandler.SCRIPT_PREFIX,
PostgreSqlPersistenceHandler.class, true, true, true);
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(SCRIPT_PREFIX, PostgreSqlPersistenceHandler.class, true,
true, true);
try (Connection con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD)) {
// CREATE 0.1.0
dbCheck.createSchema(con, StrolchConstants.DEFAULT_REALM, Version.valueOf("0.1.0"));
Version currentVersion = Version.valueOf("0.1.0");
dbCheck.createSchema(con, StrolchConstants.DEFAULT_REALM, currentVersion);
File scriptsD = new File("src/main/resources");
File[] scriptFiles = scriptsD.listFiles(f -> f.getName().endsWith("_migration.sql"));
Arrays.sort(scriptFiles, (f1, f2) -> f1.getName().compareTo(f2.getName()));
for (File scriptFile : scriptFiles) {
Version expectedDbVersion = DbSchemaVersionCheck
.getExpectedDbVersion(SCRIPT_PREFIX, PostgreSqlPersistenceHandler.class);
String name = scriptFile.getName();
String versionS = name
.substring("strolch_db_schema_".length(), name.length() - "_migration.sql".length());
Version version = Version.valueOf(versionS);
logger.info("Migrating Version " + version);
// MIGRATE
dbCheck.migrateSchema(con, StrolchConstants.DEFAULT_REALM, version);
}
// MIGRATE
dbCheck.migrateSchema(con, StrolchConstants.DEFAULT_REALM, currentVersion, expectedDbVersion);
} catch (SQLException e) {
String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$

View File

@ -19,14 +19,18 @@ import static li.strolch.db.DbConstants.PROP_DB_VERSION;
import static li.strolch.db.DbConstants.RESOURCE_DB_VERSION;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.sql.*;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import li.strolch.utils.Version;
import li.strolch.utils.dbc.DBC;
@ -127,7 +131,7 @@ public class DbSchemaVersionCheck {
createSchema(con, realm, expectedDbVersion);
break;
case MIGRATED:
migrateSchema(con, realm, expectedDbVersion);
migrateSchema(con, realm, currentVersion, expectedDbVersion);
break;
case DROPPED_CREATED:
throw new DbException("Migration type " + migrationType + " not handled!");
@ -266,11 +270,14 @@ public class DbSchemaVersionCheck {
public static String getSql(String scriptPrefix, Class<?> ctxClass, Version version, String type)
throws DbException {
String schemaResourceS = MessageFormat.format("/{0}_db_schema_{1}_{2}.sql", scriptPrefix, version, type);
try (InputStream stream = ctxClass.getResourceAsStream(schemaResourceS);) {
try (InputStream stream = ctxClass.getResourceAsStream(schemaResourceS)) {
DBC.PRE.assertNotNull(
MessageFormat.format("Schema Resource file with name {0} does not exist!", schemaResourceS),
stream);
return FileHelper.readStreamToString(stream);
} catch (IOException e) {
throw new DbException("Schema creation resource file is missing or could not be read: " + schemaResourceS,
e);
@ -315,13 +322,14 @@ public class DbSchemaVersionCheck {
*
* @param realm
* the realm to migrate (a {@link DataSource} must exist for it)
* @param version
* @param expectedVersion
* the version to upgrade to
*
* @throws DbException
* if something goes wrong
*/
public void migrateSchema(Connection con, String realm, Version version) throws DbException {
public void migrateSchema(Connection con, String realm, Version currentVersion, Version expectedVersion)
throws DbException {
if (!this.allowSchemaMigration) {
String msg = "[{0}:{1}] Schema is not valid. Schema migration is disabled, thus can not continue!";
@ -329,18 +337,100 @@ public class DbSchemaVersionCheck {
throw new DbException(msg);
}
logger.info(MessageFormat.format("[{0}:{1}] Migrating schema to {2}...", this.app, realm, version));
if (expectedVersion.equals(currentVersion))
throw new IllegalStateException("Expected version " + expectedVersion + " is same as " + currentVersion
+ " and thus no migration is necessary!");
if (expectedVersion.compareTo(currentVersion) < 0)
throw new IllegalStateException(
"Expected version " + expectedVersion + " is weirdly before current version" + currentVersion
+ " for " + this.app);
String sql = getSql(this.app, this.ctxClass, version, "migration");
try (Statement st = con.createStatement()) {
st.execute(sql);
logger.info(MessageFormat
.format("[{0}:{1}] Migrating schema from {2} to {3}...", this.app, realm, currentVersion,
expectedVersion));
// first get all possible migration scripts
List<Version> versions = parseMigrationVersions();
if (versions.isEmpty())
throw new IllegalStateException("No migration versions found for context " + this.app);
versions.sort(Version::compareTo);
if (!versions.contains(expectedVersion))
throw new IllegalStateException(
"Expected version " + expectedVersion + " is missing as a migration version for " + this.app);
for (Version version : versions) {
if (version.compareTo(currentVersion) <= 0)
continue;
logger.info("Migrating to version " + version + "...");
String sql = getSql(this.app, this.ctxClass, version, "migration");
try (Statement st = con.createStatement()) {
st.execute(sql);
} catch (SQLException e) {
logger.error("Failed to execute schema migration SQL: \n" + sql);
throw new DbException("Failed to execute schema migration SQL: " + e.getMessage(), e);
}
}
try {
Version version = getCurrentVersion(con, this.app);
if (version == null || !version.equals(expectedVersion))
throw new IllegalStateException(
"Migration to version " + expectedVersion + " failed as version after migration is " + version);
} catch (SQLException e) {
logger.error("Failed to execute schema migration SQL: \n" + sql);
throw new DbException("Failed to execute schema migration SQL: " + e.getMessage(), e);
throw new IllegalStateException("Failed to read current version", e);
}
logger.info(MessageFormat
.format("[{0}:{1}] Successfully migrated schema to version {2}", this.app, realm, version));
.format("[{0}:{1}] Successfully migrated schema to version {2}", this.app, realm, expectedVersion));
}
public List<Version> parseMigrationVersions() {
List<Version> versions = new ArrayList<>();
try {
CodeSource src = this.ctxClass.getProtectionDomain().getCodeSource();
URL url = src.getLocation();
String scheme = url.toURI().getScheme();
if (scheme.equals("jar") || scheme.equals("file") && url.toString().endsWith(".jar")) {
try (ZipInputStream zip = new ZipInputStream(url.openStream())) {
ZipEntry ze;
while ((ze = zip.getNextEntry()) != null) {
String entryName = ze.getName();
if (entryName.endsWith(".sql") && entryName.contains("migration"))
versions.add(parseVersion(entryName));
}
} catch (IOException e) {
throw new IllegalStateException("Failed to read JAR: " + url, e);
}
} else if (scheme.equals("file")) {
File file = Paths.get(url.toURI()).toFile();
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
if (f.getName().endsWith(".sql") && f.getName().contains("migration"))
versions.add(parseVersion(f.getName()));
}
}
}
} catch (Exception e) {
throw new IllegalStateException("Failed to parse migration script versions", e);
}
return versions;
}
private Version parseVersion(String scriptName) {
int versionStart = (this.app + "_db_schema_").length();
int versionEnd = scriptName.indexOf("_", versionStart);
return Version.valueOf(scriptName.substring(versionStart, versionEnd));
}
/**