void copyMutableValuesFrom(T theSource) {
myHashIdentity = source.myHashIdentity;
}
- public void setHashIdentity(Long theHashIdentity) {
- myHashIdentity = theHashIdentity;
- }
-
@Override
public Long getId() {
return myId;
@@ -184,10 +177,10 @@ public ResourceIndexedSearchParamCoords setLongitude(double theLongitude) {
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
- b.append(getParamName());
- b.append(getResourceType());
+ b.append(getHashIdentity());
b.append(getLatitude());
b.append(getLongitude());
+ b.append(isMissing());
return b.toHashCode();
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index 401aa7dd66fd..45259d4a9f5f 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
@@ -29,6 +30,7 @@
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
@@ -55,6 +57,7 @@
import java.util.Date;
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_DATE",
@@ -109,14 +112,6 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
@Column(name = "SP_ID")
private Long myId;
- /**
- * Composite of resourceType, paramName, and partition info if configured.
- * Combined with the various date fields for a query.
- * @since 3.5.0 - At some point this should be made not-null
- */
- @Column(name = "HASH_IDENTITY", nullable = true)
- private Long myHashIdentity;
-
@ManyToOne(
optional = false,
fetch = FetchType.LAZY,
@@ -264,8 +259,7 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamDate obj = (ResourceIndexedSearchParamDate) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
+ b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getTimeFromDate(getValueHigh()), getTimeFromDate(obj.getValueHigh()));
b.append(getTimeFromDate(getValueLow()), getTimeFromDate(obj.getValueLow()));
b.append(getValueLowDateOrdinal(), obj.getValueLowDateOrdinal());
@@ -274,10 +268,6 @@ public boolean equals(Object theObj) {
return b.isEquals();
}
- public void setHashIdentity(Long theHashIdentity) {
- myHashIdentity = theHashIdentity;
- }
-
@Override
public Long getId() {
return myId;
@@ -316,10 +306,12 @@ public ResourceIndexedSearchParamDate setValueLow(Date theValueLow) {
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
- b.append(getResourceType());
- b.append(getParamName());
+ b.append(getHashIdentity());
b.append(getTimeFromDate(getValueHigh()));
b.append(getTimeFromDate(getValueLow()));
+ b.append(getValueHighDateOrdinal());
+ b.append(getValueLowDateOrdinal());
+ b.append(isMissing());
return b.toHashCode();
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
index a1527437dc51..902e3ac6c0c7 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
@@ -20,11 +20,13 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.NumberParam;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
@@ -47,6 +49,7 @@
import java.util.Objects;
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_NUMBER",
@@ -69,11 +72,6 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_NUMBER")
@Column(name = "SP_ID")
private Long myId;
- /**
- * @since 3.5.0 - At some point this should be made not-null
- */
- @Column(name = "HASH_IDENTITY", nullable = true)
- private Long myHashIdentity;
@ManyToOne(
optional = false,
@@ -120,10 +118,6 @@ public void calculateHashes() {
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
}
- public Long getHashIdentity() {
- return myHashIdentity;
- }
-
@Override
public boolean equals(Object theObj) {
if (this == theObj) {
@@ -137,8 +131,6 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamNumber obj = (ResourceIndexedSearchParamNumber) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(normalizeForEqualityComparison(getValue()), normalizeForEqualityComparison(obj.getValue()));
b.append(isMissing(), obj.isMissing());
@@ -152,10 +144,6 @@ private Double normalizeForEqualityComparison(BigDecimal theValue) {
return theValue.doubleValue();
}
- public void setHashIdentity(Long theHashIdentity) {
- myHashIdentity = theHashIdentity;
- }
-
@Override
public Long getId() {
return myId;
@@ -177,8 +165,6 @@ public void setValue(BigDecimal theValue) {
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
- b.append(getResourceType());
- b.append(getParamName());
b.append(getHashIdentity());
b.append(normalizeForEqualityComparison(getValue()));
b.append(isMissing());
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
index 6b38f3b52e1f..0f1b2bd55680 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
@@ -20,11 +20,13 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
@@ -36,6 +38,7 @@
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
@@ -48,6 +51,7 @@
// @formatter:off
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_QUANTITY",
@@ -173,8 +177,6 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamQuantity obj = (ResourceIndexedSearchParamQuantity) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
@@ -183,6 +185,17 @@ public boolean equals(Object theObj) {
return b.isEquals();
}
+ @Override
+ public int hashCode() {
+ HashCodeBuilder b = new HashCodeBuilder();
+ b.append(getHashIdentity());
+ b.append(getHashIdentityAndUnits());
+ b.append(getHashIdentitySystemAndUnits());
+ b.append(isMissing());
+ b.append(getValue());
+ return b.toHashCode();
+ }
+
@Override
public boolean matches(IQueryParameterType theParam) {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java
index 4bf738b747af..b235a86bc096 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java
@@ -20,12 +20,14 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
@@ -37,6 +39,7 @@
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.fhir.ucum.Pair;
@@ -50,6 +53,7 @@
// @formatter:off
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_QUANTITY_NRML",
@@ -189,8 +193,6 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamQuantityNormalized obj = (ResourceIndexedSearchParamQuantityNormalized) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
@@ -199,6 +201,17 @@ public boolean equals(Object theObj) {
return b.isEquals();
}
+ @Override
+ public int hashCode() {
+ HashCodeBuilder b = new HashCodeBuilder();
+ b.append(getHashIdentity());
+ b.append(getHashIdentityAndUnits());
+ b.append(getHashIdentitySystemAndUnits());
+ b.append(isMissing());
+ b.append(getValue());
+ return b.toHashCode();
+ }
+
@Override
public boolean matches(IQueryParameterType theParam) {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
index c1e5b1ac19c5..5795c5896020 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
@@ -22,12 +22,14 @@
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.util.StringUtil;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
@@ -42,10 +44,12 @@
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
+import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam;
import static org.apache.commons.lang3.StringUtils.defaultString;
// @formatter:off
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_STRING",
@@ -97,11 +101,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
*/
@Column(name = "HASH_NORM_PREFIX", nullable = true)
private Long myHashNormalizedPrefix;
- /**
- * @since 3.6.0 - At some point this should be made not-null
- */
- @Column(name = "HASH_IDENTITY", nullable = true)
- private Long myHashIdentity;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@@ -180,24 +179,15 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamString obj = (ResourceIndexedSearchParamString) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
b.append(getValueExact(), obj.getValueExact());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getHashExact(), obj.getHashExact());
b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
b.append(getValueNormalized(), obj.getValueNormalized());
+ b.append(isMissing(), obj.isMissing());
return b.isEquals();
}
- private Long getHashIdentity() {
- return myHashIdentity;
- }
-
- public void setHashIdentity(Long theHashIdentity) {
- myHashIdentity = theHashIdentity;
- }
-
public Long getHashExact() {
return myHashExact;
}
@@ -251,13 +241,12 @@ public ResourceIndexedSearchParamString setValueNormalized(String theValueNormal
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
- b.append(getResourceType());
- b.append(getParamName());
b.append(getValueExact());
b.append(getHashIdentity());
b.append(getHashExact());
b.append(getHashNormalizedPrefix());
b.append(getValueNormalized());
+ b.append(isMissing());
return b.toHashCode();
}
@@ -306,7 +295,8 @@ public static long calculateHashExact(
String theResourceType,
String theParamName,
String theValueExact) {
- return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact);
+ return hashSearchParam(
+ thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact);
}
public static long calculateHashNormalized(
@@ -345,7 +335,7 @@ public static long calculateHashNormalized(
}
String value = StringUtil.left(theValueNormalized, hashPrefixLength);
- return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
+ return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
}
@Override
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
index 9066f9f25db4..cfe3d8862499 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
@@ -21,12 +21,14 @@
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.TokenParam;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
@@ -46,10 +48,12 @@
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
+import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.trim;
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_TOKEN",
@@ -89,11 +93,6 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@Column(name = "SP_ID")
private Long myId;
- /**
- * @since 3.4.0 - At some point this should be made not-null
- */
- @Column(name = "HASH_IDENTITY", nullable = true)
- private Long myHashIdentity;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@@ -217,9 +216,11 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamToken obj = (ResourceIndexedSearchParamToken) theObj;
EqualsBuilder b = new EqualsBuilder();
+ b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getHashSystem(), obj.getHashSystem());
b.append(getHashValue(), obj.getHashValue());
b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
+ b.append(isMissing(), obj.isMissing());
return b.isEquals();
}
@@ -231,10 +232,6 @@ private void setHashSystem(Long theHashSystem) {
myHashSystem = theHashSystem;
}
- private void setHashIdentity(Long theHashIdentity) {
- myHashIdentity = theHashIdentity;
- }
-
public Long getHashSystemAndValue() {
return myHashSystemAndValue;
}
@@ -283,11 +280,11 @@ public ResourceIndexedSearchParamToken setValue(String theValue) {
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
- b.append(getResourceType());
+ b.append(getHashIdentity());
b.append(getHashValue());
b.append(getHashSystem());
b.append(getHashSystemAndValue());
-
+ b.append(isMissing());
return b.toHashCode();
}
@@ -362,7 +359,8 @@ public static long calculateHashSystem(
String theResourceType,
String theParamName,
String theSystem) {
- return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, trim(theSystem));
+ return hashSearchParam(
+ thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, trim(theSystem));
}
public static long calculateHashSystemAndValue(
@@ -384,7 +382,7 @@ public static long calculateHashSystemAndValue(
String theParamName,
String theSystem,
String theValue) {
- return hash(
+ return hashSearchParam(
thePartitionSettings,
theRequestPartitionId,
theResourceType,
@@ -410,7 +408,7 @@ public static long calculateHashValue(
String theParamName,
String theValue) {
String value = trim(theValue);
- return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
+ return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
}
@Override
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
index d16396269b4d..02a5f23a16fd 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
@@ -21,11 +21,13 @@
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.UriParam;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
@@ -42,9 +44,11 @@
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
+import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam;
import static org.apache.commons.lang3.StringUtils.defaultString;
@Embeddable
+@EntityListeners(IndexStorageOptimizationListener.class)
@Entity
@Table(
name = "HFJ_SPIDX_URI",
@@ -84,11 +88,6 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
*/
@Column(name = "HASH_URI", nullable = true)
private Long myHashUri;
- /**
- * @since 3.5.0 - At some point this should be made not-null
- */
- @Column(name = "HASH_IDENTITY", nullable = true)
- private Long myHashIdentity;
@ManyToOne(
optional = false,
@@ -161,22 +160,13 @@ public boolean equals(Object theObj) {
}
ResourceIndexedSearchParamUri obj = (ResourceIndexedSearchParamUri) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
b.append(getUri(), obj.getUri());
b.append(getHashUri(), obj.getHashUri());
b.append(getHashIdentity(), obj.getHashIdentity());
+ b.append(isMissing(), obj.isMissing());
return b.isEquals();
}
- private Long getHashIdentity() {
- return myHashIdentity;
- }
-
- private void setHashIdentity(long theHashIdentity) {
- myHashIdentity = theHashIdentity;
- }
-
public Long getHashUri() {
return myHashUri;
}
@@ -207,11 +197,10 @@ public ResourceIndexedSearchParamUri setUri(String theUri) {
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
- b.append(getResourceType());
- b.append(getParamName());
b.append(getUri());
b.append(getHashUri());
b.append(getHashIdentity());
+ b.append(isMissing());
return b.toHashCode();
}
@@ -257,7 +246,7 @@ public static long calculateHashUri(
String theResourceType,
String theParamName,
String theUri) {
- return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUri);
+ return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUri);
}
@Override
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java
index 9270f6e163cf..3f931b56952a 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java
@@ -42,6 +42,8 @@
import java.io.Serializable;
+import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam;
+
@Entity
@Table(
name = "HFJ_RES_PARAM_PRESENT",
@@ -212,7 +214,6 @@ public static long calculateHashPresence(
String theParamName,
Boolean thePresent) {
String string = thePresent != null ? Boolean.toString(thePresent) : Boolean.toString(false);
- return BaseResourceIndexedSearchParam.hash(
- thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, string);
+ return hashSearchParam(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, string);
}
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java
index 630b295c946e..a6c80cf639b9 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java
@@ -21,6 +21,7 @@
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.util.ISequenceValueMassager;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc;
@@ -134,6 +135,14 @@ public class StorageSettings {
*/
private boolean myValidateResourceStatusForPackageUpload = true;
+ /**
+ * If set to true
, the server will not write data to the SP_NAME, RES_TYPE, SP_UPDATED
+ * columns for all HFJ_SPIDX tables.
+ *
+ * @since 7.4.0
+ */
+ private boolean myIndexStorageOptimized = false;
+
/**
* Constructor
*/
@@ -277,6 +286,58 @@ public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) {
myIndexMissingFieldsEnabled = theIndexMissingFields;
}
+ /**
+ * If set to true
(default is false), the server will not write data
+ * to the SP_NAME, RES_TYPE, SP_UPDATED
columns for all HFJ_SPIDX tables.
+ *
+ * This feature may be enabled on servers where HFJ_SPIDX tables are expected
+ * to have a large amount of data (millions of rows) in order to reduce overall storage size.
+ *
+ *
+ * Note that this setting only applies to newly inserted and updated rows in HFJ_SPIDX tables.
+ * In order to apply this optimization setting to existing HFJ_SPIDX index rows,
+ * $reindex
operation should be executed at the instance or server level.
+ *
+ *
+ * If this setting is enabled, {@link PartitionSettings#isIncludePartitionInSearchHashes()} should be disabled.
+ *
+ *
+ * If {@link StorageSettings#getIndexMissingFields()} is enabled, the following index may need to be added
+ * into the HFJ_SPIDX tables to improve the search performance: HASH_IDENTITY, SP_MISSING, RES_ID, PARTITION_ID
+ *
+ *
+ * @since 7.4.0
+ */
+ public boolean isIndexStorageOptimized() {
+ return myIndexStorageOptimized;
+ }
+
+ /**
+ * If set to true
(default is false), the server will not write data
+ * to the SP_NAME, RES_TYPE, SP_UPDATED
columns for all HFJ_SPIDX tables.
+ *
+ * This feature may be enabled on servers where HFJ_SPIDX tables are expected
+ * to have a large amount of data (millions of rows) in order to reduce overall storage size.
+ *
+ *
+ * Note that this setting only applies to newly inserted and updated rows in HFJ_SPIDX tables.
+ * In order to apply this optimization setting to existing HFJ_SPIDX index rows,
+ * $reindex
operation should be executed at the instance or server level.
+ *
+ *
+ * If this setting is enabled, {@link PartitionSettings#isIncludePartitionInSearchHashes()} should be set to false
.
+ *
+ *
+ * If {@link StorageSettings#getIndexMissingFields()} ()} is enabled, the following index may need to be added
+ * into the HFJ_SPIDX tables to improve the search performance: HASH_IDENTITY, SP_MISSING, RES_ID, PARTITION_ID
+ *
+ *
+ * @since 7.4.0
+ */
+ public void setIndexStorageOptimized(boolean theIndexStorageOptimized) {
+ myIndexStorageOptimized = theIndexStorageOptimized;
+ }
+
/**
* If this is enabled (disabled by default), Mass Ingestion Mode is enabled. In this mode, a number of
* runtime checks are disabled. This mode is designed for rapid backloading of data while the system is not
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/listener/IndexStorageOptimizationListener.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/listener/IndexStorageOptimizationListener.java
new file mode 100644
index 000000000000..1cb857418d1f
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/listener/IndexStorageOptimizationListener.java
@@ -0,0 +1,99 @@
+/*
+ * #%L
+ * HAPI FHIR JPA Model
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.jpa.model.listener;
+
+import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.StorageSettings;
+import ca.uhn.fhir.jpa.model.search.ISearchParamHashIdentityRegistry;
+import ca.uhn.fhir.rest.server.util.IndexedSearchParam;
+import jakarta.persistence.PostLoad;
+import jakarta.persistence.PostPersist;
+import jakarta.persistence.PostUpdate;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+import java.util.Optional;
+
+/**
+ * Sets SP_NAME, RES_TYPE, SP_UPDATED
column values to null for all HFJ_SPIDX tables
+ * if storage setting {@link ca.uhn.fhir.jpa.model.entity.StorageSettings#isIndexStorageOptimized()} is enabled.
+ *
+ * Using EntityListener to change HFJ_SPIDX column values right before insert/update to database.
+ *
+ *
+ * As SP_NAME, RES_TYPE
values could still be used after merge/persist to database, we are restoring
+ * them from HASH_IDENTITY
value.
+ *
+ * See {@link ca.uhn.fhir.jpa.model.entity.StorageSettings#setIndexStorageOptimized(boolean)}
+ */
+public class IndexStorageOptimizationListener {
+
+ public IndexStorageOptimizationListener(
+ @Autowired StorageSettings theStorageSettings, @Autowired ApplicationContext theApplicationContext) {
+ this.myStorageSettings = theStorageSettings;
+ this.myApplicationContext = theApplicationContext;
+ }
+
+ private final StorageSettings myStorageSettings;
+ private final ApplicationContext myApplicationContext;
+
+ @PrePersist
+ @PreUpdate
+ public void optimizeSearchParams(Object theEntity) {
+ if (myStorageSettings.isIndexStorageOptimized() && theEntity instanceof BaseResourceIndexedSearchParam) {
+ ((BaseResourceIndexedSearchParam) theEntity).optimizeIndexStorage();
+ }
+ }
+
+ @PostLoad
+ @PostPersist
+ @PostUpdate
+ public void restoreSearchParams(Object theEntity) {
+ if (myStorageSettings.isIndexStorageOptimized() && theEntity instanceof BaseResourceIndexedSearchParam) {
+ restoreSearchParams((BaseResourceIndexedSearchParam) theEntity);
+ }
+ }
+
+ /**
+ * As SP_NAME, RES_TYPE
values could still be used after merge/persist to database (mostly by tests),
+ * we are restoring them from HASH_IDENTITY
value.
+ * Note that SP_NAME, RES_TYPE
values are not recovered if
+ * {@link ca.uhn.fhir.jpa.model.entity.StorageSettings#isIndexOnContainedResources()} or
+ * {@link ca.uhn.fhir.jpa.model.entity.StorageSettings#isIndexOnContainedResourcesRecursively()}
+ * settings are enabled.
+ */
+ private void restoreSearchParams(BaseResourceIndexedSearchParam theResourceIndexedSearchParam) {
+ // getting ISearchParamHashIdentityRegistry from the App Context as it is initialized after EntityListeners
+ ISearchParamHashIdentityRegistry searchParamRegistry =
+ myApplicationContext.getBean(ISearchParamHashIdentityRegistry.class);
+ Optional indexedSearchParamOptional =
+ searchParamRegistry.getIndexedSearchParamByHashIdentity(
+ theResourceIndexedSearchParam.getHashIdentity());
+
+ if (indexedSearchParamOptional.isPresent()) {
+ theResourceIndexedSearchParam.setResourceType(
+ indexedSearchParamOptional.get().getResourceType());
+ theResourceIndexedSearchParam.restoreParamName(
+ indexedSearchParamOptional.get().getParameterName());
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java
new file mode 100644
index 000000000000..343ca0d0c081
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java
@@ -0,0 +1,9 @@
+package ca.uhn.fhir.jpa.model.search;
+
+import ca.uhn.fhir.rest.server.util.IndexedSearchParam;
+
+import java.util.Optional;
+
+public interface ISearchParamHashIdentityRegistry {
+ Optional getIndexedSearchParamByHashIdentity(Long theHashIdentity);
+}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java
new file mode 100644
index 000000000000..5ca532e11406
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java
@@ -0,0 +1,85 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Model
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.jpa.model.util;
+
+import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.base.Charsets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+
+/**
+ * Utility class for calculating hashes of SearchParam entity fields.
+ */
+public class SearchParamHash {
+
+ /**
+ * Don't change this without careful consideration. You will break existing hashes!
+ */
+ private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
+
+ /**
+ * Don't make this public 'cause nobody better be able to modify it!
+ */
+ private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
+
+ private SearchParamHash() {}
+
+ /**
+ * Applies a fast and consistent hashing algorithm to a set of strings
+ */
+ public static long hashSearchParam(
+ PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String... theValues) {
+ Hasher hasher = HASH_FUNCTION.newHasher();
+
+ if (thePartitionSettings.isPartitioningEnabled()
+ && thePartitionSettings.isIncludePartitionInSearchHashes()
+ && theRequestPartitionId != null) {
+ if (theRequestPartitionId.getPartitionIds().size() > 1) {
+ throw new InternalErrorException(Msg.code(1527)
+ + "Can not search multiple partitions when partitions are included in search hashes");
+ }
+ Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull();
+ if (partitionId != null) {
+ hasher.putInt(partitionId);
+ }
+ }
+
+ for (String next : theValues) {
+ if (next == null) {
+ hasher.putByte((byte) 0);
+ } else {
+ next = UrlUtil.escapeUrlParam(next);
+ byte[] bytes = next.getBytes(Charsets.UTF_8);
+ hasher.putBytes(bytes);
+ }
+ hasher.putBytes(DELIMITER_BYTES);
+ }
+
+ HashCode hashCode = hasher.hash();
+ long retVal = hashCode.asLong();
+ return retVal;
+ }
+}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java
index 669f828d9bba..cf1beb1a9a79 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java
@@ -2,15 +2,16 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamCoordsTest {
@Test
- public void testEquals() {
+ public void testEqualsAndHashCode_withSameParams_equalsIsTrueAndHashCodeIsSame() {
ResourceIndexedSearchParamCoords val1 = new ResourceIndexedSearchParamCoords()
.setLatitude(100)
.setLongitude(10);
@@ -21,8 +22,55 @@ public void testEquals() {
.setLongitude(10);
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val2, val1);
+ }
+
+ private void validateEquals(ResourceIndexedSearchParamCoords theParam1, ResourceIndexedSearchParamCoords theParam2) {
+ assertEquals(theParam2, theParam1);
+ assertEquals(theParam1, theParam2);
+ assertEquals(theParam1.hashCode(), theParam2.hashCode());
+ }
+
+ @Test
+ public void testEqualsAndHashCode_withOptimizedSearchParam_equalsIsTrueAndHashCodeIsSame() {
+ ResourceIndexedSearchParamCoords param = new ResourceIndexedSearchParamCoords(
+ new PartitionSettings(), "Patient", "param", 100, 10);
+ ResourceIndexedSearchParamCoords param2 = new ResourceIndexedSearchParamCoords(
+ new PartitionSettings(), "Patient", "param", 100, 10);
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, 100, 100, false, Observation, param, 100, 100, false, ResourceType is different",
+ "Patient, param, 100, 100, false, Patient, name, 100, 100, false, ParamName is different",
+ "Patient, param, 10, 100, false, Patient, param, 100, 100, false, Latitude is different",
+ "Patient, param, 100, 10, false, Patient, param, 100, 100, false, Longitude is different",
+ "Patient, param, 100, 100, true, Patient, param, 100, 100, false, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ double theFirstLatitude,
+ double theFirstLongitude,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ double theSecondLatitude,
+ double theSecondLongitude,
+ boolean theSecondMissing,
+ String theMessage) {
+ ResourceIndexedSearchParamCoords param = new ResourceIndexedSearchParamCoords(
+ new PartitionSettings(), theFirstResourceType, theFirstParamName, theFirstLatitude, theFirstLongitude);
+ param.setMissing(theFirstMissing);
+ ResourceIndexedSearchParamCoords param2 = new ResourceIndexedSearchParamCoords(
+ new PartitionSettings(), theSecondResourceType, theSecondParamName, theSecondLatitude, theSecondLongitude);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
}
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java
index c9137af9714d..dfefe9a77ca0 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java
@@ -3,16 +3,17 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.sql.Timestamp;
+import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamDateTest {
@@ -43,9 +44,7 @@ public void equalsIsTrueForMatchingNullDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", null, null, null, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", null, null, null, null, "SomeValue");
- assertTrue(param.equals(param2));
- assertTrue(param2.equals(param));
- assertEquals(param.hashCode(), param2.hashCode());
+ validateEquals(param, param2);
}
@Test
@@ -53,9 +52,7 @@ public void equalsIsTrueForMatchingDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1B, null, date2B, null, "SomeValue");
- assertTrue(param.equals(param2));
- assertTrue(param2.equals(param));
- assertEquals(param.hashCode(), param2.hashCode());
+ validateEquals(param, param2);
}
@Test
@@ -63,9 +60,7 @@ public void equalsIsTrueForMatchingTimeStampsThatMatch() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", timestamp1B, null, timestamp2B, null, "SomeValue");
- assertTrue(param.equals(param2));
- assertTrue(param2.equals(param));
- assertEquals(param.hashCode(), param2.hashCode());
+ validateEquals(param, param2);
}
// Scenario that occurs when updating a resource with a date search parameter. One date will be a java.util.Date, the
@@ -75,9 +70,23 @@ public void equalsIsTrueForMixedTimestampsAndDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue");
- assertTrue(param.equals(param2));
- assertTrue(param2.equals(param));
- assertEquals(param.hashCode(), param2.hashCode());
+ validateEquals(param, param2);
+ }
+
+ @Test
+ public void equalsIsTrueForOptimizedSearchParam() {
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+
+ private void validateEquals(ResourceIndexedSearchParamDate theParam, ResourceIndexedSearchParamDate theParam2) {
+ assertEquals(theParam, theParam2);
+ assertEquals(theParam2, theParam);
+ assertEquals(theParam.hashCode(), theParam2.hashCode());
}
@Test
@@ -85,9 +94,7 @@ public void equalsIsFalseForNonMatchingDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date2A, null, date1A, null, "SomeValue");
- assertFalse(param.equals(param2));
- assertFalse(param2.equals(param));
- assertThat(param2.hashCode()).isNotEqualTo(param.hashCode());
+ validateNotEquals(param, param2);
}
@Test
@@ -95,9 +102,7 @@ public void equalsIsFalseForNonMatchingDatesNullCase() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", null, null, null, null, "SomeValue");
- assertFalse(param.equals(param2));
- assertFalse(param2.equals(param));
- assertThat(param2.hashCode()).isNotEqualTo(param.hashCode());
+ validateNotEquals(param, param2);
}
@Test
@@ -105,9 +110,7 @@ public void equalsIsFalseForNonMatchingTimeStamps() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue");
- assertFalse(param.equals(param2));
- assertFalse(param2.equals(param));
- assertThat(param2.hashCode()).isNotEqualTo(param.hashCode());
+ validateNotEquals(param, param2);
}
@Test
@@ -115,14 +118,18 @@ public void equalsIsFalseForMixedTimestampsAndDatesThatDoNotMatch() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue");
- assertFalse(param.equals(param2));
- assertFalse(param2.equals(param));
- assertThat(param2.hashCode()).isNotEqualTo(param.hashCode());
+ validateNotEquals(param, param2);
+ }
+
+ private void validateNotEquals(ResourceIndexedSearchParamDate theParam, ResourceIndexedSearchParamDate theParam2) {
+ assertNotEquals(theParam, theParam2);
+ assertNotEquals(theParam2, theParam);
+ assertThat(theParam2.hashCode()).isNotEqualTo(theParam.hashCode());
}
@Test
- public void testEquals() {
+ public void testEqualsAndHashCode_withSameParams_equalsIsTrueAndHashCodeIsSame() {
ResourceIndexedSearchParamDate val1 = new ResourceIndexedSearchParamDate()
.setValueHigh(new Date(100000000L))
.setValueLow(new Date(111111111L));
@@ -133,8 +140,47 @@ public void testEquals() {
.setValueLow(new Date(111111111L));
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, " +
+ "Observation, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, ResourceType is different",
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, " +
+ "Patient, name, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, ParamName is different",
+ "Patient, param, 2017-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, " +
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, LowDate is different",
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, " +
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2020-04-25T14:05:15.953Z, false, HighDate is different",
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, true, " +
+ "Patient, param, 2018-04-25T14:05:15.953Z, 2019-04-25T14:05:15.953Z, false, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ String theFirstLowDate,
+ String theFirstHighDate,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ String theSecondLowDate,
+ String theSecondHighDate,
+ boolean theSecondMissing,
+ String theMessage) {
+ Date firstLowDate = Date.from(Instant.parse(theFirstLowDate));
+ Date firstHighDate = Date.from(Instant.parse(theFirstHighDate));
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),
+ theFirstResourceType, theFirstParamName, firstLowDate, theFirstLowDate, firstHighDate, theFirstHighDate, null);
+ param.setMissing(theFirstMissing);
+
+ Date secondLowDate = Date.from(Instant.parse(theSecondLowDate));
+ Date secondHighDate = Date.from(Instant.parse(theSecondHighDate));
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),
+ theSecondResourceType, theSecondParamName, secondLowDate, theSecondLowDate, secondHighDate, theSecondHighDate, null);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
}
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumberTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumberTest.java
index 7a73c54820c2..feb95bec1737 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumberTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumberTest.java
@@ -3,23 +3,29 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamNumberTest {
private static final String GRITTSCORE = "grittscore";
- public static final ResourceIndexedSearchParamNumber PARAM_VALUE_10_FIRST = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "Patient", GRITTSCORE, BigDecimal.valueOf(10));
- public static final ResourceIndexedSearchParamNumber PARAM_VALUE_10_SECOND = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "Patient", GRITTSCORE, BigDecimal.valueOf(10));
- public static final ResourceIndexedSearchParamNumber PARAM_VALUE_12_FIRST = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "Patient", GRITTSCORE, BigDecimal.valueOf(12));
+ public static ResourceIndexedSearchParamNumber PARAM_VALUE_10_FIRST;
+ public static ResourceIndexedSearchParamNumber PARAM_VALUE_10_SECOND;
+ public static ResourceIndexedSearchParamNumber PARAM_VALUE_12_FIRST;
@BeforeEach
void setUp() {
final ResourceTable resourceTable = new ResourceTable();
resourceTable.setId(1L);
+ PARAM_VALUE_10_FIRST = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "Patient", GRITTSCORE, BigDecimal.valueOf(10));
+ PARAM_VALUE_10_SECOND = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "Patient", GRITTSCORE, BigDecimal.valueOf(10));
+ PARAM_VALUE_12_FIRST = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "Patient", GRITTSCORE, BigDecimal.valueOf(12));
PARAM_VALUE_10_FIRST.setResource(resourceTable);
PARAM_VALUE_10_SECOND.setResource(resourceTable);
PARAM_VALUE_12_FIRST.setResource(resourceTable);
@@ -32,6 +38,34 @@ void notEqual() {
assertThat(PARAM_VALUE_12_FIRST.hashCode()).isNotEqualTo(PARAM_VALUE_10_FIRST.hashCode());
}
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, 10, false, Observation, param, 10, false, ResourceType is different",
+ "Patient, param, 10, false, Patient, name, 10, false, ParamName is different",
+ "Patient, param, 10, false, Patient, param, 9, false, Value is different",
+ "Patient, param, 10, false, Patient, param, 10, true, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ int theFirstValue,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ int theSecondValue,
+ boolean theSecondMissing,
+ String theMessage) {
+ ResourceIndexedSearchParamNumber param = new ResourceIndexedSearchParamNumber(
+ new PartitionSettings(), theFirstResourceType, theFirstParamName, BigDecimal.valueOf(theFirstValue));
+ param.setMissing(theFirstMissing);
+ ResourceIndexedSearchParamNumber param2 = new ResourceIndexedSearchParamNumber(
+ new PartitionSettings(), theSecondResourceType, theSecondParamName, BigDecimal.valueOf(theSecondValue));
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
+ }
+
@Test
void equalByReference() {
assertEquals(PARAM_VALUE_10_FIRST, PARAM_VALUE_10_FIRST);
@@ -44,4 +78,13 @@ void equalByContract() {
assertEquals(PARAM_VALUE_10_SECOND, PARAM_VALUE_10_FIRST);
assertEquals(PARAM_VALUE_10_FIRST.hashCode(), PARAM_VALUE_10_SECOND.hashCode());
}
+
+ @Test
+ void equalsIsTrueForOptimizedSearchParam() {
+ PARAM_VALUE_10_SECOND.optimizeIndexStorage();
+
+ assertEquals(PARAM_VALUE_10_FIRST, PARAM_VALUE_10_SECOND);
+ assertEquals(PARAM_VALUE_10_SECOND, PARAM_VALUE_10_FIRST);
+ assertEquals(PARAM_VALUE_10_FIRST.hashCode(), PARAM_VALUE_10_SECOND.hashCode());
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java
index 39d2f459738c..b42b96b660aa 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java
@@ -2,10 +2,11 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamQuantityNormalizedTest {
@@ -20,10 +21,59 @@ public void testEquals() {
.setValue(Double.parseDouble("123"));
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
}
+ @Test
+ public void equalsIsTrueForOptimizedSearchParam() {
+ BaseResourceIndexedSearchParamQuantity param = new ResourceIndexedSearchParamQuantityNormalized(
+ new PartitionSettings(), "Patient", "param", 123.0, "http://unitsofmeasure.org", "kg");
+ BaseResourceIndexedSearchParamQuantity param2 = new ResourceIndexedSearchParamQuantityNormalized(
+ new PartitionSettings(), "Patient", "param", 123.0, "http://unitsofmeasure.org", "kg");
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+ private void validateEquals(BaseResourceIndexedSearchParamQuantity theParam1,
+ BaseResourceIndexedSearchParamQuantity theParam2) {
+ assertEquals(theParam2, theParam1);
+ assertEquals(theParam1, theParam2);
+ assertEquals(theParam1.hashCode(), theParam2.hashCode());
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, 123.0, units, kg, false, Observation, param, 123.0, units, kg, false, ResourceType is different",
+ "Patient, param, 123.0, units, kg, false, Patient, name, 123.0, units, kg, false, ParamName is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 321.0, units, kg, false, Value is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 123.0, unitsDiff, kg, false, System is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 123.0, units, lb, false, Units is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 123.0, units, kg, true, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ double theFirstValue,
+ String theFirstSystem,
+ String theFirstUnits,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ double theSecondValue,
+ String theSecondSystem,
+ String theSecondUnits,
+ boolean theSecondMissing,
+ String theMessage) {
+ BaseResourceIndexedSearchParamQuantity param = new ResourceIndexedSearchParamQuantityNormalized(
+ new PartitionSettings(), theFirstResourceType, theFirstParamName, theFirstValue, theFirstSystem, theFirstUnits);
+ param.setMissing(theFirstMissing);
+ BaseResourceIndexedSearchParamQuantity param2 = new ResourceIndexedSearchParamQuantityNormalized(
+ new PartitionSettings(), theSecondResourceType, theSecondParamName, theSecondValue, theSecondSystem, theSecondUnits);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java
index d03895b237d0..5d1577ef501a 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java
@@ -2,12 +2,13 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.math.BigDecimal;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamQuantityTest {
@@ -38,10 +39,58 @@ public void testEquals() {
.setValue(new BigDecimal(123));
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
}
+ @Test
+ public void equalsIsTrueForOptimizedSearchParam() {
+ BaseResourceIndexedSearchParamQuantity param = createParam("NAME", "123.001", "value", "VALUE");
+ BaseResourceIndexedSearchParamQuantity param2 = createParam("NAME", "123.001", "value", "VALUE");
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+
+ private void validateEquals(BaseResourceIndexedSearchParamQuantity theParam1,
+ BaseResourceIndexedSearchParamQuantity theParam2) {
+ assertEquals(theParam2, theParam1);
+ assertEquals(theParam1, theParam2);
+ assertEquals(theParam1.hashCode(), theParam2.hashCode());
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, 123.0, units, kg, false, Observation, param, 123.0, units, kg, false, ResourceType is different",
+ "Patient, param, 123.0, units, kg, false, Patient, name, 123.0, units, kg, false, ParamName is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 321.0, units, kg, false, Value is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 123.0, unitsDiff, kg, false, System is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 123.0, units, lb, false, Units is different",
+ "Patient, param, 123.0, units, kg, false, Patient, param, 123.0, units, kg, true, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ double theFirstValue,
+ String theFirstSystem,
+ String theFirstUnits,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ double theSecondValue,
+ String theSecondSystem,
+ String theSecondUnits,
+ boolean theSecondMissing,
+ String theMessage) {
+ BaseResourceIndexedSearchParamQuantity param = new ResourceIndexedSearchParamQuantity(
+ new PartitionSettings(), theFirstResourceType, theFirstParamName, new BigDecimal(theFirstValue), theFirstSystem, theFirstUnits);
+ param.setMissing(theFirstMissing);
+ BaseResourceIndexedSearchParamQuantity param2 = new ResourceIndexedSearchParamQuantity(
+ new PartitionSettings(), theSecondResourceType, theSecondParamName, new BigDecimal(theSecondValue), theSecondSystem, theSecondUnits);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java
index f271c6a67438..d5fcf5f7a5de 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java
@@ -2,12 +2,12 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
@SuppressWarnings("SpellCheckingInspection")
public class ResourceIndexedSearchParamStringTest {
@@ -85,10 +85,7 @@ public void testEquals() {
val2.setPartitionSettings(new PartitionSettings());
val2.setStorageSettings(new StorageSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
-
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
}
@Test
@@ -105,9 +102,55 @@ public void testEqualsDifferentPartition() {
val2.setPartitionSettings(new PartitionSettings().setIncludePartitionInSearchHashes(true));
val2.setStorageSettings(new StorageSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
+ }
+
+ @Test
+ public void equalsIsTrueForOptimizedSearchParam() {
+ ResourceIndexedSearchParamString param = new ResourceIndexedSearchParamString(new PartitionSettings(), new StorageSettings(), "Patient", "param", "aaa", "AAA");
+ ResourceIndexedSearchParamString param2 = new ResourceIndexedSearchParamString(new PartitionSettings(), new StorageSettings(), "Patient", "param", "aaa", "AAA");
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+
+ private void validateEquals(ResourceIndexedSearchParamString theParam1,
+ ResourceIndexedSearchParamString theParam2) {
+ assertEquals(theParam2, theParam1);
+ assertEquals(theParam1, theParam2);
+ assertEquals(theParam1.hashCode(), theParam2.hashCode());
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, aaa, AAA, false, Observation, param, aaa, AAA, false, ResourceType is different",
+ "Patient, param, aaa, AAA, false, Patient, name, aaa, AAA, false, ParamName is different",
+ "Patient, param, aaa, AAA, false, Patient, param, bbb, AAA, false, Value is different",
+ "Patient, param, aaa, AAA, false, Patient, param, aaa, BBB, false, ValueNormalized is different",
+ "Patient, param, aaa, AAA, false, Patient, param, aaa, AAA, true, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ String theFirstValue,
+ String theFirstValueNormalized,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ String theSecondValue,
+ String theSecondValueNormalized,
+ boolean theSecondMissing,
+ String theMessage) {
+ ResourceIndexedSearchParamString param = new ResourceIndexedSearchParamString(new PartitionSettings(),
+ new StorageSettings(), theFirstResourceType, theFirstParamName, theFirstValue, theFirstValueNormalized);
+ param.setMissing(theFirstMissing);
+ ResourceIndexedSearchParamString param2 = new ResourceIndexedSearchParamString(new PartitionSettings(),
+ new StorageSettings(), theSecondResourceType, theSecondParamName, theSecondValue, theSecondValueNormalized);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
}
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java
index 92afa9b10c38..c1d867c6c5ab 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java
@@ -2,10 +2,11 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamTokenTest {
@@ -43,9 +44,54 @@ public void testEquals() {
.setValue("AAA");
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
}
+ @Test
+ public void equalsIsTrueForOptimizedSearchParam() {
+ ResourceIndexedSearchParamToken param = new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "NAME", "SYSTEM", "VALUE");
+ ResourceIndexedSearchParamToken param2 = new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "NAME", "SYSTEM", "VALUE");
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+
+ private void validateEquals(ResourceIndexedSearchParamToken theParam1,
+ ResourceIndexedSearchParamToken theParam2) {
+ assertEquals(theParam2, theParam1);
+ assertEquals(theParam1, theParam2);
+ assertEquals(theParam1.hashCode(), theParam2.hashCode());
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, system, value, false, Observation, param, system, value, false, ResourceType is different",
+ "Patient, param, system, value, false, Patient, name, system, value, false, ParamName is different",
+ "Patient, param, system, value, false, Patient, param, sys, value, false, System is different",
+ "Patient, param, system, value, false, Patient, param, system, val, false, Value is different",
+ "Patient, param, system, value, false, Patient, param, system, value, true, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ String theFirstSystem,
+ String theFirstValue,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ String theSecondSystem,
+ String theSecondValue,
+ boolean theSecondMissing,
+ String theMessage) {
+ ResourceIndexedSearchParamToken param = new ResourceIndexedSearchParamToken(
+ new PartitionSettings(), theFirstResourceType, theFirstParamName, theFirstSystem, theFirstValue);
+ param.setMissing(theFirstMissing);
+ ResourceIndexedSearchParamToken param2 = new ResourceIndexedSearchParamToken(
+ new PartitionSettings(), theSecondResourceType, theSecondParamName, theSecondSystem, theSecondValue);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java
index eb04e913758c..2d551dcb7fb9 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java
@@ -2,10 +2,11 @@
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamUriTest {
@@ -29,10 +30,52 @@ public void testEquals() {
.setUri("http://foo");
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
- assertNotNull(val1);
- assertEquals(val1, val2);
- assertThat("").isNotEqualTo(val1);
+ validateEquals(val1, val2);
}
+ @Test
+ public void equalsIsTrueForOptimizedSearchParam() {
+ ResourceIndexedSearchParamUri param = new ResourceIndexedSearchParamUri(new PartitionSettings(), "Patient", "NAME", "http://foo");
+ ResourceIndexedSearchParamUri param2 = new ResourceIndexedSearchParamUri(new PartitionSettings(), "Patient", "NAME", "http://foo");
+
+ param2.optimizeIndexStorage();
+
+ validateEquals(param, param2);
+ }
+
+ private void validateEquals(ResourceIndexedSearchParamUri theParam1,
+ ResourceIndexedSearchParamUri theParam2) {
+ assertEquals(theParam2, theParam1);
+ assertEquals(theParam1, theParam2);
+ assertEquals(theParam1.hashCode(), theParam2.hashCode());
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, param, http://test, false, Observation, param, http://test, false, ResourceType is different",
+ "Patient, param, http://test, false, Patient, name, http://test, false, ParamName is different",
+ "Patient, param, http://test, false, Patient, param, http://diff, false, Uri is different",
+ "Patient, param, http://test, false, Patient, param, http://test, true, Missing is different",
+ })
+ public void testEqualsAndHashCode_withDifferentParams_equalsIsFalseAndHashCodeIsDifferent(String theFirstResourceType,
+ String theFirstParamName,
+ String theFirstUri,
+ boolean theFirstMissing,
+ String theSecondResourceType,
+ String theSecondParamName,
+ String theSecondUri,
+ boolean theSecondMissing,
+ String theMessage) {
+ ResourceIndexedSearchParamUri param = new ResourceIndexedSearchParamUri(new PartitionSettings(),
+ theFirstResourceType, theFirstParamName, theFirstUri);
+ param.setMissing(theFirstMissing);
+ ResourceIndexedSearchParamUri param2 = new ResourceIndexedSearchParamUri(new PartitionSettings(),
+ theSecondResourceType, theSecondParamName, theSecondUri);
+ param2.setMissing(theSecondMissing);
+
+ assertNotEquals(param, param2, theMessage);
+ assertNotEquals(param2, param, theMessage);
+ assertNotEquals(param.hashCode(), param2.hashCode(), theMessage);
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/SearchParamHashUtilTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/SearchParamHashUtilTest.java
new file mode 100644
index 000000000000..fb709cdbed99
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/SearchParamHashUtilTest.java
@@ -0,0 +1,68 @@
+package ca.uhn.fhir.jpa.model.util;
+
+import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class SearchParamHashUtilTest {
+
+ private final PartitionSettings myPartitionSettings = new PartitionSettings();
+
+ @BeforeEach
+ void setUp() {
+ myPartitionSettings.setPartitioningEnabled(false);
+ }
+
+ @Test
+ public void hashSearchParam_withPartitionDisabled_generatesCorrectHashIdentity() {
+ Long hashIdentity = SearchParamHash.hashSearchParam(myPartitionSettings, null, "Patient", "name");
+ // Make sure hashing function gives consistent results
+ assertEquals(-1575415002568401616L, hashIdentity);
+ }
+
+ @Test
+ public void hashSearchParam_withPartitionDisabledAndNullValue_generatesCorrectHashIdentity() {
+ Long hashIdentity = SearchParamHash.hashSearchParam(myPartitionSettings, null, "Patient", "name", null);
+ // Make sure hashing function gives consistent results
+ assertEquals(-440750991942222070L, hashIdentity);
+ }
+
+ @Test
+ public void hashSearchParam_withIncludePartitionInSearchHashesAndNullRequestPartitionId_doesNotThrowException() {
+ myPartitionSettings.setPartitioningEnabled(true);
+ myPartitionSettings.setIncludePartitionInSearchHashes(true);
+
+ Long hashIdentity = SearchParamHash.hashSearchParam(myPartitionSettings, null, "Patient", "name");
+ assertEquals(-1575415002568401616L, hashIdentity);
+ }
+
+ @Test
+ public void hashSearchParam_withIncludePartitionInSearchHashesAndRequestPartitionId_includesPartitionIdInHash() {
+ myPartitionSettings.setPartitioningEnabled(true);
+ myPartitionSettings.setIncludePartitionInSearchHashes(true);
+ RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
+
+ Long hashIdentity = SearchParamHash.hashSearchParam(myPartitionSettings, requestPartitionId, "Patient", "name");
+ assertEquals(-6667609654163557704L, hashIdentity);
+ }
+
+ @Test
+ public void hashSearchParam_withIncludePartitionInSearchHashesAndMultipleRequestPartitionIds_throwsException() {
+ myPartitionSettings.setPartitioningEnabled(true);
+ myPartitionSettings.setIncludePartitionInSearchHashes(true);
+ RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIds(1, 2);
+
+ try {
+ SearchParamHash.hashSearchParam(myPartitionSettings, requestPartitionId, "Patient", "name");
+ fail();
+ } catch (InternalErrorException e) {
+ assertEquals(Msg.code(1527) + "Can not search multiple partitions when partitions are included in search hashes", e.getMessage());
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
index f1cabaf294d6..e9e9caa114d7 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
@@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
@@ -37,6 +38,7 @@
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
+import ca.uhn.fhir.jpa.model.util.SearchParamHash;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.searchparam.util.RuntimeSearchParamHelper;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -294,7 +296,7 @@ public boolean matchParam(
}
for (BaseResourceIndexedSearchParam nextParam : resourceParams) {
- if (nextParam.getParamName().equalsIgnoreCase(theParamName)) {
+ if (isMatchSearchParam(theStorageSettings, theResourceName, theParamName, nextParam)) {
if (nextParam.matches(value)) {
return true;
}
@@ -304,6 +306,21 @@ public boolean matchParam(
return false;
}
+ public static boolean isMatchSearchParam(
+ StorageSettings theStorageSettings,
+ String theResourceName,
+ String theParamName,
+ BaseResourceIndexedSearchParam theIndexedSearchParam) {
+
+ if (theStorageSettings.isIndexStorageOptimized()) {
+ Long hashIdentity = SearchParamHash.hashSearchParam(
+ new PartitionSettings(), RequestPartitionId.defaultPartition(), theResourceName, theParamName);
+ return theIndexedSearchParam.getHashIdentity().equals(hashIdentity);
+ } else {
+ return theIndexedSearchParam.getParamName().equalsIgnoreCase(theParamName);
+ }
+ }
+
/**
* @deprecated Replace with the method below
*/
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java
index 571b43f03b6c..78fc2cae5ef5 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java
@@ -71,6 +71,7 @@
import java.util.Set;
import java.util.stream.Collectors;
+import static ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams.isMatchSearchParam;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@@ -579,11 +580,11 @@ private boolean matchTokenParam(
switch (theQueryParam.getModifier()) {
case IN:
return theSearchParams.myTokenParams.stream()
- .filter(t -> t.getParamName().equals(theParamName))
+ .filter(t -> isMatchSearchParam(theStorageSettings, theResourceName, theParamName, t))
.anyMatch(t -> systemContainsCode(theQueryParam, t));
case NOT_IN:
return theSearchParams.myTokenParams.stream()
- .filter(t -> t.getParamName().equals(theParamName))
+ .filter(t -> isMatchSearchParam(theStorageSettings, theResourceName, theParamName, t))
.noneMatch(t -> systemContainsCode(theQueryParam, t));
case NOT:
return !theSearchParams.matchParam(
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java
index 6918ba990f77..d74faf0e656d 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java
@@ -25,9 +25,15 @@
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.interceptor.model.RequestPartitionId;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
+import ca.uhn.fhir.jpa.model.util.SearchParamHash;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.rest.server.util.IndexedSearchParam;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
@@ -46,14 +52,26 @@
import java.util.TreeSet;
import java.util.stream.Collectors;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.DATE;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.NUMBER;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.QUANTITY;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.REFERENCE;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.SPECIAL;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.STRING;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.TOKEN;
+import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.URI;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaSearchParamCache {
private static final Logger ourLog = LoggerFactory.getLogger(JpaSearchParamCache.class);
+ private static final List SUPPORTED_INDEXED_SEARCH_PARAMS =
+ List.of(SPECIAL, DATE, NUMBER, QUANTITY, STRING, TOKEN, URI, REFERENCE);
+
volatile Map> myActiveComboSearchParams = Collections.emptyMap();
volatile Map, List>> myActiveParamNamesToComboSearchParams =
Collections.emptyMap();
+ volatile Map myHashIdentityToIndexedSearchParams = Collections.emptyMap();
public List getActiveComboSearchParams(String theResourceName) {
List retval = myActiveComboSearchParams.get(theResourceName);
@@ -90,6 +108,10 @@ public List getActiveComboSearchParams(String theResourceNam
return Collections.unmodifiableList(retVal);
}
+ public Optional getIndexedSearchParamByHashIdentity(Long theHashIdentity) {
+ return Optional.ofNullable(myHashIdentityToIndexedSearchParams.get(theHashIdentity));
+ }
+
void populateActiveSearchParams(
IInterceptorService theInterceptorBroadcaster,
IPhoneticEncoder theDefaultPhoneticEncoder,
@@ -99,6 +121,7 @@ void populateActiveSearchParams(
Map idToRuntimeSearchParam = new HashMap<>();
List jpaSearchParams = new ArrayList<>();
+ Map hashIdentityToIndexedSearchParams = new HashMap<>();
/*
* Loop through parameters and find JPA params
@@ -133,6 +156,7 @@ void populateActiveSearchParams(
}
setPhoneticEncoder(theDefaultPhoneticEncoder, nextCandidate);
+ populateIndexedSearchParams(theResourceName, nextCandidate, hashIdentityToIndexedSearchParams);
}
}
@@ -183,6 +207,7 @@ void populateActiveSearchParams(
myActiveComboSearchParams = resourceNameToComboSearchParams;
myActiveParamNamesToComboSearchParams = activeParamNamesToComboSearchParams;
+ myHashIdentityToIndexedSearchParams = hashIdentityToIndexedSearchParams;
}
void setPhoneticEncoder(IPhoneticEncoder theDefaultPhoneticEncoder, RuntimeSearchParam searchParam) {
@@ -195,4 +220,36 @@ void setPhoneticEncoder(IPhoneticEncoder theDefaultPhoneticEncoder, RuntimeSearc
searchParam.setPhoneticEncoder(theDefaultPhoneticEncoder);
}
}
+
+ private void populateIndexedSearchParams(
+ String theResourceName,
+ RuntimeSearchParam theRuntimeSearchParam,
+ Map theHashIdentityToIndexedSearchParams) {
+
+ if (SUPPORTED_INDEXED_SEARCH_PARAMS.contains(theRuntimeSearchParam.getParamType())) {
+ addIndexedSearchParam(
+ theResourceName, theHashIdentityToIndexedSearchParams, theRuntimeSearchParam.getName());
+ // handle token search parameters with :of-type modifier
+ if (theRuntimeSearchParam.getParamType() == TOKEN) {
+ addIndexedSearchParam(
+ theResourceName,
+ theHashIdentityToIndexedSearchParams,
+ theRuntimeSearchParam.getName() + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE);
+ }
+ // handle Uplifted Ref Chain Search Parameters
+ theRuntimeSearchParam.getUpliftRefchainCodes().stream()
+ .map(urCode -> String.format("%s.%s", theRuntimeSearchParam.getName(), urCode))
+ .forEach(urSpName ->
+ addIndexedSearchParam(theResourceName, theHashIdentityToIndexedSearchParams, urSpName));
+ }
+ }
+
+ private void addIndexedSearchParam(
+ String theResourceName,
+ Map theHashIdentityToIndexedSearchParams,
+ String theSpName) {
+ Long hashIdentity = SearchParamHash.hashSearchParam(
+ new PartitionSettings(), RequestPartitionId.defaultPartition(), theResourceName, theSpName);
+ theHashIdentityToIndexedSearchParams.put(hashIdentity, new IndexedSearchParam(theSpName, theResourceName));
+ }
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java
index 506499076f8a..4d67c566bc14 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java
@@ -31,12 +31,14 @@
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
+import ca.uhn.fhir.jpa.model.search.ISearchParamHashIdentityRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
+import ca.uhn.fhir.rest.server.util.IndexedSearchParam;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.fhir.util.SearchParameterUtil;
import ca.uhn.fhir.util.StopWatch;
@@ -65,7 +67,10 @@
import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParamRegistryImpl
- implements ISearchParamRegistry, IResourceChangeListener, ISearchParamRegistryController {
+ implements ISearchParamRegistry,
+ IResourceChangeListener,
+ ISearchParamRegistryController,
+ ISearchParamHashIdentityRegistry {
public static final Set NON_DISABLEABLE_SEARCH_PARAMS =
Collections.unmodifiableSet(Sets.newHashSet("*:url", "Subscription:*", "SearchParameter:*"));
@@ -147,6 +152,11 @@ public List getActiveComboSearchParams(String theResourceNam
return myJpaSearchParamCache.getActiveComboSearchParams(theResourceName, theParamNames);
}
+ @Override
+ public Optional getIndexedSearchParamByHashIdentity(Long theHashIdentity) {
+ return myJpaSearchParamCache.getIndexedSearchParamByHashIdentity(theHashIdentity);
+ }
+
@Nullable
@Override
public RuntimeSearchParam getActiveSearchParamByUrl(String theUrl) {
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java
index 30ba81151b43..a1ad93d1f2df 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java
@@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
@@ -7,6 +8,8 @@
import com.google.common.collect.Lists;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.util.Date;
import java.util.List;
@@ -103,4 +106,35 @@ public void testExtractCompositeStringUniquesValueChains() {
assertThat(values).as(values.toString()).isEmpty();
}
+ @ParameterizedTest
+ @CsvSource({
+ "name, name, , false, true",
+ "name, NAME, , false, true",
+ "name, name, 7000, false, true",
+ "name, param, , false, false",
+ "name, param, 7000, false, false",
+ " , name, -1575415002568401616, true, true",
+ "param, name, -1575415002568401616, true, true",
+ " , param, -1575415002568401616, true, false",
+ "name, param, -1575415002568401616, true, false",
+ })
+ public void testIsMatchSearchParams_matchesByParamNameOrHashIdentity(String theParamName,
+ String theExpectedParamName,
+ Long theHashIdentity,
+ boolean theIndexStorageOptimized,
+ boolean theShouldMatch) {
+ // setup
+ StorageSettings storageSettings = new StorageSettings();
+ storageSettings.setIndexStorageOptimized(theIndexStorageOptimized);
+ ResourceIndexedSearchParamString param = new ResourceIndexedSearchParamString();
+ param.setResourceType("Patient");
+ param.setParamName(theParamName);
+ param.setHashIdentity(theHashIdentity);
+
+ // execute
+ boolean isMatch = ResourceIndexedSearchParams.isMatchSearchParam(storageSettings, "Patient", theExpectedParamName, param);
+
+ // validate
+ assertThat(isMatch).isEqualTo(theShouldMatch);
+ }
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5IndexStorageOptimizedTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5IndexStorageOptimizedTest.java
new file mode 100644
index 000000000000..77d46a98f072
--- /dev/null
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5IndexStorageOptimizedTest.java
@@ -0,0 +1,45 @@
+package ca.uhn.fhir.jpa.searchparam.matcher;
+
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
+import org.hl7.fhir.r5.model.Observation;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+
+public class InMemoryResourceMatcherR5IndexStorageOptimizedTest extends InMemoryResourceMatcherR5Test {
+
+ @Override
+ @BeforeEach
+ public void before() {
+ super.before();
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+
+ @AfterEach
+ public void after() {
+ myStorageSettings.setIndexStorageOptimized(false);
+ }
+
+ @Override
+ protected ResourceIndexedSearchParamDate extractEffectiveDateParam(Observation theObservation) {
+ ResourceIndexedSearchParamDate searchParamDate = super.extractEffectiveDateParam(theObservation);
+ searchParamDate.optimizeIndexStorage();
+ return searchParamDate;
+ }
+
+ @Override
+ protected ResourceIndexedSearchParamToken extractCodeTokenParam(Observation theObservation) {
+ ResourceIndexedSearchParamToken searchParamToken = super.extractCodeTokenParam(theObservation);
+ searchParamToken.optimizeIndexStorage();
+ return searchParamToken;
+ }
+
+ @Override
+ protected ResourceIndexedSearchParamUri extractSourceUriParam(Observation theObservation) {
+ ResourceIndexedSearchParamUri searchParamUri = super.extractSourceUriParam(theObservation);
+ searchParamUri.optimizeIndexStorage();
+ return searchParamUri;
+ }
+}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java
index 59eb798e1945..ccee696f68fd 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java
@@ -34,6 +34,7 @@
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.Duration;
@@ -51,6 +52,7 @@
import static org.mockito.Mockito.when;
@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {InMemoryResourceMatcherR5Test.SpringConfig.class})
public class InMemoryResourceMatcherR5Test {
public static final String OBSERVATION_DATE = "1970-10-17";
public static final String OBSERVATION_DATETIME = OBSERVATION_DATE + "T01:00:00-08:30";
@@ -76,6 +78,8 @@ public class InMemoryResourceMatcherR5Test {
IndexedSearchParamExtractor myIndexedSearchParamExtractor;
@Autowired
private InMemoryResourceMatcher myInMemoryResourceMatcher;
+ @Autowired
+ StorageSettings myStorageSettings;
private Observation myObservation;
private ResourceIndexedSearchParams mySearchParams;
@@ -414,17 +418,17 @@ private ResourceIndexedSearchParams extractSearchParams(Observation theObservati
}
@Nonnull
- private ResourceIndexedSearchParamDate extractEffectiveDateParam(Observation theObservation) {
+ protected ResourceIndexedSearchParamDate extractEffectiveDateParam(Observation theObservation) {
BaseDateTimeType dateValue = (BaseDateTimeType) theObservation.getEffective();
- return new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "date", dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValueAsString());
+ return new ResourceIndexedSearchParamDate(new PartitionSettings(), "Observation", "date", dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValueAsString());
}
- private ResourceIndexedSearchParamToken extractCodeTokenParam(Observation theObservation) {
+ protected ResourceIndexedSearchParamToken extractCodeTokenParam(Observation theObservation) {
Coding coding = theObservation.getCode().getCodingFirstRep();
return new ResourceIndexedSearchParamToken(new PartitionSettings(), "Observation", "code", coding.getSystem(), coding.getCode());
}
- private ResourceIndexedSearchParamUri extractSourceUriParam(Observation theObservation) {
+ protected ResourceIndexedSearchParamUri extractSourceUriParam(Observation theObservation) {
String source = theObservation.getMeta().getSource();
return new ResourceIndexedSearchParamUri(new PartitionSettings(), "Observation", "_source", source);
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java
index 0d8786b33d0b..5d924313a157 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java
@@ -1,17 +1,25 @@
package ca.uhn.fhir.jpa.searchparam.registry;
import ca.uhn.fhir.context.ComboSearchParamType;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.util.SearchParamHash;
+import ca.uhn.fhir.rest.server.util.IndexedSearchParam;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import static ca.uhn.fhir.util.HapiExtensions.EXTENSION_SEARCHPARAM_UPLIFT_REFCHAIN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -19,10 +27,11 @@
import static org.mockito.Mockito.when;
public class JpaSearchParamCacheTest {
-
+ private static final FhirContext ourFhirContext = FhirContext.forR4Cached();
private static final String RESOURCE_TYPE = "Patient";
private TestableJpaSearchParamCache myJpaSearchParamCache;
+
@BeforeEach
public void beforeEach(){
myJpaSearchParamCache = new TestableJpaSearchParamCache();
@@ -93,6 +102,41 @@ public void testGetActiveComboParamByIdAbsent(){
assertTrue(found.isEmpty());
}
+ @ParameterizedTest
+ @CsvSource({
+ "Patient, name, name, type = string",
+ "Patient, active, active, type = token",
+ "Patient, active, active:of-type, type = token with of-type",
+ "Patient, birthdate, birthdate, type = date",
+ "Patient, general-practitioner, general-practitioner, type = reference",
+ "Location, near, near, type = special",
+ "RiskAssessment, probability, probability, type = number",
+ "Observation, value-quantity, value-quantity, type = quantity",
+ "ValueSet, url, url, type = uri",
+ "Encounter, subject, subject.name, type = reference with refChain"
+ })
+ public void getIndexedSearchParamByHashIdentity_returnsCorrectIndexedSearchParam(String theResourceType,
+ String theSpName,
+ String theExpectedSpName,
+ String theSpType) {
+ // setup
+ RuntimeSearchParamCache runtimeCache = new RuntimeSearchParamCache();
+ RuntimeResourceDefinition resourceDefinition = ourFhirContext.getResourceDefinition(theResourceType);
+ RuntimeSearchParam runtimeSearchParam = resourceDefinition.getSearchParam(theSpName);
+ runtimeSearchParam.addUpliftRefchain("name", EXTENSION_SEARCHPARAM_UPLIFT_REFCHAIN);
+ runtimeCache.add(theResourceType, theSpName, resourceDefinition.getSearchParam(theSpName));
+ Long hashIdentity = SearchParamHash.hashSearchParam(new PartitionSettings(), null, theResourceType, theExpectedSpName);
+
+ // execute
+ myJpaSearchParamCache.populateActiveSearchParams(null, null, runtimeCache);
+ Optional indexedSearchParam = myJpaSearchParamCache.getIndexedSearchParamByHashIdentity(hashIdentity);
+
+ // validate
+ assertTrue(indexedSearchParam.isPresent(), "No IndexedSearchParam found for search param with " + theSpType);
+ assertEquals(theResourceType, indexedSearchParam.get().getResourceType());
+ assertEquals(theExpectedSpName, indexedSearchParam.get().getParameterName());
+ }
+
private RuntimeSearchParam createSearchParam(ComboSearchParamType theType){
return createSearchParam(null, theType);
}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4IndexStorageOptimizedTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4IndexStorageOptimizedTest.java
new file mode 100644
index 000000000000..d94b00893373
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4IndexStorageOptimizedTest.java
@@ -0,0 +1,362 @@
+package ca.uhn.fhir.jpa.dao.r4;
+
+import ca.uhn.fhir.batch2.api.IJobCoordinator;
+import ca.uhn.fhir.batch2.jobs.reindex.ReindexAppCtx;
+import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters;
+import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
+import ca.uhn.fhir.jpa.config.SearchConfig;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
+import ca.uhn.fhir.jpa.model.entity.StorageSettings;
+import ca.uhn.fhir.jpa.model.util.SearchParamHash;
+import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
+import ca.uhn.fhir.jpa.reindex.ReindexStepTest;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
+import ca.uhn.fhir.rest.param.BaseParam;
+import ca.uhn.fhir.rest.param.DateParam;
+import ca.uhn.fhir.rest.param.NumberParam;
+import ca.uhn.fhir.rest.param.QuantityParam;
+import ca.uhn.fhir.rest.param.SpecialParam;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.param.UriParam;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.DateType;
+import org.hl7.fhir.r4.model.DecimalType;
+import org.hl7.fhir.r4.model.Location;
+import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.Quantity;
+import org.hl7.fhir.r4.model.RiskAssessment;
+import org.hl7.fhir.r4.model.Substance;
+import org.hl7.fhir.r4.model.ValueSet;
+import org.junit.jupiter.api.AfterEach;
+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.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * This test was added to check if changing {@link StorageSettings#isIndexStorageOptimized()} setting and performing
+ * $reindex operation will correctly null/recover sp_name, res_type, sp_updated parameters
+ * of ResourceIndexedSearchParam entities.
+ */
+public class FhirResourceDaoR4IndexStorageOptimizedTest extends BaseJpaR4Test {
+
+ @Autowired
+ private IJobCoordinator myJobCoordinator;
+
+ @Autowired
+ private SearchConfig mySearchConfig;
+
+ @AfterEach
+ void cleanUp() {
+ myPartitionSettings.setIncludePartitionInSearchHashes(false);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testCoordinatesIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ Location loc = new Location();
+ loc.getPosition().setLatitude(43.7);
+ loc.getPosition().setLongitude(79.4);
+ IIdType id = myLocationDao.create(loc, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myLocationDao, myResourceIndexedSearchParamCoordsDao, id,
+ Location.SP_NEAR, "Location", new SpecialParam().setValue("43.7|79.4"), ResourceIndexedSearchParamCoords.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testDateIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ Patient p = new Patient();
+ p.setBirthDateElement(new DateType("2021-02-22"));
+ IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myPatientDao, myResourceIndexedSearchParamDateDao, id,
+ Patient.SP_BIRTHDATE, "Patient", new DateParam("2021-02-22"), ResourceIndexedSearchParamDate.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testNumberIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ RiskAssessment riskAssessment = new RiskAssessment();
+ DecimalType doseNumber = new DecimalType(15);
+ riskAssessment.addPrediction(new RiskAssessment.RiskAssessmentPredictionComponent().setProbability(doseNumber));
+ IIdType id = myRiskAssessmentDao.create(riskAssessment, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myRiskAssessmentDao, myResourceIndexedSearchParamNumberDao, id,
+ RiskAssessment.SP_PROBABILITY, "RiskAssessment", new NumberParam(15), ResourceIndexedSearchParamNumber.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testQuantityIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ Observation observation = new Observation();
+ observation.setValue(new Quantity(123));
+ IIdType id = myObservationDao.create(observation, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myObservationDao, myResourceIndexedSearchParamQuantityDao, id,
+ Observation.SP_VALUE_QUANTITY, "Observation", new QuantityParam(123), ResourceIndexedSearchParamQuantity.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testQuantityNormalizedIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ myStorageSettings.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
+ Substance res = new Substance();
+ res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
+ IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
+
+ QuantityParam quantityParam = new QuantityParam(null, 123, UcumServiceUtil.UCUM_CODESYSTEM_URL, "m");
+ validateAndReindex(theIsIndexStorageOptimized, mySubstanceDao, myResourceIndexedSearchParamQuantityNormalizedDao,
+ id, Substance.SP_QUANTITY, "Substance", quantityParam, ResourceIndexedSearchParamQuantityNormalized.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testStringIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ Patient p = new Patient();
+ p.addAddress().addLine("123 Main Street");
+ IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myPatientDao, myResourceIndexedSearchParamStringDao, id,
+ Patient.SP_ADDRESS, "Patient", new StringParam("123 Main Street"), ResourceIndexedSearchParamString.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testTokenIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ Observation observation = new Observation();
+ observation.setStatus(Observation.ObservationStatus.FINAL);
+ IIdType id = myObservationDao.create(observation, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myObservationDao, myResourceIndexedSearchParamTokenDao, id,
+ Observation.SP_STATUS, "Observation", new TokenParam("final"), ResourceIndexedSearchParamToken.class);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testUriIndexedSearchParam_searchAndReindex_searchParamUpdatedCorrectly(boolean theIsIndexStorageOptimized) {
+ // setup
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+ ValueSet valueSet = new ValueSet();
+ valueSet.setUrl("http://vs");
+ IIdType id = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless();
+
+ validateAndReindex(theIsIndexStorageOptimized, myValueSetDao, myResourceIndexedSearchParamUriDao, id,
+ ValueSet.SP_URL, "ValueSet", new UriParam("http://vs"), ResourceIndexedSearchParamUri.class);
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "false, false, false",
+ "false, false, true",
+ "false, true, false",
+ "true, false, false",
+ "true, false, true",
+ "true, true, false"})
+ public void testValidateConfiguration_withCorrectConfiguration_doesNotThrowException(boolean thePartitioningEnabled,
+ boolean theIsIncludePartitionInSearchHashes,
+ boolean theIsIndexStorageOptimized) {
+ myPartitionSettings.setPartitioningEnabled(thePartitioningEnabled);
+ myPartitionSettings.setIncludePartitionInSearchHashes(theIsIncludePartitionInSearchHashes);
+ myStorageSettings.setIndexStorageOptimized(theIsIndexStorageOptimized);
+
+ assertDoesNotThrow(() -> mySearchConfig.validateConfiguration());
+ }
+
+ @Test
+ public void testValidateConfiguration_withInCorrectConfiguration_throwsException() {
+ myPartitionSettings.setIncludePartitionInSearchHashes(true);
+ myPartitionSettings.setPartitioningEnabled(true);
+ myStorageSettings.setIndexStorageOptimized(true);
+
+ try {
+ mySearchConfig.validateConfiguration();
+ fail();
+ } catch (ConfigurationException e) {
+ assertEquals(Msg.code(2525) + "Incorrect configuration. "
+ + "StorageSettings#isIndexStorageOptimized and PartitionSettings.isIncludePartitionInSearchHashes "
+ + "cannot be enabled at the same time.", e.getMessage());
+ }
+ }
+
+ private void validateAndReindex(boolean theIsIndexStorageOptimized, IFhirResourceDao extends IBaseResource> theResourceDao,
+ JpaRepository extends BaseResourceIndexedSearchParam, Long> theIndexedSpRepository, IIdType theId,
+ String theSearchParam, String theResourceType, BaseParam theParamValue,
+ Class extends BaseResourceIndexedSearchParam> theIndexedSearchParamClass) {
+ // validate
+ validateSearchContainsResource(theResourceDao, theId, theSearchParam, theParamValue);
+ validateSearchParams(theIndexedSpRepository, theId, theSearchParam, theResourceType, theIndexedSearchParamClass);
+
+ // switch on/off storage optimization and run $reindex
+ myStorageSettings.setIndexStorageOptimized(!theIsIndexStorageOptimized);
+ executeReindex(theResourceType + "?");
+
+ // validate again
+ validateSearchContainsResource(theResourceDao, theId, theSearchParam, theParamValue);
+ validateSearchParams(theIndexedSpRepository, theId, theSearchParam, theResourceType, theIndexedSearchParamClass);
+ }
+
+ private void validateSearchParams(JpaRepository extends BaseResourceIndexedSearchParam, Long> theIndexedSpRepository,
+ IIdType theId, String theSearchParam, String theResourceType,
+ Class extends BaseResourceIndexedSearchParam> theIndexedSearchParamClass) {
+ List extends BaseResourceIndexedSearchParam> repositorySearchParams =
+ getAndValidateIndexedSearchParamsRepository(theIndexedSpRepository, theId, theSearchParam, theResourceType);
+
+ long hash = SearchParamHash.hashSearchParam(new PartitionSettings(), null, theResourceType, theSearchParam);
+ if (myStorageSettings.isIndexStorageOptimized()) {
+ // validated sp_name, res_type, sp_updated columns are null in DB
+ runInTransaction(() -> {
+ List> results = myEntityManager.createQuery("SELECT i FROM " + theIndexedSearchParamClass.getSimpleName() +
+ " i WHERE i.myResourcePid = " + theId.getIdPartAsLong() + " AND i.myResourceType IS NULL " +
+ "AND i.myParamName IS NULL AND i.myUpdated IS NULL AND i.myHashIdentity = " + hash, theIndexedSearchParamClass).getResultList();
+ assertFalse(results.isEmpty());
+ assertEquals(repositorySearchParams.size(), results.size());
+ });
+ } else {
+ // validated sp_name, res_type, sp_updated columns are not null in DB
+ runInTransaction(() -> {
+ List> results = myEntityManager.createQuery("SELECT i FROM " + theIndexedSearchParamClass.getSimpleName() +
+ " i WHERE i.myResourcePid = " + theId.getIdPartAsLong() + " AND i.myResourceType = '" + theResourceType +
+ "' AND i.myParamName = '" + theSearchParam + "' AND i.myUpdated IS NOT NULL AND i.myHashIdentity = " + hash,
+ theIndexedSearchParamClass).getResultList();
+ assertFalse(results.isEmpty());
+ assertEquals(repositorySearchParams.size(), results.size());
+ });
+ }
+ }
+
+ private List extends BaseResourceIndexedSearchParam> getAndValidateIndexedSearchParamsRepository(
+ JpaRepository extends BaseResourceIndexedSearchParam, Long> theIndexedSpRepository,
+ IIdType theId, String theSearchParam, String theResourceType) {
+
+ List extends BaseResourceIndexedSearchParam> repositorySearchParams = theIndexedSpRepository.findAll()
+ .stream()
+ .filter(sp -> sp.getResourcePid().equals(theId.getIdPartAsLong()))
+ .filter(sp -> theSearchParam.equals(sp.getParamName()))
+ .toList();
+ assertFalse(repositorySearchParams.isEmpty());
+
+ repositorySearchParams.forEach(sp -> {
+ assertEquals(theResourceType, sp.getResourceType());
+ if (myStorageSettings.isIndexStorageOptimized()) {
+ assertNull(sp.getUpdated());
+ } else {
+ assertNotNull(sp.getUpdated());
+ }
+ });
+
+ return repositorySearchParams;
+ }
+
+ private void validateSearchContainsResource(IFhirResourceDao extends IBaseResource> theResourceDao,
+ IIdType theId,
+ String theSearchParam,
+ BaseParam theParamValue) {
+ SearchParameterMap searchParameterMap = new SearchParameterMap()
+ .setLoadSynchronous(true)
+ .add(theSearchParam, theParamValue);
+ List listIds = toUnqualifiedVersionlessIds(theResourceDao.search(searchParameterMap));
+
+ assertTrue(listIds.contains(theId));
+ }
+
+ private void executeReindex(String... theUrls) {
+ ReindexJobParameters parameters = new ReindexJobParameters();
+ for (String url : theUrls) {
+ parameters.addUrl(url);
+ }
+ JobInstanceStartRequest startRequest = new JobInstanceStartRequest();
+ startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX);
+ startRequest.setParameters(parameters);
+ Batch2JobStartResponse res = myJobCoordinator.startInstance(mySrd, startRequest);
+ ourLog.info("Started reindex job with id {}", res.getInstanceId());
+ myBatch2JobHelper.awaitJobCompletion(res);
+ }
+
+ // Additional existing tests with enabled IndexStorageOptimized
+ @Nested
+ public class IndexStorageOptimizedReindexStepTest extends ReindexStepTest {
+ @BeforeEach
+ void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+ }
+
+ @Nested
+ public class IndexStorageOptimizedPartitioningSqlR4Test extends PartitioningSqlR4Test {
+ @BeforeEach
+ void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+ }
+
+ @Nested
+ public class IndexStorageOptimizedFhirResourceDaoR4SearchMissingTest extends FhirResourceDaoR4SearchMissingTest {
+ @BeforeEach
+ void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+ }
+
+ @Nested
+ public class IndexStorageOptimizedFhirResourceDaoR4QueryCountTest extends FhirResourceDaoR4QueryCountTest {
+ @BeforeEach
+ void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+ }
+
+ @Nested
+ public class IndexStorageOptimizedFhirResourceDaoR4SearchNoFtTest extends FhirResourceDaoR4SearchNoFtTest {
+ @BeforeEach
+ void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
index a21183839e66..c90a55b210c0 100644
--- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
@@ -7155,6 +7155,20 @@ private String toStr(Date theDate) {
return new InstantDt(theDate).getValueAsString();
}
+ @Nested
+ public class IndexStorageOptimizedMissingSearchParameterTests extends MissingSearchParameterTests {
+ @BeforeEach
+ public void init() {
+ super.init();
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+
+ @AfterEach
+ public void cleanUp() {
+ myStorageSettings.setIndexStorageOptimized(false);
+ }
+ }
+
@Nested
public class MissingSearchParameterTests {
diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5IndexStorageOptimizedTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5IndexStorageOptimizedTest.java
new file mode 100644
index 000000000000..232b13c97458
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5IndexStorageOptimizedTest.java
@@ -0,0 +1,52 @@
+package ca.uhn.fhir.jpa.dao.r5;
+
+import ca.uhn.fhir.jpa.model.entity.StorageSettings;
+import ca.uhn.fhir.jpa.search.reindex.InstanceReindexServiceImplR5Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+
+/**
+ * R5 Test cases with enabled {@link StorageSettings#isIndexStorageOptimized()}
+ */
+public class FhirResourceDaoR5IndexStorageOptimizedTest {
+
+ @Nested
+ public class IndexStorageOptimizedFhirSystemDaoTransactionR5Test extends FhirSystemDaoTransactionR5Test {
+ @BeforeEach
+ public void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+
+ @AfterEach
+ public void cleanUp() {
+ myStorageSettings.setIndexStorageOptimized(false);
+ }
+ }
+
+ @Nested
+ public class IndexStorageOptimizedInstanceReindexServiceImplR5Test extends InstanceReindexServiceImplR5Test {
+ @BeforeEach
+ public void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+
+ @AfterEach
+ public void cleanUp() {
+ myStorageSettings.setIndexStorageOptimized(false);
+ }
+ }
+
+ @Nested
+ public class IndexStorageOptimizedUpliftedRefchainsAndChainedSortingR5Test extends UpliftedRefchainsAndChainedSortingR5Test {
+ @BeforeEach
+ public void setUp() {
+ myStorageSettings.setIndexStorageOptimized(true);
+ }
+
+ @AfterEach
+ public void cleanUp() {
+ myStorageSettings.setIndexStorageOptimized(false);
+ }
+ }
+}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/IndexedSearchParam.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/IndexedSearchParam.java
new file mode 100644
index 000000000000..a4513480c885
--- /dev/null
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/IndexedSearchParam.java
@@ -0,0 +1,42 @@
+/*-
+ * #%L
+ * HAPI FHIR - Server Framework
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.rest.server.util;
+
+/**
+ * Simplified model of indexed search parameter
+ */
+public class IndexedSearchParam {
+
+ private final String myParameterName;
+ private final String myResourceType;
+
+ public IndexedSearchParam(String theParameterName, String theResourceType) {
+ this.myParameterName = theParameterName;
+ this.myResourceType = theResourceType;
+ }
+
+ public String getParameterName() {
+ return myParameterName;
+ }
+
+ public String getResourceType() {
+ return myResourceType;
+ }
+}