Skip to content

Commit

Permalink
Introduce MigrationContext to be prepared for JDBC migration refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
rPraml committed Dec 11, 2023
1 parent bce84e0 commit 22db0d9
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.ebean.migration;

import java.sql.Connection;

/**
* The current context while a migration runs.
* <p>
* This is used to provide meta-informations in JDBC migrations and mainly provides a read-only access
* to a subset of MigrationConfig.
* <p>
* It is possible to provide an extended implementation in <code>MigrationEngine.run(context)</code>,
* which is accessible in JdbcMigration. So you can create a EbeanMigrationContext, so that you can
* access the current ebean server in the JDBC migration.
*
* @author Roland Praml, FOCONIS AG
*/
public interface MigrationContext {
/**
* The current connection. Note: During migration, this connection is always the same.
* You must not close this connection!
*/
Connection connection();

/**
* The migration path of SQL migrations. You can use this, to load additional SQL resources
* in your JDBC migration or determine, if this JDBC migration is for a particular path.
* This can be used if you have multiple ebean servers for different databases.
*/
String migrationPath();

/**
* The platform of the current migration run. (e.g. <code>sqlserver17</code>)
*/
String platform();

/**
* The base platform of the current migration run. (e.g. <code>sqlserver</code>)
*/
String basePlatform();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.ebean.migration.runner;

import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationContext;

import java.sql.Connection;

/**
* A default implementation of the MigrationContext.
*
* @author Roland Praml, FOCONIS AG
*/
public class DefaultMigrationContext implements MigrationContext {
private final Connection connection;
private final String migrationPath;
private final String platform;
private final String basePlatform;

public DefaultMigrationContext(MigrationConfig config, Connection connection) {
this.connection = connection;
this.migrationPath = config.getMigrationPath();
this.platform = config.getPlatform();
this.basePlatform = config.getBasePlatform();
}

@Override
public Connection connection() {
return connection;
}

@Override
public String migrationPath() {
return migrationPath;
}

@Override
public String platform() {
return platform;
}

@Override
public String basePlatform() {
return basePlatform;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.ebean.migration.runner;

import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationContext;

import java.sql.Connection;
import java.sql.SQLException;
Expand All @@ -15,17 +16,17 @@ final class FirstCheck {

final MigrationConfig config;
final MigrationPlatform platform;
final Connection connection;
final MigrationContext context;
final String schema;
final String table;
final String sqlTable;
boolean tableKnownToExist;
private int count;

FirstCheck(MigrationConfig config, Connection connection, MigrationPlatform platform) {
FirstCheck(MigrationConfig config, MigrationContext context, MigrationPlatform platform) {
this.config = config;
this.platform = platform;
this.connection = connection;
this.context = context;
this.schema = config.getDbSchema();
this.table = config.getMetaTable();
this.sqlTable = schema != null ? schema + '.' + table : table;
Expand Down Expand Up @@ -80,7 +81,7 @@ private int checksumFor(LocalMigrationResource local) {
}

List<MigrationMetaRow> fastRead() throws SQLException {
return platform.fastReadMigrations(sqlTable, connection);
return platform.fastReadMigrations(sqlTable, context.connection());
}

int count() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.avaje.applog.AppLog;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationContext;
import io.ebean.migration.MigrationException;
import io.ebean.migration.MigrationResource;

Expand Down Expand Up @@ -35,51 +36,63 @@ public MigrationEngine(MigrationConfig migrationConfig, boolean checkStateOnly)

/**
* Run the migrations if there are any that need running.
*
* @param connection the connection to run on. Note the connection will be closed.
*/
public List<MigrationResource> run(Connection connection) {
try {
long startMs = System.currentTimeMillis();
LocalMigrationResources resources = new LocalMigrationResources(migrationConfig);
if (!resources.readResources() && !resources.readInitResources()) {
log.log(DEBUG, "no migrations to check");
return emptyList();
}
long splitMs = System.currentTimeMillis() - startMs;
final var platform = derivePlatform(migrationConfig, connection);
final var firstCheck = new FirstCheck(migrationConfig, connection, platform);
if (fastMode && firstCheck.fastModeCheck(resources.versions())) {
long checkMs = System.currentTimeMillis() - startMs;
log.log(INFO, "DB migrations completed in {0}ms - totalMigrations:{1} readResources:{2}ms", checkMs, firstCheck.count(), splitMs);
return emptyList();
}
// ensure running with autoCommit false
setAutoCommitFalse(connection);

final MigrationTable table = initialiseMigrationTable(firstCheck, connection);
try {
List<MigrationResource> result = runMigrations(table, resources.versions());
connection.commit();
if (!checkStateOnly) {
long commitMs = System.currentTimeMillis();
log.log(INFO, "DB migrations completed in {0}ms - executed:{1} totalMigrations:{2} mode:{3}", (commitMs - startMs), table.count(), table.size(), table.mode());
int countNonTransactional = table.runNonTransactional();
if (countNonTransactional > 0) {
log.log(INFO, "Non-transactional DB migrations completed in {0}ms - executed:{1}", (System.currentTimeMillis() - commitMs), countNonTransactional);
}
return run(new DefaultMigrationContext(migrationConfig, connection));
} finally {
close(connection);
}
}

/**
* Run the migrations if there are any that need running. (Does not close connection)
*/
public List<MigrationResource> run(MigrationContext context) {

long startMs = System.currentTimeMillis();
LocalMigrationResources resources = new LocalMigrationResources(migrationConfig);
if (!resources.readResources() && !resources.readInitResources()) {
log.log(DEBUG, "no migrations to check");
return emptyList();
}

var connection = context.connection();
long splitMs = System.currentTimeMillis() - startMs;
final var platform = derivePlatform(migrationConfig, connection);
final var firstCheck = new FirstCheck(migrationConfig, context, platform);
if (fastMode && firstCheck.fastModeCheck(resources.versions())) {
long checkMs = System.currentTimeMillis() - startMs;
log.log(INFO, "DB migrations completed in {0}ms - totalMigrations:{1} readResources:{2}ms", checkMs, firstCheck.count(), splitMs);
return emptyList();
}
// ensure running with autoCommit false
setAutoCommitFalse(connection);

final MigrationTable table = initialiseMigrationTable(firstCheck, connection);
try {
List<MigrationResource> result = runMigrations(table, resources.versions());
connection.commit();
if (!checkStateOnly) {
long commitMs = System.currentTimeMillis();
log.log(INFO, "DB migrations completed in {0}ms - executed:{1} totalMigrations:{2} mode:{3}", (commitMs - startMs), table.count(), table.size(), table.mode());
int countNonTransactional = table.runNonTransactional();
if (countNonTransactional > 0) {
log.log(INFO, "Non-transactional DB migrations completed in {0}ms - executed:{1}", (System.currentTimeMillis() - commitMs), countNonTransactional);
}
return result;
} catch (MigrationException e) {
rollback(connection);
throw e;
} catch (Throwable e) {
log.log(ERROR, "Perform rollback due to DB migration error", e);
rollback(connection);
throw new MigrationException("Error running DB migrations", e);
} finally {
table.unlockMigrationTable();
}
return result;
} catch (MigrationException e) {
rollback(connection);
throw e;
} catch (Throwable e) {
log.log(ERROR, "Perform rollback due to DB migration error", e);
rollback(connection);
throw new MigrationException("Error running DB migrations", e);
} finally {
close(connection);
table.unlockMigrationTable();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class MigrationTable {
private static final int AUTO_PATCH_CHECKSUM = -1;

private final MigrationConfig config;
private final Connection connection;
private final MigrationContext context;
private final boolean checkStateOnly;
private boolean earlyChecksumMode;
private final MigrationPlatform platform;
Expand Down Expand Up @@ -72,18 +72,18 @@ final class MigrationTable {
private int executionCount;
private boolean patchLegacyChecksums;
private MigrationMetaRow initMetaRow;
private boolean tableKnownToExist;
private final boolean tableKnownToExist;

public MigrationTable(FirstCheck firstCheck, boolean checkStateOnly) {
this.config = firstCheck.config;
this.platform = firstCheck.platform;
this.connection = firstCheck.connection;
this.context = firstCheck.context;
this.schema = firstCheck.schema;
this.table = firstCheck.table;
this.sqlTable = firstCheck.sqlTable;
this.tableKnownToExist = firstCheck.tableKnownToExist;

this.scriptRunner = new MigrationScriptRunner(connection, platform);
this.scriptRunner = new MigrationScriptRunner(context.connection(), platform);
this.checkStateOnly = checkStateOnly;
this.earlyChecksumMode = config.isEarlyChecksumMode();
this.migrations = new LinkedHashMap<>();
Expand Down Expand Up @@ -142,7 +142,7 @@ private ScriptTransform createScriptTransform(MigrationConfig config) {
void createIfNeededAndLock() throws SQLException, IOException {
SQLException suppressedException = null;
if (!tableKnownToExist) {
MigrationSchema.createIfNeeded(config, connection);
MigrationSchema.createIfNeeded(config, context.connection());
if (!tableExists()) {
try {
createTable();
Expand Down Expand Up @@ -174,14 +174,14 @@ void createIfNeededAndLock() throws SQLException, IOException {
* contain all the executed migrations in that case.
*/
private void obtainLockWithWait() throws SQLException {
platform.lockMigrationTable(sqlTable, connection);
platform.lockMigrationTable(sqlTable, context.connection());
}

/**
* Release a lock on the migration table (MySql, MariaDB only).
*/
void unlockMigrationTable() {
platform.unlockMigrationTable(sqlTable, connection);
platform.unlockMigrationTable(sqlTable, context.connection());
}

/**
Expand All @@ -191,12 +191,13 @@ void unlockMigrationTable() {
* executed during the wait for the lock.
*/
private void readExistingMigrations() throws SQLException {
for (MigrationMetaRow metaRow : platform.readExistingMigrations(sqlTable, connection)) {
for (MigrationMetaRow metaRow : platform.readExistingMigrations(sqlTable, context.connection())) {
addMigration(metaRow.version(), metaRow);
}
}

void createTable() throws IOException, SQLException {
Connection connection = context.connection();
try {
scriptRunner.runScript(createTableDdl(), "create migration table");
createInitMetaRow().executeInsert(connection, insertSql);
Expand Down Expand Up @@ -253,6 +254,7 @@ private ClassLoader classLoader() {
* Return true if the table exists.
*/
boolean tableExists() throws SQLException {
Connection connection = context.connection();
String migTable = table;
DatabaseMetaData metaData = connection.getMetaData();
if (metaData.storesUpperCaseIdentifiers()) {
Expand Down Expand Up @@ -351,7 +353,7 @@ boolean skipMigration(int checksum, int checksum2, LocalMigrationResource local,
} else if (patchLegacyChecksums && (existing.checksum() == checksum2 || checksum2 == AUTO_PATCH_CHECKSUM)) {
if (!checkStateOnly) {
log.log(INFO, "Auto patch migration, set early mode checksum on {0} to {1,number} from {2,number}", local.location(), checksum, existing.checksum());
existing.resetChecksum(checksum, connection, updateChecksumSql);
existing.resetChecksum(checksum, context.connection(), updateChecksumSql);
}
return true;

Expand All @@ -373,7 +375,7 @@ boolean skipMigration(int checksum, int checksum2, LocalMigrationResource local,
private boolean patchResetChecksum(MigrationMetaRow existing, int newChecksum) throws SQLException {
if (isResetOnVersion(existing.version())) {
if (!checkStateOnly) {
existing.resetChecksum(newChecksum, connection, updateChecksumSql);
existing.resetChecksum(newChecksum, context.connection(), updateChecksumSql);
}
return true;
} else {
Expand Down Expand Up @@ -405,7 +407,7 @@ private void executeMigration(LocalMigrationResource local, String script, int c
}
if (existing != null) {
existing.rerun(checksum, exeMillis, envUserName, runOn);
existing.executeUpdate(connection, updateSql);
existing.executeUpdate(context.connection(), updateSql);
} else {
insertIntoHistory(local, checksum, exeMillis);
}
Expand All @@ -424,7 +426,7 @@ private long executeMigration(LocalMigrationResource local, String script) throw
if (local instanceof LocalJdbcMigrationResource) {
JdbcMigration migration = ((LocalJdbcMigrationResource) local).migration();
log.log(INFO, "Executing jdbc migration version: {0} - {1}", local.version(), migration);
migration.migrate(connection);
migration.migrate(context.connection());
} else {
log.log(DEBUG, "run migration {0}", local.location());
scriptRunner.runScript(script, "run migration version: " + local.version());
Expand All @@ -435,7 +437,7 @@ private long executeMigration(LocalMigrationResource local, String script) throw

private void insertIntoHistory(LocalMigrationResource local, int checksum, long exeMillis) throws SQLException {
MigrationMetaRow metaRow = createMetaRow(local, checksum, exeMillis);
metaRow.executeInsert(connection, insertSql);
metaRow.executeInsert(context.connection(), insertSql);
addMigration(local.key(), metaRow);
}

Expand Down Expand Up @@ -525,7 +527,7 @@ List<MigrationResource> runAll(List<LocalMigrationResource> localVersions) throw
}
if (patchLegacyChecksums && !checkStateOnly) {
// only patch the legacy checksums once
initMetaRow.resetChecksum(EARLY_MODE_CHECKSUM, connection, updateChecksumSql);
initMetaRow.resetChecksum(EARLY_MODE_CHECKSUM, context.connection(), updateChecksumSql);
}
return checkMigrations;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void shutdown() {


private MigrationTable migrationTable(Connection conn) {
var fc = new FirstCheck(config, conn, platform);
var fc = new FirstCheck(config, new DefaultMigrationContext(config, conn), platform);
return new MigrationTable(fc, false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class MigrationTable2Test {


private static MigrationTable migrationTable(MigrationConfig config) {
var fc = new FirstCheck(config, null, new MigrationPlatform());
var fc = new FirstCheck(config, new DefaultMigrationContext(config, null), new MigrationPlatform());
return new MigrationTable(fc, false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ private void runTest(boolean withExisting) throws SQLException, InterruptedExcep
}

private static MigrationTable migrationTable(MigrationPlatform platform, Connection connection) {
var fc = new FirstCheck(config, connection, platform);
var fc = new FirstCheck(config, new DefaultMigrationContext(config, connection), platform);
return new MigrationTable(fc, false);
}

Expand Down
Loading

0 comments on commit 22db0d9

Please sign in to comment.