diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java index e4f18aa9ef0..a61a81d6f38 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.util; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,45 +20,44 @@ package ca.uhn.fhir.jpa.util; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.ClassPath; +import com.google.common.reflect.ClassPath.ClassInfo; +import org.apache.commons.lang3.Validate; +import javax.persistence.*; import java.io.IOException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Set; -import javax.persistence.*; - -import org.apache.commons.lang3.Validate; - -import com.google.common.collect.ImmutableSet; -import com.google.common.reflect.ClassPath; -import com.google.common.reflect.ClassPath.ClassInfo; - -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class TestUtil { private static final int MAX_LENGTH = 30; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class); - /** non instantiable */ + /** + * non instantiable + */ private TestUtil() { super(); } - + /** * This is really only useful for unit tests, do not call otherwise */ public static void scanEntities(String packageName) throws IOException, ClassNotFoundException { ImmutableSet classes = ClassPath.from(TestUtil.class.getClassLoader()).getTopLevelClasses(packageName); Set names = new HashSet(); - + if (classes.size() <= 1) { throw new InternalErrorException("Found no classes"); } - + for (ClassInfo classInfo : classes) { Class clazz = Class.forName(classInfo.getName()); Entity entity = clazz.getAnnotation(Entity.class); @@ -101,7 +100,7 @@ public class TestUtil { Validate.isTrue(nextConstraint.name().startsWith("IDX_"), nextConstraint.name() + " must start with IDX_"); } } - + JoinColumn joinColumn = ae.getAnnotation(JoinColumn.class); if (joinColumn != null) { assertNotADuplicateName(joinColumn.name(), null); @@ -119,6 +118,7 @@ public class TestUtil { Column column = ae.getAnnotation(Column.class); if (column != null) { assertNotADuplicateName(column.name(), null); + Validate.isTrue(column.unique() == false, "Should not use unique attribute on column (use named @UniqueConstraint instead) on " + ae.toString()); } GeneratedValue gen = ae.getAnnotation(GeneratedValue.class); 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 index 8695ce6d315..2c035237760 100644 --- 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 @@ -114,7 +114,7 @@ public class JdbcUtils { case Types.VARCHAR: return BaseTableColumnTypeTask.ColumnTypeEnum.STRING.getDescriptor(length); case Types.BIGINT: - return BaseTableColumnTypeTask.ColumnTypeEnum.LONG.getDescriptor(length); + return BaseTableColumnTypeTask.ColumnTypeEnum.LONG.getDescriptor(null); default: throw new IllegalArgumentException("Don't know how to handle datatype: " + dataType); } @@ -130,6 +130,32 @@ public class JdbcUtils { /** * Retrieve all index names */ + public static Set getForeignKeys(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theForeignTable) throws SQLException { + DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); + Connection connection = dataSource.getConnection(); + return theConnectionProperties.getTxTemplate().execute(t -> { + DatabaseMetaData metadata; + try { + metadata = connection.getMetaData(); + ResultSet indexes = metadata.getCrossReference(null, null, theTableName, null, null, theForeignTable); + + Set columnNames = new HashSet<>(); + while (indexes.next()) { + String fkName = indexes.getString("FK_NAME"); + fkName = StringUtils.toUpperCase(fkName, Locale.US); + columnNames.add(fkName); + } + + return columnNames; + } catch (SQLException e) { + throw new InternalErrorException(e); + } + }); + } + + /** + * Retrieve all index names + */ public static Set getColumnNames(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException { DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); Connection connection = dataSource.getConnection(); @@ -182,4 +208,35 @@ public class JdbcUtils { } }); } + + public static boolean isColumnNullable(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theColumnName) throws SQLException { + DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); + Connection connection = dataSource.getConnection(); + //noinspection ConstantConditions + return theConnectionProperties.getTxTemplate().execute(t -> { + DatabaseMetaData metadata; + try { + metadata = connection.getMetaData(); + ResultSet tables = metadata.getColumns(null, null, theTableName, theColumnName); + + while (tables.next()) { + if (theColumnName.equals(tables.getString("COLUMN_NAME"))) { + String nullable = tables.getString("IS_NULLABLE"); + if ("YES".equals(nullable)) { + return true; + } else if ("NO".equals(nullable)) { + return false; + } else { + throw new IllegalStateException("Unknown nullable: " + nullable); + } + } + } + + throw new IllegalStateException("Did not find column " + theColumnName); + } catch (SQLException e) { + throw new InternalErrorException(e); + } + }); + + } } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java new file mode 100644 index 00000000000..82591722bdb --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java @@ -0,0 +1,86 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +/*- + * #%L + * HAPI FHIR JPA Server - Migration + * %% + * Copyright (C) 2014 - 2018 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +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; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class AddForeignKeyTask extends BaseTableColumnTask { + + private static final Logger ourLog = LoggerFactory.getLogger(AddForeignKeyTask.class); + private String myConstraintName; + private String myForeignTableName; + private String myForeignColumnName; + + public void setConstraintName(String theConstraintName) { + myConstraintName = theConstraintName; + } + + public void setForeignTableName(String theForeignTableName) { + myForeignTableName = theForeignTableName; + } + + public void setForeignColumnName(String theForeignColumnName) { + myForeignColumnName = theForeignColumnName; + } + + @Override + public void validate() { + super.validate(); + + Validate.isTrue(isNotBlank(myConstraintName)); + Validate.isTrue(isNotBlank(myForeignTableName)); + Validate.isTrue(isNotBlank(myForeignColumnName)); + } + + @Override + public void execute() throws SQLException { + + Set existing = JdbcUtils.getForeignKeys(getConnectionProperties(), myForeignTableName, getTableName()); + if (existing.contains(myConstraintName)) { + ourLog.info("Already have constraint named {} - No action performed", myConstraintName); + return; + } + + String sql = null; + switch (getDriverType()) { + case MARIADB_10_1: + case MYSQL_5_7: + sql = "alter table " + getTableName() + " add constraint " + myConstraintName + " foreign key (" + getColumnName() + ") references " + myForeignTableName + " (" + myForeignColumnName + ")"; + break; + case POSTGRES_9_4: + case DERBY_EMBEDDED: + case ORACLE_12C: + case MSSQL_2012: + sql = "alter table " + getTableName() + " add constraint " + myConstraintName + " foreign key (" + getColumnName() + ") references " + myForeignTableName; + break; + } + executeSql(sql); + } + +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java index 8d26c326d25..91e9c3689c4 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java @@ -53,12 +53,12 @@ public abstract class BaseTableColumnTypeTask extends B setColumnType(ColumnTypeEnum.STRING, DriverTypeEnum.ORACLE_12C, "varchar2(?)"); setColumnType(ColumnTypeEnum.STRING, DriverTypeEnum.POSTGRES_9_4, "varchar(?)"); - setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.DERBY_EMBEDDED, "timestamp"); - setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.MARIADB_10_1, "datetime(6)"); - setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.MYSQL_5_7, "datetime(6)"); - setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.MSSQL_2012, "datetime2"); - setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.ORACLE_12C, "timestamp"); - setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.POSTGRES_9_4, "timestamp"); + setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.DERBY_EMBEDDED, "timestamp"); + setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MARIADB_10_1, "datetime(6)"); + setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MYSQL_5_7, "datetime(6)"); + setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MSSQL_2012, "datetime2"); + setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.ORACLE_12C, "timestamp"); + setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.POSTGRES_9_4, "timestamp"); } public ColumnTypeEnum getColumnType() { @@ -113,7 +113,7 @@ public abstract class BaseTableColumnTypeTask extends B } protected String getSqlNotNull() { - return isNullable() ? "" : " not null"; + return isNullable() ? " null" : " not null"; } public Long getColumnLength() { @@ -141,7 +141,7 @@ public abstract class BaseTableColumnTypeTask extends B return "varchar(" + theColumnLength + ")"; } }, - DATE_TIMESTAMPT{ + DATE_TIMESTAMP { @Override public String getDescriptor(Long theColumnLength) { Assert.isTrue(theColumnLength == null, "Must not supply a column length"); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java index 829cff579b0..e6dec8aebfe 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java @@ -36,39 +36,55 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask public void execute() { String existingType; + boolean nullable; try { existingType = JdbcUtils.getColumnType(getConnectionProperties(), getTableName(), getColumnName()); + nullable = JdbcUtils.isColumnNullable(getConnectionProperties(), getTableName(), getColumnName()); } catch (SQLException e) { throw new InternalErrorException(e); } String wantedType = getColumnType().getDescriptor(getColumnLength()); - if (existingType.equals(wantedType)) { - ourLog.info("Column {} on table {} is already of type {} - No action performed", getColumnName(), getTableName(), wantedType); + boolean alreadyOfCorrectType = existingType.equals(wantedType); + boolean alreadyCorrectNullable = isNullable() == nullable; + if (alreadyOfCorrectType && alreadyCorrectNullable) { + ourLog.info("Column {} on table {} is already of type {} and has nullable {} - No action performed", getColumnName(), getTableName(), wantedType, nullable); return; } String type = getSqlType(); String notNull = getSqlNotNull(); - String sql; + String sql = null; String sqlNotNull = null; switch (getDriverType()) { case DERBY_EMBEDDED: - sql = "alter table " + getTableName() + " alter column " + getColumnName() + " set data type " + type; + if (!alreadyOfCorrectType) { + sql = "alter table " + getTableName() + " alter column " + getColumnName() + " set data type " + type; + } + if (!alreadyCorrectNullable) { + sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + notNull; + } break; case MARIADB_10_1: case MYSQL_5_7: sql = "alter table " + getTableName() + " modify column " + getColumnName() + " " + type + notNull; break; case POSTGRES_9_4: - sql = "alter table " + getTableName() + " alter column " + getColumnName() + " type " + type; - if (isNullable() == false) { - sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + " set not null"; + if (!alreadyOfCorrectType) { + sql = "alter table " + getTableName() + " alter column " + getColumnName() + " type " + type; + } + if (!alreadyCorrectNullable) { + if (isNullable()) { + sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + " set null"; + } else { + sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + " set not null"; + } } break; case ORACLE_12C: - sql = "alter table " + getTableName() + " modify " + getColumnName() + " " + type + notNull; + String oracleNullableStmt = !alreadyCorrectNullable ? notNull : ""; + sql = "alter table " + getTableName() + " modify ( " + getColumnName() + " " + type + oracleNullableStmt + " )"; break; case MSSQL_2012: sql = "alter table " + getTableName() + " alter column " + getColumnName() + " " + type + notNull; @@ -78,7 +94,9 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask } ourLog.info("Updating column {} on table {} to type {}", getColumnName(), getTableName(), type); - executeSql(sql); + if (sql != null) { + executeSql(sql); + } if (sqlNotNull != null) { ourLog.info("Updating column {} on table {} to not null", getColumnName(), getTableName()); 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 index 7d243217e47..a4c0792caef 100644 --- 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 @@ -31,7 +31,7 @@ import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks; import ca.uhn.fhir.util.VersionEnum; @SuppressWarnings({"UnstableApiUsage", "SqlNoDataSourceInspection", "SpellCheckingInspection"}) -public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { +public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { /** * Constructor @@ -279,7 +279,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { trmConcept .addColumn("CONCEPT_UPDATED") .nullable() - .type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMPT); + .type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP); trmConcept .addIndex("IDX_CONCEPT_UPDATED") .unique(false) diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/BaseMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/BaseMigrationTasks.java index 23713e4c259..551f5fec96b 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/BaseMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/BaseMigrationTasks.java @@ -22,10 +22,9 @@ package ca.uhn.fhir.jpa.migrate.tasks.api; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.taskdef.*; -import ca.uhn.fhir.jpa.migrate.tasks.HapiFhirJpaMigrationTasks; -import ca.uhn.fhir.util.VersionEnum; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.Validate; import org.intellij.lang.annotations.Language; @@ -34,24 +33,25 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class BaseMigrationTasks { - private Multimap> myTasks = MultimapBuilder.hashKeys().arrayListValues().build(); +public class BaseMigrationTasks { + private Multimap> myTasks = MultimapBuilder.hashKeys().arrayListValues().build(); - public List> getTasks(@Nonnull VersionEnum theFrom, @Nonnull VersionEnum theTo) { + @SuppressWarnings("unchecked") + public List> getTasks(@Nonnull T theFrom, @Nonnull T theTo) { Validate.notNull(theFrom); Validate.notNull(theTo); Validate.isTrue(theFrom.ordinal() < theTo.ordinal(), "From version must be lower than to version"); List> retVal = new ArrayList<>(); - for (VersionEnum nextVersion : VersionEnum.values()) { - if (nextVersion.ordinal() <= theFrom.ordinal()) { + for (Object nextVersion : EnumUtils.getEnumList(theFrom.getClass())) { + if (((T)nextVersion).ordinal() <= theFrom.ordinal()) { continue; } - if (nextVersion.ordinal() > theTo.ordinal()) { + if (((T)nextVersion).ordinal() > theTo.ordinal()) { continue; } - Collection> nextValues = myTasks.get(nextVersion); + Collection> nextValues = myTasks.get((T)nextVersion); if (nextValues != null) { retVal.addAll(nextValues); } @@ -60,16 +60,16 @@ public class BaseMigrationTasks { return retVal; } - protected HapiFhirJpaMigrationTasks.Builder forVersion(VersionEnum theVersion) { - return new HapiFhirJpaMigrationTasks.Builder(theVersion); + protected Builder forVersion(T theVersion) { + return new Builder(theVersion); } protected class Builder { - private final VersionEnum myVersion; + private final T myVersion; private String myTableName; - Builder(VersionEnum theVersion) { + Builder(T theVersion) { myVersion = theVersion; } @@ -88,14 +88,16 @@ public class BaseMigrationTasks { return new BuilderAddTable(); } - public void startSectionWithMessage(String theMessage) { + public Builder startSectionWithMessage(String theMessage) { Validate.notBlank(theMessage); addTask(new LogStartSectionWithMessageTask(theMessage)); + return this; } public class BuilderWithTableName { private String myIndexName; private String myColumnName; + private String myForeignKeyName; public String getTableName() { return myTableName; @@ -128,6 +130,11 @@ public class BaseMigrationTasks { return new BuilderModifyColumnWithName(); } + public BuilderAddForeignKey addForeignKey(String theForeignKeyName) { + myForeignKeyName = theForeignKeyName; + return new BuilderAddForeignKey(); + } + public class BuilderAddIndexWithName { private boolean myUnique; @@ -183,8 +190,15 @@ public class BaseMigrationTasks { public class BuilderModifyColumnWithNameAndNullable { + public void withType(BaseTableColumnTypeTask.ColumnTypeEnum theColumnType) { + withType(theColumnType, 0); + } + public void withType(BaseTableColumnTypeTask.ColumnTypeEnum theColumnType, int theLength) { if (theColumnType == BaseTableColumnTypeTask.ColumnTypeEnum.STRING) { + if (theLength == 0) { + throw new IllegalArgumentException("Can not specify length 0 for column of type " + theColumnType); + } ModifyColumnTask task = new ModifyColumnTask(); task.setColumnName(myColumnName); task.setTableName(myTableName); @@ -192,13 +206,32 @@ public class BaseMigrationTasks { task.setNullable(myNullable); task.setColumnType(theColumnType); addTask(task); - } else { + } else if (theLength > 0){ throw new IllegalArgumentException("Can not specify length for column of type " + theColumnType); } } } } + + public class BuilderAddForeignKey extends BuilderModifyColumnWithName { + public BuilderAddForeignKeyToColumn toColumn(String theColumnName) { + myColumnName = theColumnName; + return new BuilderAddForeignKeyToColumn(); + } + + public class BuilderAddForeignKeyToColumn { + public void references(String theForeignTable, String theForeignColumn) { + AddForeignKeyTask task = new AddForeignKeyTask(); + task.setTableName(myTableName); + task.setConstraintName(myForeignKeyName); + task.setColumnName(myColumnName); + task.setForeignTableName(theForeignTable); + task.setForeignColumnName(theForeignColumn); + addTask(task); + } + } + } } public class BuilderAddTable { diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java new file mode 100644 index 00000000000..0f955faaf2c --- /dev/null +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.JdbcUtils; +import org.hamcrest.Matchers; +import org.junit.Test; + +import java.sql.SQLException; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertThat; + +public class AddForeignKeyTaskTest extends BaseTest { + + @Test + public void testAddForeignKey() throws SQLException { + executeSql("create table HOME (PID bigint not null, TEXTCOL varchar(255), primary key (PID))"); + executeSql("create table FOREIGNTBL (PID bigint not null, HOMEREF bigint)"); + assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "HOME", "FOREIGNTBL"), empty()); + + AddForeignKeyTask task = new AddForeignKeyTask(); + task.setTableName("FOREIGNTBL"); + task.setColumnName("HOMEREF"); + task.setConstraintName("FK_HOME_FOREIGN"); + task.setForeignColumnName("PID"); + task.setForeignTableName("HOME"); + getMigrator().addTask(task); + + getMigrator().migrate(); + + assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "HOME", "FOREIGNTBL"), Matchers.contains("FK_HOME_FOREIGN")); + + // Make sure additional calls don't crash + getMigrator().migrate(); + getMigrator().migrate(); + } + + +} diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java index f0c540af1c4..ad7ee9c11f7 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java @@ -5,9 +5,7 @@ import org.junit.Test; import java.sql.SQLException; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; public class ModifyColumnTest extends BaseTest { @@ -27,6 +25,90 @@ public class ModifyColumnTest extends BaseTest { getMigrator().migrate(); assertEquals("varchar(300)", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + + // Make sure additional migrations don't crash + getMigrator().migrate(); + getMigrator().migrate(); + + } + + @Test + public void testColumnMakeNullable() throws SQLException { + executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255) not null)"); + assertFalse(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "PID")); + assertFalse(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + assertEquals("bigint", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "PID")); + assertEquals("varchar(255)", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + + // PID + ModifyColumnTask task = new ModifyColumnTask(); + task.setTableName("SOMETABLE"); + task.setColumnName("PID"); + task.setColumnType(AddColumnTask.ColumnTypeEnum.LONG); + task.setNullable(true); + getMigrator().addTask(task); + + // STRING + task = new ModifyColumnTask(); + task.setTableName("SOMETABLE"); + task.setColumnName("TEXTCOL"); + task.setColumnType(AddColumnTask.ColumnTypeEnum.STRING); + task.setNullable(true); + task.setColumnLength(255); + getMigrator().addTask(task); + + // Do migration + getMigrator().migrate(); + + assertTrue(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "PID")); + assertTrue(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + assertEquals("bigint", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "PID")); + assertEquals("varchar(255)", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + + // Make sure additional migrations don't crash + getMigrator().migrate(); + getMigrator().migrate(); + + + } + + @Test + public void testColumnMakeNotNullable() throws SQLException { + executeSql("create table SOMETABLE (PID bigint, TEXTCOL varchar(255))"); + assertTrue(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "PID")); + assertTrue(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + assertEquals("bigint", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "PID")); + assertEquals("varchar(255)", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + + // PID + ModifyColumnTask task = new ModifyColumnTask(); + task.setTableName("SOMETABLE"); + task.setColumnName("PID"); + task.setColumnType(AddColumnTask.ColumnTypeEnum.LONG); + task.setNullable(false); + getMigrator().addTask(task); + + // STRING + task = new ModifyColumnTask(); + task.setTableName("SOMETABLE"); + task.setColumnName("TEXTCOL"); + task.setColumnType(AddColumnTask.ColumnTypeEnum.STRING); + task.setNullable(false); + task.setColumnLength(255); + getMigrator().addTask(task); + + // Do migration + getMigrator().migrate(); + + assertFalse(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "PID")); + assertFalse(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + assertEquals("bigint", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "PID")); + assertEquals("varchar(255)", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + + // Make sure additional migrations don't crash + getMigrator().migrate(); + getMigrator().migrate(); + } } 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 index b2e50ab0c08..4ff02d95a2d 100644 --- 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 @@ -9,4 +9,6 @@ public class HapiFhirJpaMigrationTasksTest { new HapiFhirJpaMigrationTasks(); } + + }