Work on migrator

This commit is contained in:
James Agnew 2018-09-14 08:34:39 -04:00
parent 640ee544c8
commit bb7fca19a3
22 changed files with 706 additions and 59 deletions

View File

@ -20,30 +20,29 @@ 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();
}
@ -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);

View File

@ -11,11 +11,21 @@ import javax.annotation.Nonnull;
/*-
* #%L
* Smile CDR - CDR
* HAPI FHIR JPA Server - Migration
* %%
* Copyright (C) 2016 - 2018 Simpatico Intelligent Systems Inc
* Copyright (C) 2014 - 2018 University Health Network
* %%
* All rights reserved.
* 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%
*/

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate;
/*-
* #%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.taskdef.BaseTableColumnTypeTask;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger;
@ -94,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);
}
@ -110,6 +130,32 @@ public class JdbcUtils {
/**
* Retrieve all index names
*/
public static Set<String> 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<String> 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<String> getColumnNames(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException {
DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
Connection connection = dataSource.getConnection();
@ -162,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);
}
});
}
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate;
/*-
* #%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.taskdef.BaseTask;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger;

View File

@ -1,5 +1,25 @@
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -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<AddForeignKeyTask> {
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<String> 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);
}
}

View File

@ -1,5 +1,25 @@
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;

View File

@ -1,5 +1,25 @@
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.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import org.apache.commons.lang3.Validate;

View File

@ -1,5 +1,25 @@
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.ColumnMapRowMapper;

View File

@ -1,5 +1,25 @@
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 org.apache.commons.lang3.Validate;
import org.thymeleaf.util.StringUtils;

View File

@ -1,5 +1,25 @@
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.DriverTypeEnum;
import org.apache.commons.lang3.Validate;
import org.springframework.util.Assert;
@ -33,12 +53,12 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> 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() {
@ -93,7 +113,7 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> extends B
}
protected String getSqlNotNull() {
return isNullable() ? "" : " not null";
return isNullable() ? " null" : " not null";
}
public Long getColumnLength() {
@ -121,7 +141,7 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> 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");

View File

@ -1,5 +1,25 @@
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 org.apache.commons.lang3.Validate;
public abstract class BaseTableTask<T extends BaseTableTask> extends BaseTask {

View File

@ -1,5 +1,25 @@
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.DriverTypeEnum;
import org.intellij.lang.annotations.Language;
import org.slf4j.Logger;

View File

@ -1,5 +1,25 @@
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.util.StopWatch;
import com.google.common.collect.ForwardingMap;
import org.apache.commons.lang3.Validate;

View File

@ -1,5 +1,25 @@
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;

View File

@ -1,5 +1,25 @@
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 org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,5 +1,25 @@
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 ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger;
@ -16,39 +36,55 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask<ModifyColumnTask>
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;
@ -58,7 +94,9 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask<ModifyColumnTask>
}
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());

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.tasks;
/*-
* #%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.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
@ -11,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<VersionEnum> {
/**
* Constructor
@ -259,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)

View File

@ -1,11 +1,30 @@
package ca.uhn.fhir.jpa.migrate.tasks.api;
/*-
* #%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.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;
@ -14,24 +33,25 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class BaseMigrationTasks {
private Multimap<VersionEnum, BaseTask<?>> myTasks = MultimapBuilder.hashKeys().arrayListValues().build();
public class BaseMigrationTasks<T extends Enum> {
private Multimap<T, BaseTask<?>> myTasks = MultimapBuilder.hashKeys().arrayListValues().build();
public List<BaseTask<?>> getTasks(@Nonnull VersionEnum theFrom, @Nonnull VersionEnum theTo) {
@SuppressWarnings("unchecked")
public List<BaseTask<?>> 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<BaseTask<?>> 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<BaseTask<?>> nextValues = myTasks.get(nextVersion);
Collection<BaseTask<?>> nextValues = myTasks.get((T)nextVersion);
if (nextValues != null) {
retVal.addAll(nextValues);
}
@ -40,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;
}
@ -68,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;
@ -108,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;
@ -163,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);
@ -172,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 {

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -9,4 +9,6 @@ public class HapiFhirJpaMigrationTasksTest {
new HapiFhirJpaMigrationTasks();
}
}