From 07cfed14b854aa8b786f5eb63f6844c0a1d44b00 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 27 Aug 2018 08:21:20 -0400 Subject: [PATCH] Work on migrator --- .../java/ca/uhn/fhir/util/VersionEnum.java | 8 ++ .../ca/uhn/fhir/util/VersionEnumTest.java | 28 +++++ hapi-fhir-jpaserver-base/pom.xml | 7 +- .../fhir/jpa/entity/IBaseResourceEntity.java | 17 ++- .../ResourceIndexedSearchParamCoords.java | 2 +- .../ResourceIndexedSearchParamQuantity.java | 1 - hapi-fhir-jpaserver-migrate/pom.xml | 45 ++++--- .../src/main/java/Migrator.java | 16 --- .../uhn/fhir/jpa/migrate/DriverTypeEnum.java | 94 +++++++++++++++ .../ca/uhn/fhir/jpa/migrate/JdbcUtils.java | 40 +++++++ .../ca/uhn/fhir/jpa/migrate/Migrator.java | 69 +++++++++++ .../jpa/migrate/taskdef/AddIndexTask.java | 62 ++++++++++ .../fhir/jpa/migrate/taskdef/BaseTask.java | 72 +++++++++++ .../migrate/taskdef/CalculateHashesTask.java | 57 +++++++++ .../jpa/migrate/taskdef/DropIndexTask.java | 67 +++++++++++ .../tasks/HapiFhirJpaMigrationTasks.java | 112 ++++++++++++++++++ .../jpa/migrate/taskdef/AddIndexTest.java | 50 ++++++++ .../fhir/jpa/migrate/taskdef/BaseTest.java | 54 +++++++++ .../jpa/migrate/taskdef/DropIndexTest.java | 46 +++++++ .../tasks/HapiFhirJpaMigrationTasksTest.java | 13 ++ .../src/test/resources/logback-test.xml | 14 +++ pom.xml | 5 + 22 files changed, 842 insertions(+), 37 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java delete mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/Migrator.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/DriverTypeEnum.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTask.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java create mode 100644 hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java create mode 100644 hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java create mode 100644 hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java create mode 100644 hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java create mode 100644 hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java create mode 100644 hapi-fhir-jpaserver-migrate/src/test/resources/logback-test.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java new file mode 100644 index 00000000000..02c9dad650f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java @@ -0,0 +1,8 @@ +package ca.uhn.fhir.util; + +public enum VersionEnum { + + V3_4_0, + V3_5_0 + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java new file mode 100644 index 00000000000..e7be7d7be79 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.util; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.junit.Assert.assertThat; + +public class VersionEnumTest { + + @Test + public void testCurrentVersionExists() { + List versions = Arrays.stream(VersionEnum.values()) + .map(Enum::name) + .collect(Collectors.toList()); + + String version = VersionUtil.getVersion(); + version = "V" + version.replace(".", "_"); + version = version.replace("-SNAPSHOT", ""); + + assertThat(versions, hasItem(version)); + } + + +} diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 0954f47e0de..cfe0b212030 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -236,7 +236,12 @@ commons-dbcp2 test - ca.uhn.hapi.fhir - hapi-fhir + hapi-deployable-pom 3.5.0-SNAPSHOT - ../pom.xml + ../hapi-deployable-pom/pom.xml hapi-fhir-jpaserver-migrate @@ -33,6 +27,11 @@ --> + + org.springframework + spring-jdbc + + ca.uhn.hapi.fhir @@ -40,17 +39,33 @@ ${project.version} - + + + org.apache.derby + derby + test + + + org.apache.commons + commons-dbcp2 + test + + + junit + junit + test + ch.qos.logback logback-classic + test - org.flywaydb - flyway-core - + com.intellij + annotations + 12.0 + @@ -81,8 +96,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 8 + 8 diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/Migrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/Migrator.java deleted file mode 100644 index ec2194fb046..00000000000 --- a/hapi-fhir-jpaserver-migrate/src/main/java/Migrator.java +++ /dev/null @@ -1,16 +0,0 @@ -package ca.uhn.fhir.jpa.migrate; - -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.Location; -import org.flywaydb.core.api.configuration.Configuration; -import org.flywaydb.core.api.configuration.FluentConfiguration; - -public class Migrator { - - public static void main(String[] theArgs) { - Configuration config = new FluentConfiguration() - .locations(new Location()); - new Flyway(config); - } - -} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/DriverTypeEnum.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/DriverTypeEnum.java new file mode 100644 index 00000000000..371791d3fdc --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/DriverTypeEnum.java @@ -0,0 +1,94 @@ +package ca.uhn.fhir.jpa.migrate; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SingleConnectionDataSource; +import org.springframework.transaction.support.TransactionTemplate; + +/*- + * #%L + * Smile CDR - CDR + * %% + * Copyright (C) 2016 - 2018 Simpatico Intelligent Systems Inc + * %% + * All rights reserved. + * #L% + */ + +public enum DriverTypeEnum { + + DERBY_EMBEDDED("org.apache.derby.jdbc.EmbeddedDriver", true), + MARIADB_10_1("org.mariadb.jdbc.Driver", false), + + // Formerly com.mysql.jdbc.Driver + MYSQL_5_7("com.mysql.cj.jdbc.Driver", false), + + POSTGRES_9_4("org.postgresql.Driver", false), + + ORACLE_12C("oracle.jdbc.OracleDriver", false), + + MSSQL_2012("com.microsoft.sqlserver.jdbc.SQLServerDataSource", false), + + ; + + private String myDriverClassName; + private boolean myDerby; + + /** + * Constructor + */ + DriverTypeEnum(String theDriverClassName, boolean theDerby) { + myDriverClassName = theDriverClassName; + myDerby = theDerby; + } + + + public ConnectionProperties newJdbcTemplate(String theUrl, String theUsername, String thePassword) { + SingleConnectionDataSource dataSource = new SingleConnectionDataSource(); + dataSource.setAutoCommit(false); + dataSource.setDriverClassName(myDriverClassName); + dataSource.setUrl(theUrl); + dataSource.setUsername(theUsername); + dataSource.setPassword(thePassword); + + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + transactionManager.afterPropertiesSet(); + + TransactionTemplate txTemplate = new TransactionTemplate(); + txTemplate.setTransactionManager(transactionManager); + txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + txTemplate.afterPropertiesSet(); + + return new ConnectionProperties(dataSource, txTemplate); + } + + public static class ConnectionProperties { + + private final SingleConnectionDataSource myDataSource; + private final TransactionTemplate myTxTemplate; + + public ConnectionProperties(SingleConnectionDataSource theDataSource, TransactionTemplate theTxTemplate) { + myDataSource = theDataSource; + myTxTemplate = theTxTemplate; + } + + public SingleConnectionDataSource getDataSource() { + return myDataSource; + } + + public JdbcTemplate newJdbcTemplate() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(myDataSource); + return jdbcTemplate; + } + + public TransactionTemplate getTxTemplate() { + return myTxTemplate; + } + + public void close() { + myDataSource.destroy(); + } + } +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java new file mode 100644 index 00000000000..001d143e816 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java @@ -0,0 +1,40 @@ +package ca.uhn.fhir.jpa.migrate; + +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class JdbcUtils { + /** + * Retrieve all index names + */ + public static Set getIndexNames(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException { + DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); + Connection connection = dataSource.getConnection(); + return theConnectionProperties.getTxTemplate().execute(t -> { + DatabaseMetaData metadata = null; + try { + metadata = connection.getMetaData(); + ResultSet indexes = metadata.getIndexInfo(null, null, theTableName, false, false); + + Set indexNames = new HashSet<>(); + while (indexes.next()) { + String indexName = indexes.getString("INDEX_NAME"); + indexNames.add(indexName); + } + + return indexNames; + } catch (SQLException e) { + throw new InternalErrorException(e); + } + }); + + } +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java new file mode 100644 index 00000000000..bbc7ccf03fc --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java @@ -0,0 +1,69 @@ +package ca.uhn.fhir.jpa.migrate; + +import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class Migrator { + + private static final Logger ourLog = LoggerFactory.getLogger(Migrator.class); + private DriverTypeEnum myDriverType; + private String myConnectionUrl; + private String myUsername; + private String myPassword; + private List myTasks = new ArrayList<>(); + private DriverTypeEnum.ConnectionProperties myConnectionProperties; + private int myChangesCount; + + public int getChangesCount() { + return myChangesCount; + } + + public void setDriverType(DriverTypeEnum theDriverType) { + myDriverType = theDriverType; + } + + public void setConnectionUrl(String theConnectionUrl) { + myConnectionUrl = theConnectionUrl; + } + + public void setUsername(String theUsername) { + myUsername = theUsername; + } + + public void setPassword(String thePassword) { + myPassword = thePassword; + } + + public void addTask(BaseTask theTask) { + myTasks.add(theTask); + } + + public void migrate() { + ourLog.info("Starting migration with {} tasks", myTasks.size()); + + myConnectionProperties = DriverTypeEnum.DERBY_EMBEDDED.newJdbcTemplate(myConnectionUrl, myUsername, myPassword); + try { + for (BaseTask next : myTasks) { + next.setDriverType(myDriverType); + next.setConnectionProperties(myConnectionProperties); + try { + next.execute(); + } catch (SQLException e) { + throw new InternalErrorException("Failure executing task \"" + next.getDescription() + "\", aborting! Cause: " + e.toString(), e); + } + + myChangesCount += next.getChangesCount(); + } + } finally { + myConnectionProperties.close(); + } + + ourLog.info("Finished migration of {} tasks", myTasks.size()); + } +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java new file mode 100644 index 00000000000..60e668d52b1 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java @@ -0,0 +1,62 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.JdbcUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class AddIndexTask extends BaseTask { + + private static final Logger ourLog = LoggerFactory.getLogger(AddIndexTask.class); + private String myTableName; + private String myIndexName; + private List myColumns; + private Boolean myUnique; + + public void setTableName(String theTableName) { + myTableName = theTableName; + } + + public void setIndexName(String theIndexName) { + myIndexName = theIndexName; + } + + public void setColumns(List theColumns) { + myColumns = theColumns; + } + + public void setUnique(boolean theUnique) { + myUnique = theUnique; + } + + @Override + public void validate() { + Validate.notBlank(myIndexName, "Index name not specified"); + Validate.notBlank(myTableName, "Table name not specified"); + Validate.isTrue(myColumns.size() > 0, "Columns not specified"); + Validate.notNull(myUnique, "Uniqueness not specified"); + } + + @Override + public void execute() throws SQLException { + Set indexNames = JdbcUtils.getIndexNames(getConnectionProperties(), myTableName); + if (indexNames.contains(myIndexName)) { + ourLog.info("Index {} already exists on table {} - No action performed", myIndexName, myTableName); + return; + } + + String unique = myUnique ? "UNIQUE " : ""; + String columns = String.join(", ", myColumns); + String sql = "CREATE " + unique + " INDEX " + myIndexName + " ON " + myTableName + "(" + columns + ")"; + executeSql(sql); + } + + public void setColumns(String... theColumns) { + setColumns(Arrays.asList(theColumns)); + } +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java new file mode 100644 index 00000000000..687aed0fdc7 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import org.intellij.lang.annotations.Language; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.support.TransactionTemplate; + +import java.sql.SQLException; + +public abstract class BaseTask { + + private static final Logger ourLog = LoggerFactory.getLogger(BaseTask.class); + private DriverTypeEnum.ConnectionProperties myConnectionProperties; + private DriverTypeEnum myDriverType; + private String myDescription; + private int myChangesCount; + + public String getDescription() { + return myDescription; + } + + public T setDescription(String theDescription) { + myDescription = theDescription; + return (T) this; + } + + public int getChangesCount() { + return myChangesCount; + } + + public void executeSql(@Language("SQL") String theSql, Object... theArguments) { + Integer changes = getConnectionProperties().getTxTemplate().execute(t -> { + JdbcTemplate jdbcTemplate = getConnectionProperties().newJdbcTemplate(); + int changesCount = jdbcTemplate.update(theSql, theArguments); + ourLog.info("SQL {} returned {}", theSql, changesCount); + return changesCount; + }); + + myChangesCount += changes; + + } + + public DriverTypeEnum.ConnectionProperties getConnectionProperties() { + return myConnectionProperties; + } + + public void setConnectionProperties(DriverTypeEnum.ConnectionProperties theConnectionProperties) { + myConnectionProperties = theConnectionProperties; + } + + public DriverTypeEnum getDriverType() { + return myDriverType; + } + + public void setDriverType(DriverTypeEnum theDriverType) { + myDriverType = theDriverType; + } + + public abstract void validate(); + + public TransactionTemplate getTxTemplate() { + return getConnectionProperties().getTxTemplate(); + } + + public JdbcTemplate newJdbcTemnplate() { + return getConnectionProperties().newJdbcTemplate(); + } + + public abstract void execute() throws SQLException; +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTask.java new file mode 100644 index 00000000000..c2beae48234 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTask.java @@ -0,0 +1,57 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +public class CalculateHashesTask extends BaseTask { + + private String myTableName; + private String myColumnName; + + public void setTableName(String theTableName) { + myTableName = theTableName; + } + + public void setColumnName(String theColumnName) { + myColumnName = theColumnName; + } + + @Override + public void validate() { + Validate.notBlank(myTableName); + Validate.notBlank(myColumnName); + } + + @Override + public void execute() { + List> rows = getTxTemplate().execute(t->{ + JdbcTemplate jdbcTemplate = newJdbcTemnplate(); + int batchSize = 10000; + jdbcTemplate.setMaxRows(batchSize); + String sql = "SELECT * FROM " + myTableName + " WHERE " + myColumnName + " IS NULL"; + ourLog.info("Loading up to {} rows in {} with no hashes", batchSize, myTableName); + return jdbcTemplate.queryForList(sql); + }); + + + } + + + private Map, Long>> myColumnMappers; + + public void addCalculator(String theColumnName, Function, Long> theConsumer) { + + } + + private static final Logger ourLog = LoggerFactory.getLogger(CalculateHashesTask.class); +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java new file mode 100644 index 00000000000..bceea5417ea --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java @@ -0,0 +1,67 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.JdbcUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.Set; + +public class DropIndexTask extends BaseTask { + + private static final Logger ourLog = LoggerFactory.getLogger(DropIndexTask.class); + private String myIndexName; + private String myTableName; + + @Override + public void validate() { + Validate.notBlank(myIndexName, "The index name must not be blank"); + Validate.notBlank(myTableName, "The table name must not be blank"); + + if (getDescription() == null) { + setDescription("Drop index " + myIndexName + " on table " + myTableName); + } + } + + @Override + public void execute() throws SQLException { + Set indexNames = JdbcUtils.getIndexNames(getConnectionProperties(), myTableName); + + if (!indexNames.contains(myIndexName)) { + ourLog.info("Index {} does not exist on table {} - No action needed", myIndexName, myTableName); + return; + } + + ourLog.info("Dropping index {} on table {}", myIndexName, myTableName); + + String sql = null; + switch (getDriverType()) { + case MYSQL_5_7: + case MARIADB_10_1: + sql = "ALTER TABLE " + myTableName + " DROP INDEX " + myIndexName; + break; + case POSTGRES_9_4: + case DERBY_EMBEDDED: + case ORACLE_12C: + sql = "DROP INDEX " + myIndexName; + break; + case MSSQL_2012: + sql = "DROP INDEX " + myTableName + "." + myIndexName; + break; + } + executeSql(sql); + + } + + + public DropIndexTask setTableName(String theTableName) { + myTableName = theTableName; + return this; + } + + public DropIndexTask setIndexName(String theIndexName) { + myIndexName = theIndexName; + return this; + } +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java new file mode 100644 index 00000000000..150915e9336 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -0,0 +1,112 @@ +package ca.uhn.fhir.jpa.migrate.tasks; + +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; +import ca.uhn.fhir.jpa.migrate.taskdef.AddIndexTask; +import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; +import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask; +import ca.uhn.fhir.jpa.migrate.taskdef.DropIndexTask; +import ca.uhn.fhir.util.VersionEnum; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; + +@SuppressWarnings("UnstableApiUsage") +public class HapiFhirJpaMigrationTasks { + + private Multimap> myTasks = MultimapBuilder.hashKeys().arrayListValues().build(); + + /** + * Constructor + */ + public HapiFhirJpaMigrationTasks() { + + // Forced ID changes + forVersion(VersionEnum.V3_5_0) + .onTable("HFJ_FORCED_ID") + .dropIndex("IDX_FORCEDID_TYPE_FORCEDID"); + forVersion(VersionEnum.V3_5_0) + .onTable("HFJ_FORCED_ID") + .dropIndex("IDX_FORCEDID_TYPE_RESID"); + forVersion(VersionEnum.V3_5_0) + .onTable("HFJ_FORCED_ID") + .addIndex("IDX_FORCEDID_TYPE_FID") + .unique(true) + .withColumns("RESOURCE_TYPE", "FORCED_ID"); + + // Indexes - Coords + forVersion(VersionEnum.V3_5_0) + .onTable("HFJ_SPIDX_COORDS") + .dropIndex("IDX_SP_COORDS_HASH"); + forVersion(VersionEnum.V3_5_0) + .onTable("HFJ_SPIDX_COORDS") + .addIndex("IDX_SP_COORDS_HASH") + .unique(false) + .withColumns("HASH_IDENTITY", "SP_VALUE", "SP_LATITUDE", "SP_LONGITUDE"); + forVersion(VersionEnum.V3_5_0) + .addTask(new CalculateHashesTask().calculator(()->{ + return ResourceIndexedSearchParamCoords.calculateHashIdentity("resourceType", "paramName"); + })); + + } + + private Builder forVersion(VersionEnum theVersion) { + return new Builder(theVersion); + } + + + private class Builder { + + private final VersionEnum myVersion; + private String myTableName; + + public Builder(VersionEnum theVersion) { + myVersion = theVersion; + } + + public BuilderWithTableName onTable(String theTableName) { + myTableName = theTableName; + return new BuilderWithTableName(); + } + + private void addTask(BaseTask theTask) { + theTask.validate(); + myTasks.put(myVersion, theTask); + } + + private class BuilderWithTableName { + private String myIndexName; + + void dropIndex(String theIndexName) { + DropIndexTask task = new DropIndexTask(); + task.setIndexName(theIndexName); + task.setTableName(myTableName); + addTask(task); + } + + public BuilderAddIndexWithName addIndex(String theIndexName) { + myIndexName = theIndexName; + return new BuilderAddIndexWithName(); + } + + private class BuilderAddIndexWithName { + private boolean myUnique; + + public BuilderAddIndexUnique unique(boolean theUnique) { + myUnique = theUnique; + return new BuilderAddIndexUnique(); + } + + private class BuilderAddIndexUnique { + public void withColumns(String... theColumnNames) { + AddIndexTask task = new AddIndexTask(); + task.setTableName(myTableName); + task.setIndexName(myIndexName); + task.setUnique(myUnique); + task.setColumns(theColumnNames); + addTask(task); + } + } + } + } + } + +} diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java new file mode 100644 index 00000000000..bfaf62fe938 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.JdbcUtils; +import com.google.common.collect.Lists; +import org.junit.Test; + +import java.sql.SQLException; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +public class AddIndexTest extends BaseTest { + + @Test + public void testIndexAlreadyExists() throws SQLException { + executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); + executeSql("create unique index IDX_ANINDEX on SOMETABLE (PID, TEXTCOL)"); + executeSql("create unique index IDX_DIFINDEX on SOMETABLE (TEXTCOL)"); + + AddIndexTask task = new AddIndexTask(); + task.setIndexName("IDX_ANINDEX"); + task.setTableName("SOMETABLE"); + task.setColumns("PID", "TEXTCOL"); + task.setUnique(false); + getMigrator().addTask(task); + + getMigrator().migrate(); + + assertThat(JdbcUtils.getIndexNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("IDX_DIFINDEX", "IDX_ANINDEX")); + } + + @Test + public void testIndexDoesntAlreadyExist() throws SQLException { + executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); + executeSql("create unique index IDX_DIFINDEX on SOMETABLE (TEXTCOL)"); + + AddIndexTask task = new AddIndexTask(); + task.setIndexName("IDX_ANINDEX"); + task.setTableName("SOMETABLE"); + task.setColumns("PID", "TEXTCOL"); + task.setUnique(false); + getMigrator().addTask(task); + + getMigrator().migrate(); + + assertThat(JdbcUtils.getIndexNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("IDX_DIFINDEX", "IDX_ANINDEX")); + } + +} diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java new file mode 100644 index 00000000000..8f60d81373c --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.fhir.jpa.migrate.Migrator; +import org.intellij.lang.annotations.Language; +import org.junit.After; +import org.junit.Before; + +public class BaseTest { + + private static int ourDatabaseUrl = 0; + private String myUrl; + private Migrator myMigrator; + private DriverTypeEnum.ConnectionProperties myConnectionProperties; + + public String getUrl() { + return myUrl; + } + + public DriverTypeEnum.ConnectionProperties getConnectionProperties() { + return myConnectionProperties; + } + + + protected void executeSql(@Language("SQL") String theSql) { + myConnectionProperties.getTxTemplate().execute(t -> { + myConnectionProperties.newJdbcTemplate().execute(theSql); + return null; + }); + } + + public Migrator getMigrator() { + return myMigrator; + } + + @After + public void after() { + myConnectionProperties.close(); + } + + @Before() + public void before() { + myUrl = "jdbc:derby:memory:database " + (ourDatabaseUrl++) + ";create=true"; + + myConnectionProperties = DriverTypeEnum.DERBY_EMBEDDED.newJdbcTemplate(myUrl, "SA", "SA"); + + myMigrator = new Migrator(); + myMigrator.setConnectionUrl(myUrl); + myMigrator.setDriverType(DriverTypeEnum.DERBY_EMBEDDED); + myMigrator.setUsername("SA"); + myMigrator.setPassword("SA"); + } + +} diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java new file mode 100644 index 00000000000..8858fbe17ab --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.JdbcUtils; +import org.junit.Test; + +import java.sql.SQLException; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +public class DropIndexTest extends BaseTest { + + @Test + public void testIndexAlreadyExists() throws SQLException { + executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); + executeSql("create unique index IDX_ANINDEX on SOMETABLE (PID, TEXTCOL)"); + executeSql("create unique index IDX_DIFINDEX on SOMETABLE (TEXTCOL)"); + + DropIndexTask task = new DropIndexTask(); + task.setDescription("Drop an index"); + task.setIndexName("IDX_ANINDEX"); + task.setTableName("SOMETABLE"); + getMigrator().addTask(task); + + getMigrator().migrate(); + + assertThat(JdbcUtils.getIndexNames(getConnectionProperties(), "SOMETABLE"), contains("IDX_DIFINDEX")); + } + + @Test + public void testIndexDoesntAlreadyExist() throws SQLException { + executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); + executeSql("create unique index IDX_DIFINDEX on SOMETABLE (TEXTCOL)"); + + DropIndexTask task = new DropIndexTask(); + task.setDescription("Drop an index"); + task.setIndexName("IDX_ANINDEX"); + task.setTableName("SOMETABLE"); + getMigrator().addTask(task); + + getMigrator().migrate(); + + assertThat(JdbcUtils.getIndexNames(getConnectionProperties(), "SOMETABLE"), contains("IDX_DIFINDEX")); + } + +} diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java new file mode 100644 index 00000000000..bebbe38ca5e --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.jpa.migrate.tasks; + +import org.junit.Test; + +public class HapiFhirJpaMigrationTasksTest { + + @Test + public void testCreate() { + new HapiFhirJpaMigrationTasks(); + } + + +} diff --git a/hapi-fhir-jpaserver-migrate/src/test/resources/logback-test.xml b/hapi-fhir-jpaserver-migrate/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..dc857b12814 --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n + + + + + + + + + diff --git a/pom.xml b/pom.xml index e1379ae4c0f..8b23eed142a 100644 --- a/pom.xml +++ b/pom.xml @@ -1156,6 +1156,11 @@ spring-core ${spring_version} + + org.springframework + spring-jdbc + ${spring_version} + org.springframework.data spring-data-jpa