Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add result column to migration table #6580

Merged
merged 19 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
type: add
issue: 6580
fil512 marked this conversation as resolved.
Show resolved Hide resolved
title: "A new RESULT column has been added to the database migration table to record the migration execution result.
fil512 marked this conversation as resolved.
Show resolved Hide resolved
values are SKIPPED (either skipped via the `skip-versions` flag or the migration task was stubbed),
fil512 marked this conversation as resolved.
Show resolved Hide resolved
DOES_NOT_APPLY (does not apply to that database), PRECONDITION_FAILED (not run based on a SQL script outcome),
fil512 marked this conversation as resolved.
Show resolved Hide resolved
fil512 marked this conversation as resolved.
Show resolved Hide resolved
ALLOWED_TO_FAIL (the migration failed, but it is permitted to fail), SUCCESS."
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ public void saveTask(BaseTask theBaseTask, Integer theMillis, boolean theSuccess
HapiMigrationEntity entity = HapiMigrationEntity.fromBaseTask(theBaseTask);
entity.setExecutionTime(theMillis);
entity.setSuccess(theSuccess);
if (theBaseTask.getExecutionResult() != null) {
entity.setResult(theBaseTask.getExecutionResult().name());
}
myHapiMigrationDao.save(entity);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ private void executeTask(BaseTask theTask, MigrationResult theMigrationResult) {
theTask.execute();
recordTaskAsCompletedIfNotDryRun(theTask, sw.getMillis(), true);
theMigrationResult.changes += theTask.getChangesCount();
theMigrationResult.executionResult = theTask.getExecutionResult();
theMigrationResult.executedStatements.addAll(theTask.getExecutedStatements());
theMigrationResult.succeededTasks.add(theTask);
} catch (SQLException | HapiMigrationException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.migrate;

import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
import ca.uhn.fhir.jpa.migrate.taskdef.MigrationTaskExecutionResultEnum;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -29,6 +30,7 @@ public class MigrationResult {
public final List<BaseTask.ExecutedStatement> executedStatements = new ArrayList<>();
public final List<BaseTask> succeededTasks = new ArrayList<>();
public final List<BaseTask> failedTasks = new ArrayList<>();
public MigrationTaskExecutionResultEnum executionResult;

public String summary() {
return String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ private Integer getHighestKey() {

public boolean createMigrationTableIfRequired() {
if (migrationTableExists()) {
if (!columnExists("result")) {
String addResultColumnStatement = myMigrationQueryBuilder.addResultColumnStatement();
ourLog.info(addResultColumnStatement);
myJdbcTemplate.execute(addResultColumnStatement);
}
return false;
}
ourLog.info("Creating table {}", myMigrationTablename);
Expand Down Expand Up @@ -144,6 +149,29 @@ private boolean migrationTableExists() {
}
}

private boolean columnExists(String theColumnName) {
try (Connection connection = myDataSource.getConnection()) {
ResultSet columnsUpper = connection
.getMetaData()
.getColumns(
connection.getCatalog(),
connection.getSchema(),
myMigrationTablename,
theColumnName.toUpperCase());
ResultSet columnsLower = connection
.getMetaData()
.getColumns(
connection.getCatalog(),
connection.getSchema(),
myMigrationTablename,
theColumnName.toLowerCase());

return columnsUpper.next() || columnsLower.next(); // If there's a row, the column exists
} catch (SQLException e) {
throw new InternalErrorException(Msg.code(2615) + "Error checking column existence: " + e.getMessage(), e);
}
}

public List<HapiMigrationEntity> findAll() {
String allQuery = myMigrationQueryBuilder.findAllQuery();
ourLog.debug("Executing query: [{}]", allQuery);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import ca.uhn.fhir.jpa.migrate.entity.HapiMigrationEntity;
import ca.uhn.fhir.jpa.migrate.taskdef.ColumnTypeEnum;
import ca.uhn.fhir.jpa.migrate.taskdef.ColumnTypeToDriverTypeToSqlType;
import com.healthmarketscience.sqlbuilder.AlterTableQuery;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.CreateIndexQuery;
import com.healthmarketscience.sqlbuilder.CreateTableQuery;
Expand Down Expand Up @@ -55,6 +56,7 @@ public class MigrationQueryBuilder {
private final DbColumn myInstalledOnCol;
private final DbColumn myExecutionTimeCol;
private final DbColumn mySuccessCol;
private final DbColumn myResultCol;
private final String myDeleteAll;
private final String myHighestKeyQuery;
private final DriverTypeEnum myDriverType;
Expand Down Expand Up @@ -102,6 +104,8 @@ public MigrationQueryBuilder(DriverTypeEnum theDriverType, String theMigrationTa
mySuccessCol = myTable.addColumn("\"success\"", myBooleanType, null);
mySuccessCol.notNull();

myResultCol = myTable.addColumn("\"result\"", Types.VARCHAR, HapiMigrationEntity.RESULT_MAX_SIZE);

myDeleteAll = new DeleteQuery(myTable).toString();
myHighestKeyQuery = buildHighestKeyQuery();
}
Expand Down Expand Up @@ -133,7 +137,8 @@ public String insertPreparedStatement() {
myInstalledByCol,
myInstalledOnCol,
myExecutionTimeCol,
mySuccessCol)
mySuccessCol,
myResultCol)
.validate()
.toString();
}
Expand All @@ -142,6 +147,10 @@ public String createTableStatement() {
return new CreateTableQuery(myTable, true).validate().toString();
}

public String addResultColumnStatement() {
return new AlterTableQuery(myTable).setAddColumn(myResultCol).validate().toString();
}

public String createIndexStatement() {
return new CreateIndexQuery(myTable, myMigrationTablename.toUpperCase() + "_PK_INDEX")
.setIndexType(CreateIndexQuery.IndexType.UNIQUE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class HapiMigrationEntity {
public static final int TYPE_MAX_SIZE = 20;
public static final int SCRIPT_MAX_SIZE = 1000;
public static final int INSTALLED_BY_MAX_SIZE = 100;
public static final int RESULT_MAX_SIZE = 100;
public static final int CREATE_TABLE_PID = -1;
public static final String INITIAL_RECORD_DESCRIPTION = "<< HAPI FHIR Schema History table created >>";
public static final String INITIAL_RECORD_SCRIPT = "HAPI FHIR";
Expand Down Expand Up @@ -80,6 +81,9 @@ public class HapiMigrationEntity {
@Column(name = "SUCCESS")
private Boolean mySuccess;

@Column(name = "RESULT", length = RESULT_MAX_SIZE)
private String myResult;

public static HapiMigrationEntity tableCreatedRecord() {
HapiMigrationEntity retVal = new HapiMigrationEntity();
retVal.setPid(CREATE_TABLE_PID);
Expand Down Expand Up @@ -195,6 +199,7 @@ public static RowMapper<HapiMigrationEntity> rowMapper() {
entity.setInstalledOn(rs.getDate(8));
entity.setExecutionTime(rs.getInt(9));
entity.setSuccess(rs.getBoolean(10));
entity.setResult(rs.getString(11));
return entity;
};
}
Expand All @@ -219,6 +224,15 @@ public PreparedStatementSetter asPreparedStatementSetter() {
: null);
ps.setInt(9, getExecutionTime());
ps.setBoolean(10, getSuccess());
ps.setString(11, getResult());
};
}

public String getResult() {
return myResult;
}

public void setResult(String theResult) {
myResult = theResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public abstract class BaseTask {
private DriverTypeEnum myDriverType;
private String myDescription;
private Integer myChangesCount = 0;
private MigrationTaskExecutionResultEnum myExecutionResult;
private boolean myDryRun;
private boolean myTransactional = true;
private Set<DriverTypeEnum> myOnlyAppliesToPlatforms = new HashSet<>();
Expand Down Expand Up @@ -210,6 +211,7 @@ private int doExecuteSql(@Language("SQL") String theSql, Object... theArguments)
} else {
int changesCount = jdbcTemplate.update(theSql, theArguments);
logInfo(ourLog, "SQL \"{}\" returned {}", theSql, changesCount);
myExecutionResult = MigrationTaskExecutionResultEnum.SUCCESS;
return changesCount;
}
} catch (DataAccessException e) {
Expand All @@ -218,6 +220,7 @@ private int doExecuteSql(@Language("SQL") String theSql, Object... theArguments)
"Task {} did not exit successfully on doExecuteSql(), but task is allowed to fail",
getMigrationVersion());
ourLog.debug("Error was: {}", e.getMessage(), e);
myExecutionResult = MigrationTaskExecutionResultEnum.ALLOWED_TO_FAIL;
return 0;
} else {
throw new HapiMigrationException(
Expand Down Expand Up @@ -262,11 +265,13 @@ public JdbcTemplate newJdbcTemplate() {
public void execute() throws SQLException {
if (myFlags.contains(TaskFlagEnum.DO_NOTHING)) {
ourLog.info("Skipping stubbed task: {}", getDescription());
myExecutionResult = MigrationTaskExecutionResultEnum.SKIPPED;
return;
}
if (!myOnlyAppliesToPlatforms.isEmpty()) {
if (!myOnlyAppliesToPlatforms.contains(getDriverType())) {
ourLog.info("Skipping task {} as it does not apply to {}", getDescription(), getDriverType());
myExecutionResult = MigrationTaskExecutionResultEnum.DOES_NOT_APPLY;
return;
}
}
Expand All @@ -277,6 +282,7 @@ public void execute() throws SQLException {
ourLog.info(
"Skipping task since one of the preconditions was not met: {}",
precondition.getPreconditionReason());
myExecutionResult = MigrationTaskExecutionResultEnum.PRECONDITION_FAILED;
return;
}
}
Expand Down Expand Up @@ -356,6 +362,10 @@ public boolean hasFlag(TaskFlagEnum theFlag) {
return myFlags.contains(theFlag);
}

public MigrationTaskExecutionResultEnum getExecutionResult() {
return myExecutionResult;
}

public static class ExecutedStatement {
private final String mySql;
private final List<Object> myArguments;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ca.uhn.fhir.jpa.migrate.taskdef;

public enum MigrationTaskExecutionResultEnum {
/**
* Was either skipped via the `skip-versions` flag or the migration task was stubbed
*/
SKIPPED,

/**
* This migration task does not apply to this database
*/
DOES_NOT_APPLY,

/**
* This migration task had precondition criteria (expressed as SQL) that was not met
*/
PRECONDITION_FAILED,

/**
* The migration failed, but the task has the FAILURE_ALLOWED flag set.
*/
ALLOWED_TO_FAIL,

/**
* The migration executed successfully
*/
SUCCESS,
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static void beforeAll() {
ourHapiMigrationStorageSvc = new HapiMigrationStorageSvc(ourHapiMigrationDao);
}

static BasicDataSource getDataSource() {
public static BasicDataSource getDataSource() {
BasicDataSource retVal = new BasicDataSource();
retVal.setDriver(new org.h2.Driver());
retVal.setUrl("jdbc:h2:mem:test_migration");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -164,6 +166,14 @@ public void testSkipSchemaVersion(Supplier<TestDatabaseDetails> theTestDatabaseD
DriverTypeEnum.ConnectionProperties connectionProperties = super.getDriverType().newConnectionProperties(getDataSource().getUrl(), getDataSource().getUsername(), getDataSource().getPassword());
Set<String> tableNames = JdbcUtils.getTableNames(connectionProperties);
assertThat(tableNames).containsExactlyInAnyOrder("SOMETABLE_A", "SOMETABLE_C");

List<HapiMigrationEntity> entities = myHapiMigrationDao.findAll();

assertThat(entities).hasSize(4);
assertThat(entities.get(0).getResult()).isEqualTo("SUCCESS");
assertThat(entities.get(1).getResult()).isEqualTo("SKIPPED");
assertThat(entities.get(2).getResult()).isEqualTo("SUCCESS");
assertThat(entities.get(3).getResult()).isEqualTo("SKIPPED");
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ca.uhn.fhir.jpa.migrate.dao;

import ca.uhn.fhir.jpa.migrate.BaseMigrationTest;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.HapiMigrationStorageSvc;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class AlterMigrationTableIT {
private static final Logger ourLog = LoggerFactory.getLogger(AlterMigrationTableIT.class);

static final String TABLE_NAME = "TEST_MIGRATION_TABLE";
protected static HapiMigrationDao ourHapiMigrationDao;
static JdbcTemplate ourJdbcTemplate;
protected static HapiMigrationStorageSvc ourHapiMigrationStorageSvc;

@BeforeAll
public static void beforeAll() {
BasicDataSource dataSource = BaseMigrationTest.getDataSource();
ourHapiMigrationDao = new HapiMigrationDao(dataSource, DriverTypeEnum.H2_EMBEDDED, TABLE_NAME);
ourJdbcTemplate = new JdbcTemplate(dataSource);
createOldTable();
}

private static void createOldTable() {
String oldSchema = """
CREATE TABLE "TEST_MIGRATION_TABLE" (
"installed_rank" INTEGER NOT NULL,
"version" VARCHAR(50),"description" VARCHAR(200) NOT NULL,
"type" VARCHAR(20) NOT NULL,"script" VARCHAR(1000) NOT NULL,
"checksum" INTEGER,
"installed_by" VARCHAR(100) NOT NULL,
"installed_on" DATE NOT NULL,
"execution_time" INTEGER NOT NULL,
"success" boolean NOT NULL)
""";
ourJdbcTemplate.execute(oldSchema);
}

@Test
void testNewColumnAdded() {
assertFalse(doesColumnExist("TEST_MIGRATION_TABLE", "RESULT"));
ourHapiMigrationDao.createMigrationTableIfRequired();
assertTrue(doesColumnExist("TEST_MIGRATION_TABLE", "RESULT"));
}

private boolean doesColumnExist(String theTableName, String theColumnName) {
String sql = """
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ?
""";

List<String> columns = ourJdbcTemplate.query(
sql,
new Object[] { theTableName.toUpperCase() },
(rs, rowNum) -> rs.getString("COLUMN_NAME")
);

ourLog.info("Columns in table '{}': {}", theTableName, columns);

return columns.stream()
.map(String::toUpperCase)
.anyMatch(columnName -> columnName.equals(theColumnName.toUpperCase()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.HapiMigrationException;
import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import ca.uhn.fhir.jpa.migrate.entity.HapiMigrationEntity;
import ca.uhn.fhir.jpa.migrate.tasks.api.TaskFlagEnum;
import jakarta.annotation.Nonnull;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.sql.SQLException;
import java.util.List;
import java.util.function.Supplier;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -279,6 +281,9 @@ public void testFailureAllowed(Supplier<TestDatabaseDetails> theTestDatabaseDeta
getMigrator().migrate();
assertEquals(ColumnTypeEnum.STRING, JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL").getColumnTypeEnum());

List<HapiMigrationEntity> entities = myHapiMigrationDao.findAll();
assertThat(entities).hasSize(1);
assertThat(entities.get(0).getResult()).isEqualTo(MigrationTaskExecutionResultEnum.ALLOWED_TO_FAIL.name());
}

@ParameterizedTest(name = "{index}: {0}")
Expand Down
Loading