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% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isBlank; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import static org.apache.commons.lang3.StringUtils.isNotBlank; 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.io.IOException;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.persistence.*; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
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;
public class TestUtil { public class TestUtil {
private static final int MAX_LENGTH = 30; private static final int MAX_LENGTH = 30;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
/** non instantiable */ /**
* non instantiable
*/
private TestUtil() { private TestUtil() {
super(); super();
} }
@ -119,6 +118,7 @@ public class TestUtil {
Column column = ae.getAnnotation(Column.class); Column column = ae.getAnnotation(Column.class);
if (column != null) { if (column != null) {
assertNotADuplicateName(column.name(), 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); GeneratedValue gen = ae.getAnnotation(GeneratedValue.class);

View File

@ -11,11 +11,21 @@ import javax.annotation.Nonnull;
/*- /*-
* #%L * #%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% * #L%
*/ */

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate; 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.jpa.migrate.taskdef.BaseTableColumnTypeTask;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -94,7 +114,7 @@ public class JdbcUtils {
case Types.VARCHAR: case Types.VARCHAR:
return BaseTableColumnTypeTask.ColumnTypeEnum.STRING.getDescriptor(length); return BaseTableColumnTypeTask.ColumnTypeEnum.STRING.getDescriptor(length);
case Types.BIGINT: case Types.BIGINT:
return BaseTableColumnTypeTask.ColumnTypeEnum.LONG.getDescriptor(length); return BaseTableColumnTypeTask.ColumnTypeEnum.LONG.getDescriptor(null);
default: default:
throw new IllegalArgumentException("Don't know how to handle datatype: " + dataType); throw new IllegalArgumentException("Don't know how to handle datatype: " + dataType);
} }
@ -110,6 +130,32 @@ public class JdbcUtils {
/** /**
* Retrieve all index names * 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 { public static Set<String> getColumnNames(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException {
DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
Connection connection = dataSource.getConnection(); 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; 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.jpa.migrate.taskdef.BaseTask;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger; import org.slf4j.Logger;

View File

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

View File

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

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.ColumnMapRowMapper;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.apache.commons.lang3.Validate;
import org.thymeleaf.util.StringUtils; import org.thymeleaf.util.StringUtils;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.DriverTypeEnum;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.springframework.util.Assert; 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.ORACLE_12C, "varchar2(?)");
setColumnType(ColumnTypeEnum.STRING, DriverTypeEnum.POSTGRES_9_4, "varchar(?)"); setColumnType(ColumnTypeEnum.STRING, DriverTypeEnum.POSTGRES_9_4, "varchar(?)");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.DERBY_EMBEDDED, "timestamp"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.DERBY_EMBEDDED, "timestamp");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.MARIADB_10_1, "datetime(6)"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MARIADB_10_1, "datetime(6)");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.MYSQL_5_7, "datetime(6)"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MYSQL_5_7, "datetime(6)");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.MSSQL_2012, "datetime2"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MSSQL_2012, "datetime2");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.ORACLE_12C, "timestamp"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.ORACLE_12C, "timestamp");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMPT, DriverTypeEnum.POSTGRES_9_4, "timestamp"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.POSTGRES_9_4, "timestamp");
} }
public ColumnTypeEnum getColumnType() { public ColumnTypeEnum getColumnType() {
@ -93,7 +113,7 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> extends B
} }
protected String getSqlNotNull() { protected String getSqlNotNull() {
return isNullable() ? "" : " not null"; return isNullable() ? " null" : " not null";
} }
public Long getColumnLength() { public Long getColumnLength() {
@ -121,7 +141,7 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> extends B
return "varchar(" + theColumnLength + ")"; return "varchar(" + theColumnLength + ")";
} }
}, },
DATE_TIMESTAMPT{ DATE_TIMESTAMP {
@Override @Override
public String getDescriptor(Long theColumnLength) { public String getDescriptor(Long theColumnLength) {
Assert.isTrue(theColumnLength == null, "Must not supply a column length"); Assert.isTrue(theColumnLength == null, "Must not supply a column length");

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.apache.commons.lang3.Validate;
public abstract class BaseTableTask<T extends BaseTableTask> extends BaseTask { public abstract class BaseTableTask<T extends BaseTableTask> extends BaseTask {

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.DriverTypeEnum;
import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.Language;
import org.slf4j.Logger; import org.slf4j.Logger;

View File

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

View File

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

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.taskdef; 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.jpa.migrate.JdbcUtils;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -16,39 +36,55 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask<ModifyColumnTask>
public void execute() { public void execute() {
String existingType; String existingType;
boolean nullable;
try { try {
existingType = JdbcUtils.getColumnType(getConnectionProperties(), getTableName(), getColumnName()); existingType = JdbcUtils.getColumnType(getConnectionProperties(), getTableName(), getColumnName());
nullable = JdbcUtils.isColumnNullable(getConnectionProperties(), getTableName(), getColumnName());
} catch (SQLException e) { } catch (SQLException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
String wantedType = getColumnType().getDescriptor(getColumnLength()); String wantedType = getColumnType().getDescriptor(getColumnLength());
if (existingType.equals(wantedType)) { boolean alreadyOfCorrectType = existingType.equals(wantedType);
ourLog.info("Column {} on table {} is already of type {} - No action performed", getColumnName(), getTableName(), 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; return;
} }
String type = getSqlType(); String type = getSqlType();
String notNull = getSqlNotNull(); String notNull = getSqlNotNull();
String sql; String sql = null;
String sqlNotNull = null; String sqlNotNull = null;
switch (getDriverType()) { switch (getDriverType()) {
case DERBY_EMBEDDED: case DERBY_EMBEDDED:
if (!alreadyOfCorrectType) {
sql = "alter table " + getTableName() + " alter column " + getColumnName() + " set data type " + type; sql = "alter table " + getTableName() + " alter column " + getColumnName() + " set data type " + type;
}
if (!alreadyCorrectNullable) {
sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + notNull;
}
break; break;
case MARIADB_10_1: case MARIADB_10_1:
case MYSQL_5_7: case MYSQL_5_7:
sql = "alter table " + getTableName() + " modify column " + getColumnName() + " " + type + notNull; sql = "alter table " + getTableName() + " modify column " + getColumnName() + " " + type + notNull;
break; break;
case POSTGRES_9_4: case POSTGRES_9_4:
if (!alreadyOfCorrectType) {
sql = "alter table " + getTableName() + " alter column " + getColumnName() + " type " + type; sql = "alter table " + getTableName() + " alter column " + getColumnName() + " type " + type;
if (isNullable() == false) { }
if (!alreadyCorrectNullable) {
if (isNullable()) {
sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + " set null";
} else {
sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + " set not null"; sqlNotNull = "alter table " + getTableName() + " alter column " + getColumnName() + " set not null";
} }
}
break; break;
case ORACLE_12C: case ORACLE_12C:
sql = "alter table " + getTableName() + " modify " + getColumnName() + " " + type + notNull; String oracleNullableStmt = !alreadyCorrectNullable ? notNull : "";
sql = "alter table " + getTableName() + " modify ( " + getColumnName() + " " + type + oracleNullableStmt + " )";
break; break;
case MSSQL_2012: case MSSQL_2012:
sql = "alter table " + getTableName() + " alter column " + getColumnName() + " " + type + notNull; 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); ourLog.info("Updating column {} on table {} to type {}", getColumnName(), getTableName(), type);
if (sql != null) {
executeSql(sql); executeSql(sql);
}
if (sqlNotNull != null) { if (sqlNotNull != null) {
ourLog.info("Updating column {} on table {} to not null", getColumnName(), getTableName()); ourLog.info("Updating column {} on table {} to not null", getColumnName(), getTableName());

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.migrate.tasks; 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.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; 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; import ca.uhn.fhir.util.VersionEnum;
@SuppressWarnings({"UnstableApiUsage", "SqlNoDataSourceInspection", "SpellCheckingInspection"}) @SuppressWarnings({"UnstableApiUsage", "SqlNoDataSourceInspection", "SpellCheckingInspection"})
public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
/** /**
* Constructor * Constructor
@ -259,7 +279,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
trmConcept trmConcept
.addColumn("CONCEPT_UPDATED") .addColumn("CONCEPT_UPDATED")
.nullable() .nullable()
.type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMPT); .type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP);
trmConcept trmConcept
.addIndex("IDX_CONCEPT_UPDATED") .addIndex("IDX_CONCEPT_UPDATED")
.unique(false) .unique(false)

View File

@ -1,11 +1,30 @@
package ca.uhn.fhir.jpa.migrate.tasks.api; 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.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.taskdef.*; 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.Multimap;
import com.google.common.collect.MultimapBuilder; import com.google.common.collect.MultimapBuilder;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.Language;
@ -14,24 +33,25 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public class BaseMigrationTasks { public class BaseMigrationTasks<T extends Enum> {
private Multimap<VersionEnum, BaseTask<?>> myTasks = MultimapBuilder.hashKeys().arrayListValues().build(); 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(theFrom);
Validate.notNull(theTo); Validate.notNull(theTo);
Validate.isTrue(theFrom.ordinal() < theTo.ordinal(), "From version must be lower than to version"); Validate.isTrue(theFrom.ordinal() < theTo.ordinal(), "From version must be lower than to version");
List<BaseTask<?>> retVal = new ArrayList<>(); List<BaseTask<?>> retVal = new ArrayList<>();
for (VersionEnum nextVersion : VersionEnum.values()) { for (Object nextVersion : EnumUtils.getEnumList(theFrom.getClass())) {
if (nextVersion.ordinal() <= theFrom.ordinal()) { if (((T)nextVersion).ordinal() <= theFrom.ordinal()) {
continue; continue;
} }
if (nextVersion.ordinal() > theTo.ordinal()) { if (((T)nextVersion).ordinal() > theTo.ordinal()) {
continue; continue;
} }
Collection<BaseTask<?>> nextValues = myTasks.get(nextVersion); Collection<BaseTask<?>> nextValues = myTasks.get((T)nextVersion);
if (nextValues != null) { if (nextValues != null) {
retVal.addAll(nextValues); retVal.addAll(nextValues);
} }
@ -40,16 +60,16 @@ public class BaseMigrationTasks {
return retVal; return retVal;
} }
protected HapiFhirJpaMigrationTasks.Builder forVersion(VersionEnum theVersion) { protected Builder forVersion(T theVersion) {
return new HapiFhirJpaMigrationTasks.Builder(theVersion); return new Builder(theVersion);
} }
protected class Builder { protected class Builder {
private final VersionEnum myVersion; private final T myVersion;
private String myTableName; private String myTableName;
Builder(VersionEnum theVersion) { Builder(T theVersion) {
myVersion = theVersion; myVersion = theVersion;
} }
@ -68,14 +88,16 @@ public class BaseMigrationTasks {
return new BuilderAddTable(); return new BuilderAddTable();
} }
public void startSectionWithMessage(String theMessage) { public Builder startSectionWithMessage(String theMessage) {
Validate.notBlank(theMessage); Validate.notBlank(theMessage);
addTask(new LogStartSectionWithMessageTask(theMessage)); addTask(new LogStartSectionWithMessageTask(theMessage));
return this;
} }
public class BuilderWithTableName { public class BuilderWithTableName {
private String myIndexName; private String myIndexName;
private String myColumnName; private String myColumnName;
private String myForeignKeyName;
public String getTableName() { public String getTableName() {
return myTableName; return myTableName;
@ -108,6 +130,11 @@ public class BaseMigrationTasks {
return new BuilderModifyColumnWithName(); return new BuilderModifyColumnWithName();
} }
public BuilderAddForeignKey addForeignKey(String theForeignKeyName) {
myForeignKeyName = theForeignKeyName;
return new BuilderAddForeignKey();
}
public class BuilderAddIndexWithName { public class BuilderAddIndexWithName {
private boolean myUnique; private boolean myUnique;
@ -163,8 +190,15 @@ public class BaseMigrationTasks {
public class BuilderModifyColumnWithNameAndNullable { public class BuilderModifyColumnWithNameAndNullable {
public void withType(BaseTableColumnTypeTask.ColumnTypeEnum theColumnType) {
withType(theColumnType, 0);
}
public void withType(BaseTableColumnTypeTask.ColumnTypeEnum theColumnType, int theLength) { public void withType(BaseTableColumnTypeTask.ColumnTypeEnum theColumnType, int theLength) {
if (theColumnType == BaseTableColumnTypeTask.ColumnTypeEnum.STRING) { 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(); ModifyColumnTask task = new ModifyColumnTask();
task.setColumnName(myColumnName); task.setColumnName(myColumnName);
task.setTableName(myTableName); task.setTableName(myTableName);
@ -172,13 +206,32 @@ public class BaseMigrationTasks {
task.setNullable(myNullable); task.setNullable(myNullable);
task.setColumnType(theColumnType); task.setColumnType(theColumnType);
addTask(task); addTask(task);
} else { } else if (theLength > 0){
throw new IllegalArgumentException("Can not specify length for column of type " + theColumnType); 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 { 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 java.sql.SQLException;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class ModifyColumnTest extends BaseTest { public class ModifyColumnTest extends BaseTest {
@ -27,6 +25,90 @@ public class ModifyColumnTest extends BaseTest {
getMigrator().migrate(); getMigrator().migrate();
assertEquals("varchar(300)", JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); 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(); new HapiFhirJpaMigrationTasks();
} }
} }