Mb new date search index (#3368)

* New date index definitions.
* Dropped unused indexes.
* Add online option to drop index and add index
* Push sp->resource FK down to control name
* Update annotations to match and redefine FK
* Continue to allow the legacy hibernate names while we update the indexing.
This commit is contained in:
michaelabuckley 2022-02-16 17:12:20 -05:00 committed by GitHub
parent ae33cf825b
commit 1c82d0933c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 564 additions and 91 deletions

View File

@ -62,6 +62,7 @@ import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -282,7 +283,14 @@ public class TestUtil {
} else {
Validate.notNull(fk);
Validate.isTrue(isNotBlank(fk.name()), "Foreign key on " + theAnnotatedElement + " has no name()");
Validate.isTrue(fk.name().startsWith("FK_"));
List<String> legacySPHibernateFKNames = Arrays.asList(
"FK7ULX3J1GG3V7MAQREJGC7YBC4", "FKC97MPK37OKWU8QVTCEG2NH9VN", "FKCLTIHNC5TGPRJ9BHPT7XI5OTB", "FKGXSREUTYMMFJUWDSWV3Y887DO");
if (legacySPHibernateFKNames.contains(fk.name())) {
// wipmb temporarily allow the hibernate legacy sp fk names
} else {
Validate.isTrue(fk.name().startsWith("FK_"),
"Foreign key " + fk.name() + " on " + theAnnotatedElement + " must start with FK");
}
if ( ! duplicateNameValidationExceptionList.contains(fk.name())) {
assertNotADuplicateName(fk.name(), theNames);
}

View File

@ -40,7 +40,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.util.VersionEnum;
import javax.persistence.Index;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@ -83,12 +82,82 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
init550(); // 20210520 -
init560(); // 20211027 -
init570(); // 20211102 -
init600(); // 20211102 -
}
private void init600() {
Builder version = forVersion(VersionEnum.V6_0_0);
/*
* New indexing for the core SPIDX tables.
* Ensure all queries can be satisfied by the index directly,
* either as left or right table in a hash or sort join.
*/
// new date search indexes
Builder.BuilderWithTableName dateTable = version.onTable("HFJ_SPIDX_DATE");
// replace and drop IDX_SP_DATE_HASH
dateTable
.addIndex("20220207.1", "IDX_SP_DATE_HASH_V2" )
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE_LOW", "SP_VALUE_HIGH", "RES_ID", "PARTITION_ID");
dateTable.dropIndexOnline("20220207.2", "IDX_SP_DATE_HASH");
// drop redundant
dateTable.dropIndexOnline("20220207.3", "IDX_SP_DATE_HASH_LOW");
// replace and drop IDX_SP_DATE_HASH_HIGH
dateTable
.addIndex("20220207.4", "IDX_SP_DATE_HASH_HIGH_V2" )
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE_HIGH", "RES_ID", "PARTITION_ID");
dateTable.dropIndexOnline("20220207.5", "IDX_SP_DATE_HASH_HIGH");
// replace and drop IDX_SP_DATE_ORD_HASH
dateTable
.addIndex("20220207.6", "IDX_SP_DATE_ORD_HASH_V2" )
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL", "SP_VALUE_HIGH_DATE_ORDINAL", "RES_ID", "PARTITION_ID");
dateTable.dropIndexOnline("20220207.7", "IDX_SP_DATE_ORD_HASH");
// replace and drop IDX_SP_DATE_ORD_HASH_HIGH
dateTable
.addIndex("20220207.8", "IDX_SP_DATE_ORD_HASH_HIGH_V2" )
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE_HIGH_DATE_ORDINAL", "RES_ID", "PARTITION_ID");
dateTable.dropIndexOnline("20220207.9", "IDX_SP_DATE_ORD_HASH_HIGH");
// drop redundant
dateTable.dropIndexOnline("20220207.10", "IDX_SP_DATE_ORD_HASH_LOW");
// replace and drop IDX_SP_DATE_RESID
dateTable
.addIndex("20220207.11", "IDX_SP_DATE_RESID_V2" )
.unique(false)
.online(true)
.withColumns("RES_ID", "HASH_IDENTITY", "SP_VALUE_LOW", "SP_VALUE_HIGH", "SP_VALUE_LOW_DATE_ORDINAL", "SP_VALUE_HIGH_DATE_ORDINAL", "PARTITION_ID");
// some engines tie the FK constraint to a particular index.
// So we need to drop and recreate the constraint to drop the old RES_ID index.
// Rename it while we're at it. FK17s70oa59rm9n61k9thjqrsqm was not a pretty name.
dateTable.dropForeignKey("20220207.12", "FK17S70OA59RM9N61K9THJQRSQM", "HFJ_RESOURCE");
dateTable.dropIndexOnline("20220207.13", "IDX_SP_DATE_RESID");
dateTable.dropIndexOnline("20220207.14", "FK17S70OA59RM9N61K9THJQRSQM");
dateTable.addForeignKey("20220207.15", "FK_SP_DATE_RES")
.toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
// drop obsolete
dateTable.dropIndexOnline("20220207.16", "IDX_SP_DATE_UPDATED");
}
/**
* See https://github.com/hapifhir/hapi-fhir/issues/3237 for reasoning for these indexes.
* This adds indexes to various tables to enhance delete-expunge performance, which does deletes by PID.
* This adds indexes to various tables to enhance delete-expunge performance, which deletes by PID.
*/
private void addIndexesForDeleteExpunge(Builder theVersion) {
@ -247,7 +316,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
//-- add index on HFJ_SPIDX_DATE
version.onTable("HFJ_SPIDX_DATE").addIndex("20210309.1", "IDX_SP_DATE_HASH_HIGH")
.unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_HIGH");
.unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_HIGH")
.doNothing();
//-- add index on HFJ_FORCED_ID
version.onTable("HFJ_FORCED_ID").addIndex("20210309.2", "IDX_FORCEID_FID")
@ -463,9 +533,12 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
Builder version = forVersion(VersionEnum.V5_0_1);
Builder.BuilderWithTableName spidxDate = version.onTable("HFJ_SPIDX_DATE");
spidxDate.addIndex("20200514.1", "IDX_SP_DATE_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW");
spidxDate.addIndex("20200514.2", "IDX_SP_DATE_ORD_HASH").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL", "SP_VALUE_HIGH_DATE_ORDINAL");
spidxDate.addIndex("20200514.3", "IDX_SP_DATE_ORD_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL");
spidxDate.addIndex("20200514.1", "IDX_SP_DATE_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW")
.doNothing();
spidxDate.addIndex("20200514.2", "IDX_SP_DATE_ORD_HASH").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL", "SP_VALUE_HIGH_DATE_ORDINAL")
.doNothing();
spidxDate.addIndex("20200514.3", "IDX_SP_DATE_ORD_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL")
.doNothing();
// MPI_LINK
version.addIdGenerator("20200517.1", "SEQ_EMPI_LINK_ID");
@ -1049,7 +1122,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxDate
.addIndex("20180903.8", "IDX_SP_DATE_HASH")
.unique(false)
.withColumns("HASH_IDENTITY", "SP_VALUE_LOW", "SP_VALUE_HIGH");
.withColumns("HASH_IDENTITY", "SP_VALUE_LOW", "SP_VALUE_HIGH")
.doNothing();
spidxDate
.dropIndex("20180903.9", "IDX_SP_DATE");
spidxDate

View File

@ -37,9 +37,6 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextFi
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@ -69,10 +66,6 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
@Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false)
private String myParamName;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
@Column(name = "RES_ID", insertable = false, updatable = false, nullable = false)
private Long myResourcePid;
@ -105,15 +98,12 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
}
}
public ResourceTable getResource() {
return myResource;
}
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
myResourceType = theResource.getResourceType();
return this;
}
// MB pushed these down to the individual SP classes so we could name the FK in the join annotation
/**
* Get the Resource this SP indexes
*/
public abstract ResourceTable getResource();
public abstract BaseResourceIndexedSearchParam setResource(ResourceTable theResource);
@Override
public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {

View File

@ -21,6 +21,9 @@ package ca.uhn.fhir.jpa.model.entity;
*/
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import org.apache.commons.lang3.builder.HashCodeBuilder;
@ -60,6 +63,10 @@ public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourc
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
/**
* Constructor
*/
@ -158,4 +165,16 @@ public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourc
public static long calculateHashUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUnits);
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -30,10 +30,14 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@ -66,6 +70,11 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FKC97MPK37OKWU8QVTCEG2NH9VN"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
public ResourceIndexedSearchParamCoords() {
}
@ -186,4 +195,15 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
return b.build();
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -27,10 +27,14 @@ import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
@ -57,13 +61,11 @@ import ca.uhn.fhir.util.DateUtils;
@Entity
@Table(name = "HFJ_SPIDX_DATE", indexes = {
// We previously had an index called IDX_SP_DATE - Dont reuse
@Index(name = "IDX_SP_DATE_HASH", columnList = "HASH_IDENTITY,SP_VALUE_LOW,SP_VALUE_HIGH"),
@Index(name = "IDX_SP_DATE_HASH_LOW", columnList = "HASH_IDENTITY,SP_VALUE_LOW"),
@Index(name = "IDX_SP_DATE_HASH_HIGH", columnList = "HASH_IDENTITY,SP_VALUE_HIGH"),
@Index(name = "IDX_SP_DATE_ORD_HASH", columnList = "HASH_IDENTITY,SP_VALUE_LOW_DATE_ORDINAL,SP_VALUE_HIGH_DATE_ORDINAL"),
@Index(name = "IDX_SP_DATE_ORD_HASH_LOW", columnList = "HASH_IDENTITY,SP_VALUE_LOW_DATE_ORDINAL"),
@Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID"),
@Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_DATE_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE_LOW,SP_VALUE_HIGH,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_DATE_HASH_HIGH_V2", columnList = "HASH_IDENTITY,SP_VALUE_HIGH,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_DATE_ORD_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE_LOW_DATE_ORDINAL,SP_VALUE_HIGH_DATE_ORDINAL,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_DATE_ORD_HASH_HIGH_V2", columnList = "HASH_IDENTITY,SP_VALUE_HIGH_DATE_ORDINAL,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_DATE_RESID_V2", columnList = "RES_ID,HASH_IDENTITY,SP_VALUE_LOW,SP_VALUE_HIGH,SP_VALUE_LOW_DATE_ORDINAL,SP_VALUE_HIGH_DATE_ORDINAL,PARTITION_ID"),
})
public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam {
@ -101,6 +103,12 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn( nullable = false,
name = "RES_ID", referencedColumnName = "RES_ID",
foreignKey = @ForeignKey(name="FK_SP_DATE_RES"))
private ResourceTable myResource;
/**
* Constructor
*/
@ -378,4 +386,15 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return (long) DateUtils.convertDateToDayInteger(theDate);
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -32,10 +32,14 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumb
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.math.BigDecimal;
@ -67,6 +71,11 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FKCLTIHNC5TGPRJ9BHPT7XI5OTB"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
public ResourceIndexedSearchParamNumber() {
}
@ -180,4 +189,15 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
return Objects.equals(getValue(), number.getValue());
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -34,6 +34,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
@ -80,8 +81,9 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
private Long myId;
@ManyToOne(optional = false)
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
private ResourceTable myResourceTable;
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false,
foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
private ResourceTable myResource;
@Column(name = "SP_VALUE_EXACT", length = MAX_LENGTH, nullable = true)
private String myValueExact;
@ -298,4 +300,15 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -35,10 +35,14 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextFi
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@ -107,6 +111,11 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@Column(name = "HASH_VALUE", nullable = true)
private Long myHashValue;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FK7ULX3J1GG3V7MAQREJGC7YBC4"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
/**
* Constructor
@ -344,4 +353,15 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -33,10 +33,14 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextFi
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@ -80,6 +84,11 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FKGXSREUTYMMFJUWDSWV3Y887DO"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
/**
* Constructor
*/
@ -229,4 +238,15 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -39,10 +40,12 @@ import java.util.Set;
public class AddIndexTask extends BaseTableTask {
private static final Logger ourLog = LoggerFactory.getLogger(AddIndexTask.class);
private String myIndexName;
private List<String> myColumns;
private Boolean myUnique;
private List<String> myIncludeColumns = Collections.emptyList();
private boolean myOnline;
public AddIndexTask(String theProductVersion, String theSchemaVersion) {
super(theProductVersion, theSchemaVersion);
@ -126,7 +129,27 @@ public class AddIndexTask extends BaseTableTask {
}
mssqlWhereClause += ")";
}
String sql = "create " + unique + "index " + myIndexName + " on " + getTableName() + "(" + columns + ")" + includeClause + mssqlWhereClause;
String postgresOnline = "";
String oracleOnlineDeferred = "";
if (myOnline) {
switch (getDriverType()) {
case POSTGRES_9_4:
postgresOnline = "CONCURRENTLY ";
break;
case ORACLE_12C:
oracleOnlineDeferred = " ONLINE DEFERRED";
break;
case MSSQL_2012:
oracleOnlineDeferred = " WITH (ONLINE = ON)";
break;
default:
}
}
String sql =
"create " + unique + "index " + postgresOnline + myIndexName +
" on " + getTableName() + "(" + columns + ")" + includeClause + mssqlWhereClause + oracleOnlineDeferred;
return sql;
}
@ -134,25 +157,6 @@ public class AddIndexTask extends BaseTableTask {
setColumns(Arrays.asList(theColumns));
}
@Override
protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) {
super.generateEquals(theBuilder, theOtherObject);
AddIndexTask otherObject = (AddIndexTask) theOtherObject;
theBuilder.append(myIndexName, otherObject.myIndexName);
theBuilder.append(myColumns, otherObject.myColumns);
theBuilder.append(myUnique, otherObject.myUnique);
}
@Override
protected void generateHashCode(HashCodeBuilder theBuilder) {
super.generateHashCode(theBuilder);
theBuilder.append(myIndexName);
theBuilder.append(myColumns);
theBuilder.append(myUnique);
}
public void setIncludeColumns(String... theIncludeColumns) {
setIncludeColumns(Arrays.asList(theIncludeColumns));
}
@ -161,4 +165,33 @@ public class AddIndexTask extends BaseTableTask {
Validate.notNull(theIncludeColumns);
myIncludeColumns = theIncludeColumns;
}
/**
* Add Index without locking the table.
*/
public void setOnline(boolean theFlag) {
myOnline = theFlag;
}
@Override
protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) {
super.generateEquals(theBuilder, theOtherObject);
AddIndexTask otherObject = (AddIndexTask) theOtherObject;
theBuilder.append(myIndexName, otherObject.myIndexName);
theBuilder.append(myColumns, otherObject.myColumns);
theBuilder.append(myUnique, otherObject.myUnique);
theBuilder.append(myIncludeColumns, otherObject.myIncludeColumns);
theBuilder.append(myOnline, otherObject.myOnline);
}
@Override
protected void generateHashCode(HashCodeBuilder theBuilder) {
super.generateHashCode(theBuilder);
theBuilder.append(myIndexName);
theBuilder.append(myColumns);
theBuilder.append(myUnique);
theBuilder.append(myOnline);
}
}

View File

@ -32,6 +32,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import javax.annotation.Nonnull;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
@ -44,63 +45,74 @@ public class DropIndexTask extends BaseTableTask {
private static final Logger ourLog = LoggerFactory.getLogger(DropIndexTask.class);
private String myIndexName;
private boolean myOnline;
public DropIndexTask(String theProductVersion, String theSchemaVersion) {
super(theProductVersion, theSchemaVersion);
}
static List<String> createDropIndexSql(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theIndexName, DriverTypeEnum theDriverType) throws SQLException {
Validate.notBlank(theIndexName, "theIndexName must not be blank");
Validate.notBlank(theTableName, "theTableName must not be blank");
List<String> generateSql() throws SQLException {
Validate.notBlank(myIndexName, "indexName must not be blank");
Validate.notBlank(getTableName(), "tableName must not be blank");
if (!JdbcUtils.getIndexNames(theConnectionProperties, theTableName).contains(theIndexName)) {
if (!JdbcUtils.getIndexNames(getConnectionProperties(), getTableName()).contains(myIndexName)) {
return Collections.emptyList();
}
boolean isUnique = JdbcUtils.isIndexUnique(getConnectionProperties(), getTableName(), myIndexName);
boolean isUnique = JdbcUtils.isIndexUnique(theConnectionProperties, theTableName, theIndexName);
return doGenerateSql(isUnique);
}
// testable without jdbc
@Nonnull
List<String> doGenerateSql(boolean isUnique) {
DriverTypeEnum driverType = getDriverType();
List<String> sql = new ArrayList<>();
if (isUnique) {
// Drop constraint
switch (theDriverType) {
switch (driverType) {
case MYSQL_5_7:
case MARIADB_10_1:
// Need to quote the index name as the word "PRIMARY" is reserved in MySQL
sql.add("alter table " + theTableName + " drop index `" + theIndexName + "`");
sql.add("alter table " + getTableName() + " drop index `" + myIndexName + "`");
break;
case H2_EMBEDDED:
sql.add("drop index " + theIndexName);
sql.add("drop index " + myIndexName);
break;
case DERBY_EMBEDDED:
sql.add("alter table " + theTableName + " drop constraint " + theIndexName);
sql.add("alter table " + getTableName() + " drop constraint " + myIndexName);
break;
case ORACLE_12C:
sql.add("drop index " + theIndexName);
sql.add("drop index " + myIndexName + (myOnline?" ONLINE":""));
break;
case MSSQL_2012:
sql.add("drop index " + theIndexName + " on " + theTableName);
sql.add("drop index " + myIndexName + " on " + getTableName() + (myOnline?" WITH (ONLINE = ON)":""));
break;
case POSTGRES_9_4:
sql.add("alter table " + theTableName + " drop constraint if exists " + theIndexName + " cascade");
sql.add("drop index if exists " + theIndexName + " cascade");
sql.add("alter table " + getTableName() + " drop constraint if exists " + myIndexName + " cascade");
sql.add("drop index " + (myOnline?"CONCURRENTLY ":"") + "if exists " + myIndexName + " cascade");
break;
}
} else {
// Drop index
switch (theDriverType) {
switch (driverType) {
case MYSQL_5_7:
case MARIADB_10_1:
sql.add("alter table " + theTableName + " drop index " + theIndexName);
sql.add("alter table " + getTableName() + " drop index " + myIndexName);
break;
case POSTGRES_9_4:
sql.add("drop index " + (myOnline?"CONCURRENTLY ":"") + myIndexName);
break;
case DERBY_EMBEDDED:
case H2_EMBEDDED:
sql.add("drop index " + myIndexName);
break;
case ORACLE_12C:
sql.add("drop index " + theIndexName);
sql.add("drop index " + myIndexName + (myOnline?" ONLINE":""));
break;
case MSSQL_2012:
sql.add("drop index " + theTableName + "." + theIndexName);
sql.add("drop index " + getTableName() + "." + myIndexName + (myOnline?" WITH (ONLINE = ON)":""));
break;
}
}
@ -163,7 +175,7 @@ public class DropIndexTask extends BaseTableTask {
boolean isUnique = JdbcUtils.isIndexUnique(getConnectionProperties(), getTableName(), myIndexName);
String uniquenessString = isUnique ? "unique" : "non-unique";
List<String> sqls = createDropIndexSql(getConnectionProperties(), getTableName(), myIndexName, getDriverType());
List<String> sqls = generateSql();
if (!sqls.isEmpty()) {
logInfo(ourLog, "Dropping {} index {} on table {}", uniquenessString, myIndexName, getTableName());
}
@ -196,11 +208,17 @@ public class DropIndexTask extends BaseTableTask {
DropIndexTask otherObject = (DropIndexTask) theOtherObject;
super.generateEquals(theBuilder, otherObject);
theBuilder.append(myIndexName, otherObject.myIndexName);
theBuilder.append(myOnline, otherObject.myOnline);
}
@Override
protected void generateHashCode(HashCodeBuilder theBuilder) {
super.generateHashCode(theBuilder);
theBuilder.append(myIndexName);
theBuilder.append(myOnline);
}
public void setOnline(boolean theFlag) {
this.myOnline = theFlag;
}
}

View File

@ -116,7 +116,6 @@ public class Builder {
*
* @param theVersion The version of the migration.
* @param theDriverToSql Map of driver types to SQL statements.
* @return
*/
public Builder executeRawSql(String theVersion, Map<DriverTypeEnum, String> theDriverToSql) {
Map<DriverTypeEnum, List<String>> singleSqlStatementMap = new HashMap<>();
@ -134,7 +133,6 @@ public class Builder {
*
* @param theVersion The version of the migration.
* @param theDriverToSqls Map of driver types to list of SQL statements.
* @return
*/
public Builder executeRawSqls(String theVersion, Map<DriverTypeEnum, List<String>> theDriverToSqls) {
ExecuteRawSqlTask executeRawSqlTask = new ExecuteRawSqlTask(myRelease, theVersion);
@ -191,6 +189,15 @@ public class Builder {
return new BuilderCompleteTask(task);
}
/**
* Drop index without taking write lock on PG, Oracle, MSSQL.
*/
public BuilderCompleteTask dropIndexOnline(String theVersion, String theIndexName) {
DropIndexTask task = dropIndexOptional(false, theVersion, theIndexName);
task.setOnline(true);
return new BuilderCompleteTask(task);
}
public void dropIndexStub(String theVersion, String theIndexName) {
dropIndexOptional(true, theVersion, theIndexName);
}
@ -322,6 +329,7 @@ public class Builder {
private final String myVersion;
private final boolean myUnique;
private String[] myIncludeColumns;
private boolean myOnline;
public BuilderAddIndexUnique(String theVersion, boolean theUnique) {
myVersion = theVersion;
@ -344,6 +352,7 @@ public class Builder {
task.setUnique(myUnique);
task.setColumns(theColumnNames);
task.setDoNothing(theDoNothing);
task.setOnline(myOnline);
if (myIncludeColumns != null) {
task.setIncludeColumns(myIncludeColumns);
}
@ -355,6 +364,14 @@ public class Builder {
myIncludeColumns = theIncludeColumns;
return this;
}
/**
* Add the index without locking the table.
*/
public BuilderAddIndexUnique online(boolean theOnlineFlag) {
myOnline = theOnlineFlag;
return this;
}
}
}
@ -454,7 +471,7 @@ public class Builder {
}
}
public class BuilderAddColumnWithName {
public static class BuilderAddColumnWithName {
private final String myRelease;
private final String myVersion;
private final String myColumnName;

View File

@ -2,8 +2,11 @@ package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.sql.SQLException;
@ -109,25 +112,82 @@ public class AddIndexTest extends BaseTest {
assertThat(JdbcUtils.getIndexNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("IDX_DIFINDEX", "IDX_ANINDEX"));
}
@Test
public void testIncludeColumns() {
@Nested
public class SqlFeatures {
private AddIndexTask myTask;
private String mySql;
AddIndexTask task = new AddIndexTask("1", "1");
task.setIndexName("IDX_ANINDEX");
task.setTableName("SOMETABLE");
task.setColumns("PID", "TEXTCOL");
task.setIncludeColumns("FOO1", "FOO2");
task.setUnique(false);
// MSSQL supports include clause
task.setDriverType(DriverTypeEnum.MSSQL_2012);
String sql = task.generateSql();
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL) INCLUDE (FOO1, FOO2)", sql);
@Nested
public class IncludeColumns {
@BeforeEach
public void beforeEach() {
myTask = new AddIndexTask("1", "1");
myTask.setIndexName("IDX_ANINDEX");
myTask.setTableName("SOMETABLE");
myTask.setColumns("PID", "TEXTCOL");
myTask.setIncludeColumns("FOO1", "FOO2");
myTask.setUnique(false);
}
// Oracle does not support include clause
task.setDriverType(DriverTypeEnum.ORACLE_12C);
sql = task.generateSql();
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL)", sql);
@Test
public void testIncludeColumns() {
// MSSQL supports include clause
myTask.setDriverType(DriverTypeEnum.MSSQL_2012);
mySql = myTask.generateSql();
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL) INCLUDE (FOO1, FOO2)", mySql);
// Oracle does not support include clause
myTask.setDriverType(DriverTypeEnum.ORACLE_12C);
mySql = myTask.generateSql();
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL)", mySql);
}
}
@Nested
public class OnlineNoLocks {
@BeforeEach
public void beforeEach() {
myTask = new AddIndexTask("1", "1");
myTask.setIndexName("IDX_ANINDEX");
myTask.setTableName("SOMETABLE");
myTask.setColumns("PID", "TEXTCOL");
myTask.setUnique(false);
}
@ParameterizedTest(name = "{index}: {0}")
@EnumSource()
public void noAffectOff(DriverTypeEnum theDriver) {
myTask.setDriverType(theDriver);
mySql = myTask.generateSql();
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL)", mySql);
}
@ParameterizedTest(name = "{index}: {0}")
@EnumSource()
public void platformSyntaxWhenOn(DriverTypeEnum theDriver) {
myTask.setDriverType(theDriver);
myTask.setOnline(true);
mySql = myTask.generateSql();
switch (theDriver) {
case POSTGRES_9_4:
assertEquals("create index CONCURRENTLY IDX_ANINDEX on SOMETABLE(PID, TEXTCOL)", mySql);
break;
case ORACLE_12C:
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL) ONLINE DEFERRED", mySql);
break;
case MSSQL_2012:
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL) WITH (ONLINE = ON)", mySql);
break;
default:
// unsupported is ok. But it means we lock the table for a bit.
assertEquals("create index IDX_ANINDEX on SOMETABLE(PID, TEXTCOL)", mySql);
break;
}
}
}
}
}

View File

@ -1,16 +1,26 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DropIndexTest extends BaseTest {
@ -113,4 +123,136 @@ public class DropIndexTest extends BaseTest {
assertThat(JdbcUtils.getIndexNames(getConnectionProperties(), "SOMETABLE"), empty());
}
@Nested
public class OnlineNoLocks {
private DropIndexTask myTask;
private List<String> mySql;
@BeforeEach
public void beforeEach() {
myTask = new DropIndexTask("1", "1");
myTask.setIndexName("IDX_ANINDEX");
myTask.setTableName("SOMETABLE");
}
@ParameterizedTest(name = "{index}: {0}")
@EnumSource()
public void noAffectOffUnique(DriverTypeEnum theDriver) throws SQLException {
myTask.setDriverType(theDriver);
mySql = myTask.doGenerateSql(true);
switch (theDriver) {
case MYSQL_5_7:
case MARIADB_10_1:
assertThat(mySql, equalTo(asList("alter table SOMETABLE drop index `IDX_ANINDEX`")));
break;
case H2_EMBEDDED:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case DERBY_EMBEDDED:
assertThat(mySql, equalTo(asList("alter table SOMETABLE drop constraint IDX_ANINDEX")));
break;
case ORACLE_12C:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case MSSQL_2012:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX on SOMETABLE")));
break;
case POSTGRES_9_4:
assertThat(mySql, equalTo(asList(
"alter table SOMETABLE drop constraint if exists IDX_ANINDEX cascade",
"drop index if exists IDX_ANINDEX cascade")));
break;
}
}
@ParameterizedTest(name = "{index}: {0}")
@EnumSource()
public void noAffectOffNotUnique(DriverTypeEnum theDriver) throws SQLException {
myTask.setDriverType(theDriver);
mySql = myTask.doGenerateSql(false);
switch (theDriver) {
case MYSQL_5_7:
case MARIADB_10_1:
assertThat(mySql, equalTo(asList("alter table SOMETABLE drop index IDX_ANINDEX")));
break;
case H2_EMBEDDED:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case DERBY_EMBEDDED:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case ORACLE_12C:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case MSSQL_2012:
assertThat(mySql, equalTo(asList("drop index SOMETABLE.IDX_ANINDEX")));
break;
case POSTGRES_9_4:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
}
}
@ParameterizedTest(name = "{index}: {0}")
@EnumSource()
public void onlineUnique(DriverTypeEnum theDriver) throws SQLException {
myTask.setDriverType(theDriver);
myTask.setOnline(true);
mySql = myTask.doGenerateSql(true);
switch (theDriver) {
case MYSQL_5_7:
case MARIADB_10_1:
assertThat(mySql, equalTo(asList("alter table SOMETABLE drop index `IDX_ANINDEX`")));
break;
case H2_EMBEDDED:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case DERBY_EMBEDDED:
assertThat(mySql, equalTo(asList("alter table SOMETABLE drop constraint IDX_ANINDEX")));
break;
case ORACLE_12C:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX ONLINE")));
break;
case MSSQL_2012:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX on SOMETABLE WITH (ONLINE = ON)")));
break;
case POSTGRES_9_4:
assertThat(mySql, equalTo(asList(
"alter table SOMETABLE drop constraint if exists IDX_ANINDEX cascade",
"drop index CONCURRENTLY if exists IDX_ANINDEX cascade")));
break;
}
}
@ParameterizedTest(name = "{index}: {0}")
@EnumSource()
public void onlineNotUnique(DriverTypeEnum theDriver) throws SQLException {
myTask.setDriverType(theDriver);
myTask.setOnline(true);
mySql = myTask.doGenerateSql(false);
switch (theDriver) {
case MYSQL_5_7:
case MARIADB_10_1:
assertThat(mySql, equalTo(asList("alter table SOMETABLE drop index IDX_ANINDEX")));
break;
case H2_EMBEDDED:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case DERBY_EMBEDDED:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX")));
break;
case ORACLE_12C:
assertThat(mySql, equalTo(asList("drop index IDX_ANINDEX ONLINE")));
break;
case MSSQL_2012:
assertThat(mySql, equalTo(asList("drop index SOMETABLE.IDX_ANINDEX WITH (ONLINE = ON)")));
break;
case POSTGRES_9_4:
assertThat(mySql, equalTo(asList("drop index CONCURRENTLY IDX_ANINDEX")));
break;
}
}
}
}