[Fix] Fixed running intermediary migration scripts for PostgreSQL
This commit is contained in:
parent
d0751345ff
commit
434dd5a2dc
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package li.strolch.persistence.postgresql.dao.test;
|
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 static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -49,8 +51,8 @@ public class DbMigrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreate() throws Exception {
|
public void shouldCreate() throws Exception {
|
||||||
|
|
||||||
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(PostgreSqlPersistenceHandler.SCRIPT_PREFIX,
|
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(SCRIPT_PREFIX, PostgreSqlPersistenceHandler.class, true,
|
||||||
PostgreSqlPersistenceHandler.class, true, true, true);
|
true, true);
|
||||||
|
|
||||||
try (Connection con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD)) {
|
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 scriptsD = new File("src/main/resources");
|
||||||
File[] scriptFiles = scriptsD.listFiles(f -> f.getName().endsWith("_initial.sql"));
|
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) {
|
for (File scriptFile : scriptFiles) {
|
||||||
|
|
||||||
String name = scriptFile.getName();
|
String name = scriptFile.getName();
|
||||||
|
@ -84,28 +86,20 @@ public class DbMigrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void shouldMigrate() throws Exception {
|
public void shouldMigrate() throws Exception {
|
||||||
|
|
||||||
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(PostgreSqlPersistenceHandler.SCRIPT_PREFIX,
|
DbSchemaVersionCheck dbCheck = new DbSchemaVersionCheck(SCRIPT_PREFIX, PostgreSqlPersistenceHandler.class, true,
|
||||||
PostgreSqlPersistenceHandler.class, true, true, true);
|
true, true);
|
||||||
|
|
||||||
try (Connection con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD)) {
|
try (Connection con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD)) {
|
||||||
|
|
||||||
// CREATE 0.1.0
|
// 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");
|
Version expectedDbVersion = DbSchemaVersionCheck
|
||||||
File[] scriptFiles = scriptsD.listFiles(f -> f.getName().endsWith("_migration.sql"));
|
.getExpectedDbVersion(SCRIPT_PREFIX, PostgreSqlPersistenceHandler.class);
|
||||||
Arrays.sort(scriptFiles, (f1, f2) -> f1.getName().compareTo(f2.getName()));
|
|
||||||
for (File scriptFile : scriptFiles) {
|
|
||||||
|
|
||||||
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
|
// MIGRATE
|
||||||
dbCheck.migrateSchema(con, StrolchConstants.DEFAULT_REALM, version);
|
dbCheck.migrateSchema(con, StrolchConstants.DEFAULT_REALM, currentVersion, expectedDbVersion);
|
||||||
}
|
|
||||||
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$
|
String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$
|
||||||
|
|
|
@ -19,14 +19,18 @@ import static li.strolch.db.DbConstants.PROP_DB_VERSION;
|
||||||
import static li.strolch.db.DbConstants.RESOURCE_DB_VERSION;
|
import static li.strolch.db.DbConstants.RESOURCE_DB_VERSION;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.CodeSource;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
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.Version;
|
||||||
import li.strolch.utils.dbc.DBC;
|
import li.strolch.utils.dbc.DBC;
|
||||||
|
@ -127,7 +131,7 @@ public class DbSchemaVersionCheck {
|
||||||
createSchema(con, realm, expectedDbVersion);
|
createSchema(con, realm, expectedDbVersion);
|
||||||
break;
|
break;
|
||||||
case MIGRATED:
|
case MIGRATED:
|
||||||
migrateSchema(con, realm, expectedDbVersion);
|
migrateSchema(con, realm, currentVersion, expectedDbVersion);
|
||||||
break;
|
break;
|
||||||
case DROPPED_CREATED:
|
case DROPPED_CREATED:
|
||||||
throw new DbException("Migration type " + migrationType + " not handled!");
|
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)
|
public static String getSql(String scriptPrefix, Class<?> ctxClass, Version version, String type)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
String schemaResourceS = MessageFormat.format("/{0}_db_schema_{1}_{2}.sql", scriptPrefix, version, type);
|
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(
|
DBC.PRE.assertNotNull(
|
||||||
MessageFormat.format("Schema Resource file with name {0} does not exist!", schemaResourceS),
|
MessageFormat.format("Schema Resource file with name {0} does not exist!", schemaResourceS),
|
||||||
stream);
|
stream);
|
||||||
|
|
||||||
return FileHelper.readStreamToString(stream);
|
return FileHelper.readStreamToString(stream);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new DbException("Schema creation resource file is missing or could not be read: " + schemaResourceS,
|
throw new DbException("Schema creation resource file is missing or could not be read: " + schemaResourceS,
|
||||||
e);
|
e);
|
||||||
|
@ -315,13 +322,14 @@ public class DbSchemaVersionCheck {
|
||||||
*
|
*
|
||||||
* @param realm
|
* @param realm
|
||||||
* the realm to migrate (a {@link DataSource} must exist for it)
|
* the realm to migrate (a {@link DataSource} must exist for it)
|
||||||
* @param version
|
* @param expectedVersion
|
||||||
* the version to upgrade to
|
* the version to upgrade to
|
||||||
*
|
*
|
||||||
* @throws DbException
|
* @throws DbException
|
||||||
* if something goes wrong
|
* 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) {
|
if (!this.allowSchemaMigration) {
|
||||||
String msg = "[{0}:{1}] Schema is not valid. Schema migration is disabled, thus can not continue!";
|
String msg = "[{0}:{1}] Schema is not valid. Schema migration is disabled, thus can not continue!";
|
||||||
|
@ -329,7 +337,33 @@ public class DbSchemaVersionCheck {
|
||||||
throw new DbException(msg);
|
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);
|
||||||
|
|
||||||
|
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");
|
String sql = getSql(this.app, this.ctxClass, version, "migration");
|
||||||
try (Statement st = con.createStatement()) {
|
try (Statement st = con.createStatement()) {
|
||||||
|
@ -338,9 +372,65 @@ public class DbSchemaVersionCheck {
|
||||||
logger.error("Failed to execute schema migration SQL: \n" + sql);
|
logger.error("Failed to execute schema migration SQL: \n" + sql);
|
||||||
throw new DbException("Failed to execute schema migration SQL: " + e.getMessage(), e);
|
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) {
|
||||||
|
throw new IllegalStateException("Failed to read current version", e);
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(MessageFormat
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue