Performance enhancements to JPA searching

This commit is contained in:
James Agnew 2018-06-15 01:13:44 +08:00
parent a46b4a4637
commit 99f80eef88
21 changed files with 276 additions and 218 deletions

View File

@ -910,7 +910,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
param = new ResourceIndexedSearchParamQuantity(); param = new ResourceIndexedSearchParamQuantity();
break; break;
case STRING: case STRING:
param = new ResourceIndexedSearchParamString(); param = new ResourceIndexedSearchParamString()
.setDaoConfig(myConfig);
break; break;
case TOKEN: case TOKEN:
param = new ResourceIndexedSearchParamToken(); param = new ResourceIndexedSearchParamToken();
@ -2057,6 +2058,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
if (thePerformIndexing) { if (thePerformIndexing) {
for (ResourceIndexedSearchParamString next : removeCommon(existingStringParams, stringParams)) { for (ResourceIndexedSearchParamString next : removeCommon(existingStringParams, stringParams)) {
next.setDaoConfig(myConfig);
myEntityManager.remove(next); myEntityManager.remove(next);
theEntity.getParamsString().remove(next); theEntity.getParamsString().remove(next);
} }

View File

@ -20,43 +20,41 @@ package ca.uhn.fhir.jpa.dao;
* #L% * #L%
*/ */
import java.util.ArrayList; import ca.uhn.fhir.context.FhirContext;
import java.util.Collection; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import java.util.Collections; import ca.uhn.fhir.context.RuntimeSearchParam;
import java.util.List; import ca.uhn.fhir.util.FhirTerser;
import java.util.regex.Pattern; import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList;
import java.util.Collection;
import ca.uhn.fhir.context.FhirContext; import java.util.Collections;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import java.util.List;
import ca.uhn.fhir.context.RuntimeSearchParam; import java.util.regex.Pattern;
import ca.uhn.fhir.util.FhirTerser;
public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { public abstract class BaseSearchParamExtractor implements ISearchParamExtractor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
public static final Pattern SPLIT = Pattern.compile("\\||( or )"); public static final Pattern SPLIT = Pattern.compile("\\||( or )");
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
@Autowired @Autowired
private FhirContext myContext; private FhirContext myContext;
@Autowired
private DaoConfig myDaoConfig;
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
public BaseSearchParamExtractor() { public BaseSearchParamExtractor() {
super(); super();
} }
public BaseSearchParamExtractor(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) { public BaseSearchParamExtractor(DaoConfig theDaoConfig, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
myContext = theCtx; myContext = theCtx;
mySearchParamRegistry = theSearchParamRegistry; mySearchParamRegistry = theSearchParamRegistry;
myDaoConfig = theDaoConfig;
} }
@Override @Override
@ -105,6 +103,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return myContext; return myContext;
} }
public DaoConfig getDaoConfig() {
return myDaoConfig;
}
public Collection<RuntimeSearchParam> getSearchParams(IBaseResource theResource) { public Collection<RuntimeSearchParam> getSearchParams(IBaseResource theResource) {
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
Collection<RuntimeSearchParam> retVal = mySearchParamRegistry.getActiveSearchParams(def.getName()).values(); Collection<RuntimeSearchParam> retVal = mySearchParamRegistry.getActiveSearchParams(def.getName()).values();

View File

@ -89,7 +89,7 @@ public class DaoConfig {
/** /**
* update setter javadoc if default changes * update setter javadoc if default changes
*/ */
private boolean myAllowContainsSearches = true; private boolean myAllowContainsSearches = false;
/** /**
* update setter javadoc if default changes * update setter javadoc if default changes
@ -754,7 +754,15 @@ public class DaoConfig {
* If enabled, the server will support the use of :contains searches, * If enabled, the server will support the use of :contains searches,
* which are helpful but can have adverse effects on performance. * which are helpful but can have adverse effects on performance.
* <p> * <p>
* Default is <code>true</code> * Default is <code>false</code> (Note that prior to HAPI FHIR
* 3.5.0 the default was <code>true</code>)
* </p>
* <p>
* Note: If you change this value after data already has
* already been stored in the database, you must for a reindexing
* of all data in the database or resources may not be
* searchable.
* </p>
*/ */
public boolean isAllowContainsSearches() { public boolean isAllowContainsSearches() {
return myAllowContainsSearches; return myAllowContainsSearches;
@ -764,12 +772,21 @@ public class DaoConfig {
* If enabled, the server will support the use of :contains searches, * If enabled, the server will support the use of :contains searches,
* which are helpful but can have adverse effects on performance. * which are helpful but can have adverse effects on performance.
* <p> * <p>
* Default is <code>true</code> * Default is <code>false</code> (Note that prior to HAPI FHIR
* 3.5.0 the default was <code>true</code>)
* </p>
* <p>
* Note: If you change this value after data already has
* already been stored in the database, you must for a reindexing
* of all data in the database or resources may not be
* searchable.
* </p>
*/ */
public void setAllowContainsSearches(boolean theAllowContainsSearches) { public void setAllowContainsSearches(boolean theAllowContainsSearches) {
this.myAllowContainsSearches = theAllowContainsSearches; this.myAllowContainsSearches = theAllowContainsSearches;
} }
/** /**
* If set to <code>true</code> (default is <code>false</code>) the server will allow * If set to <code>true</code> (default is <code>false</code>) the server will allow
* resources to have references to external servers. For example if this server is * resources to have references to external servers. For example if this server is

View File

@ -61,6 +61,8 @@ import org.apache.commons.lang3.tuple.Pair;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.query.Query; import org.hibernate.query.Query;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPredicate;
import org.hl7.fhir.dstu3.model.BaseResource; import org.hl7.fhir.dstu3.model.BaseResource;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -594,7 +596,7 @@ public class SearchBuilder implements ISearchBuilder {
return; return;
} }
List<Predicate> codePredicates = new ArrayList<Predicate>(); List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) { for (IQueryParameterType nextOr : theList) {
IQueryParameterType theParameter = nextOr; IQueryParameterType theParameter = nextOr;
Predicate singleCode = createPredicateString(theParameter, theResourceName, theParamName, myBuilder, join); Predicate singleCode = createPredicateString(theParameter, theResourceName, theParamName, myBuilder, join);
@ -742,7 +744,7 @@ public class SearchBuilder implements ISearchBuilder {
return; return;
} }
List<Predicate> codePredicates = new ArrayList<Predicate>(); List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) { for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof TokenParam) { if (nextOr instanceof TokenParam) {
@ -1087,6 +1089,7 @@ public class SearchBuilder implements ISearchBuilder {
private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder, private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom) { From<?, ResourceIndexedSearchParamString> theFrom) {
String rawSearchTerm; String rawSearchTerm;
DaoConfig daoConfig = myCallingDao.getConfig();
if (theParameter instanceof TokenParam) { if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter; TokenParam id = (TokenParam) theParameter;
if (!id.isText()) { if (!id.isText()) {
@ -1097,7 +1100,7 @@ public class SearchBuilder implements ISearchBuilder {
StringParam id = (StringParam) theParameter; StringParam id = (StringParam) theParameter;
rawSearchTerm = id.getValue(); rawSearchTerm = id.getValue();
if (id.isContains()) { if (id.isContains()) {
if (!myCallingDao.getConfig().isAllowContainsSearches()) { if (!daoConfig.isAllowContainsSearches()) {
throw new MethodNotAllowedException(":contains modifier is disabled on this server"); throw new MethodNotAllowedException(":contains modifier is disabled on this server");
} }
} }
@ -1113,22 +1116,34 @@ public class SearchBuilder implements ISearchBuilder {
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
} }
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm); boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
if (exactMatch) {
// Exact match
Long hash = ResourceIndexedSearchParamString.calculateHashExact(theResourceName, theParamName, rawSearchTerm);
return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash);
} else {
// Normalized Match
String normalizedString = BaseHapiFhirDao.normalizeString(rawSearchTerm);
String likeExpression;
if (theParameter instanceof StringParam && if (theParameter instanceof StringParam &&
((StringParam) theParameter).isContains() && ((StringParam) theParameter).isContains() &&
myCallingDao.getConfig().isAllowContainsSearches()) { daoConfig.isAllowContainsSearches()) {
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression); likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
} else { } else {
likeExpression = createLeftMatchLikeExpression(likeExpression); likeExpression = createLeftMatchLikeExpression(normalizedString);
} }
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(daoConfig, theResourceName, theParamName, normalizedString);
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) { return theBuilder.and(hashCode, singleCode);
Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm);
singleCode = theBuilder.and(singleCode, exactCode);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); }
} }
private List<Predicate> createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) { private List<Predicate> createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
@ -1183,7 +1198,7 @@ public class SearchBuilder implements ISearchBuilder {
* Process token modifiers (:in, :below, :above) * Process token modifiers (:in, :below, :above)
*/ */
List<VersionIndependentConcept> codes = null; List<VersionIndependentConcept> codes;
if (modifier == TokenParamModifier.IN) { if (modifier == TokenParamModifier.IN) {
codes = myTerminologySvc.expandValueSet(code); codes = myTerminologySvc.expandValueSet(code);
} else if (modifier == TokenParamModifier.ABOVE) { } else if (modifier == TokenParamModifier.ABOVE) {
@ -1192,81 +1207,53 @@ public class SearchBuilder implements ISearchBuilder {
} else if (modifier == TokenParamModifier.BELOW) { } else if (modifier == TokenParamModifier.BELOW) {
system = determineSystemIfMissing(theParamName, code, system); system = determineSystemIfMissing(theParamName, code, system);
codes = myTerminologySvc.findCodesBelow(system, code); codes = myTerminologySvc.findCodesBelow(system, code);
} else {
codes = Collections.singletonList(new VersionIndependentConcept(system, code));
} }
ArrayList<Predicate> singleCodePredicates = new ArrayList<>();
if (codes != null) {
if (codes.isEmpty()) { if (codes.isEmpty()) {
// This will never match anything // This will never match anything
Predicate codePredicate = theBuilder.isNull(theFrom.get("myMissing")); return new BooleanStaticAssertionPredicate((CriteriaBuilderImpl) theBuilder, false);
singleCodePredicates.add(codePredicate);
} else {
List<Predicate> orPredicates = new ArrayList<Predicate>();
Map<String, List<VersionIndependentConcept>> map = new HashMap<String, List<VersionIndependentConcept>>();
for (VersionIndependentConcept nextCode : codes) {
List<VersionIndependentConcept> systemCodes = map.get(nextCode.getSystem());
if (null == systemCodes) {
systemCodes = new ArrayList<>();
map.put(nextCode.getSystem(), systemCodes);
} }
systemCodes.add(nextCode);
}
// Use "in" in case of large numbers of codes due to param modifiers
final Path<String> systemExpression = theFrom.get("mySystem");
final Path<String> valueExpression = theFrom.get("myValue");
for (Map.Entry<String, List<VersionIndependentConcept>> entry : map.entrySet()) {
Predicate systemPredicate = theBuilder.equal(systemExpression, entry.getKey());
In<String> codePredicate = theBuilder.in(valueExpression);
for (VersionIndependentConcept nextCode : entry.getValue()) {
codePredicate.value(nextCode.getCode());
}
orPredicates.add(theBuilder.and(systemPredicate, codePredicate));
}
singleCodePredicates.add(theBuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()])));
}
} else {
/* /*
* Ok, this is a normal query * Note: A null system value means "match any system", but
* an empty-string system value means "match values that
* explicitly have no system".
*/ */
boolean haveSystem = codes.get(0).getSystem() != null;
if (StringUtils.isNotBlank(system)) { boolean haveCode = isNotBlank(codes.get(0).getCode());
if (modifier != null && modifier == TokenParamModifier.NOT) { Expression<Long> hashField;
singleCodePredicates.add(theBuilder.notEqual(theFrom.get("mySystem"), system)); if (!haveSystem && !haveCode) {
// If we have neither, this isn't actually an expression so
// just return 1=1
return new BooleanStaticAssertionPredicate((CriteriaBuilderImpl) theBuilder, true);
} else if (haveSystem && haveCode) {
hashField = theFrom.get("myHashSystemAndValue").as(Long.class);
} else if (haveSystem) {
hashField = theFrom.get("myHashSystem").as(Long.class);
} else { } else {
singleCodePredicates.add(theBuilder.equal(theFrom.get("mySystem"), system)); hashField = theFrom.get("myHashValue").as(Long.class);
}
} else if (system == null) {
// don't check the system
} else {
// If the system is "", we only match on null systems
singleCodePredicates.add(theBuilder.isNull(theFrom.get("mySystem")));
} }
if (StringUtils.isNotBlank(code)) { List<Long> values = new ArrayList<>(codes.size());
if (modifier != null && modifier == TokenParamModifier.NOT) { for (VersionIndependentConcept next : codes) {
singleCodePredicates.add(theBuilder.notEqual(theFrom.get("myValue"), code)); if (haveSystem && haveCode) {
values.add(ResourceIndexedSearchParamToken.calculateHashSystemAndValue(theResourceName, theParamName, next.getSystem(), next.getCode()));
} else if (haveSystem) {
values.add(ResourceIndexedSearchParamToken.calculateHashSystem(theResourceName, theParamName, next.getSystem()));
} else { } else {
singleCodePredicates.add(theBuilder.equal(theFrom.get("myValue"), code)); values.add(ResourceIndexedSearchParamToken.calculateHashValue(theResourceName, theParamName, next.getCode()));
}
} else {
/*
* As of HAPI FHIR 1.5, if the client searched for a token with a system but no specified value this means to
* match all tokens with the given value.
*
* I'm not sure I agree with this, but hey.. FHIR-I voted and this was the result :)
*/
// singleCodePredicates.add(theBuilder.isNull(theFrom.get("myValue")));
} }
} }
Predicate singleCode = theBuilder.and(toArray(singleCodePredicates)); Predicate predicate = hashField.in(values);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); if (modifier == TokenParamModifier.NOT) {
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), ResourceIndexedSearchParamToken.calculateHashIdentity(theResourceName, theParamName));
Predicate disjunctionPredicate = theBuilder.not(predicate);
predicate = theBuilder.and(identityPredicate, disjunctionPredicate);
}
return predicate;
} }
@Override @Override

View File

@ -59,7 +59,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
} }
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm); ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} }
@ -68,7 +68,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
} }
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value); ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} }

View File

@ -65,8 +65,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
super(); super();
} }
public SearchParamExtractorDstu3(FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) { public SearchParamExtractorDstu3(DaoConfig theDaoConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
super(theCtx, theSearchParamRegistry); super(theDaoConfig, theCtx, theSearchParamRegistry);
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
@ -78,7 +78,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
} }
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm); ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} }
@ -87,7 +87,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
} }
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value); ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} }

View File

@ -64,8 +64,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
super(); super();
} }
public SearchParamExtractorR4(FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) { public SearchParamExtractorR4(DaoConfig theDaoConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
super(theCtx, theSearchParamRegistry); super(theDaoConfig, theCtx, theSearchParamRegistry);
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
@ -77,7 +77,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
} }
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm); ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), resourceName, BaseHapiFhirDao.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} }
@ -86,7 +86,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
} }
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value); ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getDaoConfig(), nextSpDef.getName(), BaseHapiFhirDao.normalizeString(value), value);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} }
@ -104,7 +104,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
*/ */
@Override @Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>(); HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) { for (RuntimeSearchParam nextSpDef : searchParams) {

View File

@ -38,7 +38,7 @@ import java.util.Date;
public abstract class BaseResourceIndexedSearchParam implements Serializable { public abstract class BaseResourceIndexedSearchParam implements Serializable {
/** Don't change this without careful consideration. You will break existing hashes! */ /** Don't change this without careful consideration. You will break existing hashes! */
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0); private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
/** Don't make this public 'cause nobody better touch it! */ /** Don't make this public 'cause nobody better be able to modify it! */
private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8); private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
static final int MAX_SP_NAME = 100; static final int MAX_SP_NAME = 100;

View File

@ -33,7 +33,6 @@ import org.hibernate.search.annotations.NumericField;
import javax.persistence.*; import javax.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@ -66,15 +65,15 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; private Long myId;
/** /**
* @since 3.4.0 - At some point this should be made not-null * @since 3.5.0 - At some point this should be made not-null
*/ */
@Column(name = "HASH_UNITS_AND_VALPREFIX", nullable = true) @Column(name = "HASH_IDENTITY_AND_UNITS", nullable = true)
private Long myHashUnitsAndValPrefix; private Long myHashIdentityAndUnits;
/** /**
* @since 3.4.0 - At some point this should be made not-null * @since 3.5.0 - At some point this should be made not-null
*/ */
@Column(name = "HASH_VALPREFIX", nullable = true) @Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashValPrefix; private Long myHashIdentity;
public ResourceIndexedSearchParamQuantity() { public ResourceIndexedSearchParamQuantity() {
// nothing // nothing
@ -89,16 +88,16 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
@PrePersist @PrePersist
public void calculateHashes() { public void calculateHashes() {
if (myHashUnitsAndValPrefix == null) { if (myHashIdentity == null) {
setHashUnitsAndValPrefix(hash(getResourceType(), getParamName(), getSystem(), getUnits(), toTruncatedString(getValue()))); setHashIdentity(hash(getResourceType(), getParamName()));
setHashValPrefix(hash(getResourceType(), getParamName(), toTruncatedString(getValue()))); setHashIdentityAndUnits(hash(getResourceType(), getParamName(), getSystem(), getUnits()));
} }
} }
@Override @Override
protected void clearHashes() { protected void clearHashes() {
myHashUnitsAndValPrefix = null; myHashIdentity = null;
myHashValPrefix = null; myHashIdentityAndUnits = null;
} }
@Override @Override
@ -119,27 +118,25 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
b.append(getSystem(), obj.getSystem()); b.append(getSystem(), obj.getSystem());
b.append(getUnits(), obj.getUnits()); b.append(getUnits(), obj.getUnits());
b.append(getValue(), obj.getValue()); b.append(getValue(), obj.getValue());
b.append(getHashUnitsAndValPrefix(), obj.getHashUnitsAndValPrefix());
b.append(getHashValPrefix(), obj.getHashValPrefix());
return b.isEquals(); return b.isEquals();
} }
public Long getHashUnitsAndValPrefix() { public Long getHashIdentity() {
calculateHashes(); calculateHashes();
return myHashUnitsAndValPrefix; return myHashIdentity;
} }
public void setHashUnitsAndValPrefix(Long theHashUnitsAndValPrefix) { public void setHashIdentity(Long theHashIdentity) {
myHashUnitsAndValPrefix = theHashUnitsAndValPrefix; myHashIdentity = theHashIdentity;
} }
public Long getHashValPrefix() { public Long getHashIdentityAndUnits() {
calculateHashes(); calculateHashes();
return myHashValPrefix; return myHashIdentityAndUnits;
} }
public void setHashValPrefix(Long theHashValPrefix) { public void setHashIdentityAndUnits(Long theHashIdentityAndUnits) {
myHashValPrefix = theHashValPrefix; myHashIdentityAndUnits = theHashIdentityAndUnits;
} }
@Override @Override
@ -176,14 +173,13 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
@Override @Override
public int hashCode() { public int hashCode() {
calculateHashes();
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
b.append(getResourceType());
b.append(getParamName()); b.append(getParamName());
b.append(getResource());
b.append(getSystem()); b.append(getSystem());
b.append(getUnits()); b.append(getUnits());
b.append(getValue()); b.append(getValue());
b.append(getHashUnitsAndValPrefix());
b.append(getHashValPrefix());
return b.toHashCode(); return b.toHashCode();
} }
@ -204,11 +200,4 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return b.build(); return b.build();
} }
private static String toTruncatedString(BigDecimal theValue) {
if (theValue == null) {
return null;
}
return theValue.setScale(0, RoundingMode.FLOOR).toPlainString();
}
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -38,7 +39,14 @@ import static org.apache.commons.lang3.StringUtils.left;
@Embeddable @Embeddable
@Entity @Entity
@Table(name = "HFJ_SPIDX_STRING", indexes = { @Table(name = "HFJ_SPIDX_STRING", indexes = {
@Index(name = "IDX_SP_STRING", columnList = "RES_TYPE,SP_NAME,SP_VALUE_NORMALIZED"), /*
* Note: We previously had indexes with the following names,
* do not reuse these names:
* IDX_SP_STRING
*/
@Index(name = "IDX_SP_STRING_HASH_NRM", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"),
@Index(name = "IDX_SP_STRING_HASH_EXCT", columnList = "HASH_EXACT"),
@Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID") @Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
}) })
@ -127,13 +135,16 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
*/ */
@Column(name = "HASH_EXACT", nullable = true) @Column(name = "HASH_EXACT", nullable = true)
private Long myHashExact; private Long myHashExact;
@Transient
private transient DaoConfig myDaoConfig;
public ResourceIndexedSearchParamString() { public ResourceIndexedSearchParamString() {
super(); super();
} }
public ResourceIndexedSearchParamString(String theName, String theValueNormalized, String theValueExact) { public ResourceIndexedSearchParamString(DaoConfig theDaoConfig, String theName, String theValueNormalized, String theValueExact) {
setDaoConfig(theDaoConfig);
setParamName(theName); setParamName(theName);
setValueNormalized(theValueNormalized); setValueNormalized(theValueNormalized);
setValueExact(theValueExact); setValueExact(theValueExact);
@ -142,8 +153,12 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
@PrePersist @PrePersist
public void calculateHashes() { public void calculateHashes() {
if (myHashNormalizedPrefix == null) { if (myHashNormalizedPrefix == null) {
setHashNormalizedPrefix(hash(getResourceType(), getParamName(), left(getValueNormalized(), HASH_PREFIX_LENGTH))); String resourceType = getResourceType();
setHashExact(hash(getResourceType(), getParamName(), getValueExact())); String paramName = getParamName();
String valueNormalized = getValueNormalized();
String valueExact = getValueExact();
setHashNormalizedPrefix(calculateHashNormalized(myDaoConfig, resourceType, paramName, valueNormalized));
setHashExact(calculateHashExact(resourceType, paramName, valueExact));
} }
} }
@ -169,8 +184,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
b.append(getParamName(), obj.getParamName()); b.append(getParamName(), obj.getParamName());
b.append(getResource(), obj.getResource()); b.append(getResource(), obj.getResource());
b.append(getValueExact(), obj.getValueExact()); b.append(getValueExact(), obj.getValueExact());
b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
b.append(getHashExact(), obj.getHashExact());
return b.isEquals(); return b.isEquals();
} }
@ -179,6 +192,11 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return myHashExact; return myHashExact;
} }
public BaseResourceIndexedSearchParam setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig;
return this;
}
public void setHashExact(Long theHashExact) { public void setHashExact(Long theHashExact) {
myHashExact = theHashExact; myHashExact = theHashExact;
} }
@ -221,12 +239,11 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
@Override @Override
public int hashCode() { public int hashCode() {
calculateHashes();
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
b.append(getParamName()); b.append(getParamName());
b.append(getResource()); b.append(getResource());
b.append(getValueExact()); b.append(getValueExact());
b.append(getHashNormalizedPrefix());
b.append(getHashExact());
return b.toHashCode(); return b.toHashCode();
} }
@ -244,4 +261,23 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return b.build(); return b.build();
} }
public static long calculateHashExact(String theResourceType, String theParamName, String theValueExact) {
return hash(theResourceType, theParamName, theValueExact);
}
public static long calculateHashNormalized(DaoConfig theDaoConfig, String theResourceType, String theParamName, String theValueNormalized) {
/*
* If we're not allowing contained searches, we'll add the first
* bit of the normalized value to the hash. This helps to
* make the hash even more unique, which will be good for
* performance.
*/
int hashPrefixLength = HASH_PREFIX_LENGTH;
if (theDaoConfig.isAllowContainsSearches()) {
hashPrefixLength = 0;
}
return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
}
} }

View File

@ -31,11 +31,23 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*; import javax.persistence.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.trim;
@Embeddable @Embeddable
@Entity @Entity
@Table(name = "HFJ_SPIDX_TOKEN", indexes = { @Table(name = "HFJ_SPIDX_TOKEN", indexes = {
@Index(name = "IDX_SP_TOKEN", columnList = "RES_TYPE,SP_NAME,SP_SYSTEM,SP_VALUE"), /*
@Index(name = "IDX_SP_TOKEN_UNQUAL", columnList = "RES_TYPE,SP_NAME,SP_VALUE"), * Note: We previously had indexes with the following names,
* do not reuse these names:
* IDX_SP_TOKEN
* IDX_SP_TOKEN_UNQUAL
*/
@Index(name = "IDX_SP_TOKEN_HASH", columnList = "HASH_IDENTITY"),
@Index(name = "IDX_SP_TOKEN_HASH_S", columnList = "HASH_SYS"),
@Index(name = "IDX_SP_TOKEN_HASH_SV", columnList = "HASH_SYS_AND_VALUE"),
@Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE"),
@Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID") @Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID")
}) })
@ -56,6 +68,11 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; 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 * @since 3.4.0 - At some point this should be made not-null
*/ */
@ -90,17 +107,20 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
setValue(theValue); setValue(theValue);
} }
@PrePersist @PrePersist
public void calculateHashes() { public void calculateHashes() {
if (myHashSystem == null) { if (myHashSystem == null) {
setHashSystem(hash(getResourceType(), getParamName(), getSystem())); String resourceType = getResourceType();
setHashSystemAndValue(hash(getResourceType(), getParamName(), getSystem(), getValue())); String paramName = getParamName();
setHashValue(hash(getResourceType(), getParamName(), getValue())); String system = getSystem();
String value = getValue();
setHashIdentity(calculateHashIdentity(resourceType, paramName));
setHashSystem(calculateHashSystem(resourceType, paramName, system));
setHashSystemAndValue(calculateHashSystemAndValue(resourceType, paramName, system, value));
setHashValue(calculateHashValue(resourceType, paramName, value));
} }
} }
@Override @Override
protected void clearHashes() { protected void clearHashes() {
myHashSystem = null; myHashSystem = null;
@ -125,9 +145,6 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append(getResource(), obj.getResource()); b.append(getResource(), obj.getResource());
b.append(getSystem(), obj.getSystem()); b.append(getSystem(), obj.getSystem());
b.append(getValue(), obj.getValue()); b.append(getValue(), obj.getValue());
b.append(getHashSystem(), obj.getHashSystem());
b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
b.append(getHashValue(), obj.getHashValue());
return b.isEquals(); return b.isEquals();
} }
@ -136,6 +153,15 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return myHashSystem; return myHashSystem;
} }
public Long getHashIdentity() {
calculateHashes();
return myHashIdentity;
}
public void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}
public void setHashSystem(Long theHashSystem) { public void setHashSystem(Long theHashSystem) {
myHashSystem = theHashSystem; myHashSystem = theHashSystem;
} }
@ -184,18 +210,15 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@Override @Override
public int hashCode() { public int hashCode() {
calculateHashes();
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
b.append(getParamName()); b.append(getParamName());
b.append(getResource()); b.append(getResource());
b.append(getSystem()); b.append(getSystem());
b.append(getValue()); b.append(getValue());
b.append(getHashSystem());
b.append(getHashSystemAndValue());
b.append(getHashValue());
return b.toHashCode(); return b.toHashCode();
} }
@Override @Override
public IQueryParameterType toQueryParameterType() { public IQueryParameterType toQueryParameterType() {
return new TokenParam(getSystem(), getValue()); return new TokenParam(getSystem(), getValue());
@ -210,4 +233,20 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append("value", getValue()); b.append("value", getValue());
return b.build(); return b.build();
} }
public static long calculateHashSystem(String theResourceType, String theParamName, String theSystem) {
return hash(theResourceType, theParamName, trim(theSystem));
}
public static long calculateHashIdentity(String theResourceType, String theParamName) {
return hash(theResourceType, theParamName);
}
public static long calculateHashSystemAndValue(String theResourceType, String theParamName, String theSystem, String theValue) {
return hash(theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue));
}
public static long calculateHashValue(String theResourceType, String theParamName, String theValue) {
return hash(theResourceType, theParamName, trim(theValue));
}
} }

View File

@ -66,14 +66,12 @@ public class ResourceLink implements Serializable {
@ManyToOne(optional = false, fetch=FetchType.LAZY) @ManyToOne(optional = false, fetch=FetchType.LAZY)
@JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey=@ForeignKey(name="FK_RESLINK_SOURCE")) @JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey=@ForeignKey(name="FK_RESLINK_SOURCE"))
// @ContainedIn()
private ResourceTable mySourceResource; private ResourceTable mySourceResource;
@Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false) @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false)
private Long mySourceResourcePid; private Long mySourceResourcePid;
@Column(name = "SOURCE_RESOURCE_TYPE", nullable=false, length=ResourceTable.RESTYPE_LEN) @Column(name = "SOURCE_RESOURCE_TYPE", nullable=false, length=ResourceTable.RESTYPE_LEN)
@ColumnDefault("''") // TODO: remove this (it's only here for simplifying upgrades of 1.3 -> 1.4)
@Field() @Field()
private String mySourceResourceType; private String mySourceResourceType;
@ -86,7 +84,6 @@ public class ResourceLink implements Serializable {
private Long myTargetResourcePid; private Long myTargetResourcePid;
@Column(name = "TARGET_RESOURCE_TYPE", nullable=false, length=ResourceTable.RESTYPE_LEN) @Column(name = "TARGET_RESOURCE_TYPE", nullable=false, length=ResourceTable.RESTYPE_LEN)
@ColumnDefault("''") // TODO: remove this (it's only here for simplifying upgrades of 1.3 -> 1.4)
@Field() @Field()
private String myTargetResourceType; private String myTargetResourceType;

View File

@ -107,7 +107,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
DataSource dataSource = ProxyDataSourceBuilder DataSource dataSource = ProxyDataSourceBuilder
.create(retVal) .create(retVal)
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS) .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
.countQuery(new ThreadQueryCountHolder()) .countQuery(new ThreadQueryCountHolder())
.build(); .build();

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
@ -81,7 +82,7 @@ public class SearchParamExtractorDstu3Test {
} }
}; };
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport, searchParamRegistry); SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new DaoConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
Set<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs); Set<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs);
assertEquals(1, tokens.size()); assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();

View File

@ -257,6 +257,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
myDaoConfig.setExpireSearchResultsAfterMillis(new DaoConfig().getExpireSearchResultsAfterMillis()); myDaoConfig.setExpireSearchResultsAfterMillis(new DaoConfig().getExpireSearchResultsAfterMillis());
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange()); myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
} }
@After @After

View File

@ -2261,6 +2261,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test @Test
public void testSearchWithContains() { public void testSearchWithContains() {
myDaoConfig.setAllowContainsSearches(true);
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.addName().setFamily("ABCDEFGHIJK"); pt1.addName().setFamily("ABCDEFGHIJK");

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
@ -80,7 +81,7 @@ public class SearchParamExtractorR4Test {
} }
}; };
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(ourCtx, ourValidationSupport, searchParamRegistry); SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new DaoConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
Set<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs); Set<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs);
assertEquals(1, tokens.size()); assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();

View File

@ -19,19 +19,8 @@ public class ResourceIndexedSearchParamQuantityTest {
ResourceIndexedSearchParamQuantity token = createParam("NAME", "123.001", "value", "VALUE"); ResourceIndexedSearchParamQuantity token = createParam("NAME", "123.001", "value", "VALUE");
// Make sure our hashing function gives consistent results // Make sure our hashing function gives consistent results
assertEquals(945335027461836896L, token.getHashUnitsAndValPrefix().longValue()); assertEquals(834432764963581074L, token.getHashIdentity().longValue());
assertEquals(5549105497508660145L, token.getHashValPrefix().longValue()); assertEquals(3029184989308001259L, token.getHashIdentityAndUnits().longValue());
}
@Test
public void testValueTrimming() {
assertEquals(7265149425397186226L, createParam("NAME", "401.001", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
assertEquals(7265149425397186226L, createParam("NAME", "401.99999", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
assertEquals(7265149425397186226L, createParam("NAME", "401", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
// Should be different
assertEquals(-8387917096585386046L, createParam("NAME", "400.9999999", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
// Should be different
assertEquals(8819656626732693650L, createParam("NAME", "402.000000", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
} }

View File

@ -1,14 +1,16 @@
package ca.uhn.fhir.jpa.entity; package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@SuppressWarnings("SpellCheckingInspection")
public class ResourceIndexedSearchParamStringTest { public class ResourceIndexedSearchParamStringTest {
@Test @Test
public void testHashFunctions() { public void testHashFunctions() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString("NAME", "value", "VALUE"); ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new DaoConfig(), "NAME", "value", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient")); token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results // Make sure our hashing function gives consistent results
@ -18,7 +20,7 @@ public class ResourceIndexedSearchParamStringTest {
@Test @Test
public void testHashFunctionsPrefixOnly() { public void testHashFunctionsPrefixOnly() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString("NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ"); ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new DaoConfig(), "NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ");
token.setResource(new ResourceTable().setResourceType("Patient")); token.setResource(new ResourceTable().setResourceType("Patient"));
// Should be the same as in testHashFunctions() // Should be the same as in testHashFunctions()

View File

@ -3159,16 +3159,13 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
testSearchWithEmptyParameter("/Observation?code=bar&value-concept="); testSearchWithEmptyParameter("/Observation?code=bar&value-concept=");
} }
private void testSearchWithEmptyParameter(String url) throws IOException { private void testSearchWithEmptyParameter(String theUrl) throws IOException {
HttpGet get = new HttpGet(ourServerBase + url); HttpGet get = new HttpGet(ourServerBase + theUrl);
CloseableHttpResponse resp = ourHttpClient.execute(get); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
try {
assertEquals(200, resp.getStatusLine().getStatusCode()); assertEquals(200, resp.getStatusLine().getStatusCode());
String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8);
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString);
assertEquals(1, bundle.getEntry().size()); assertEquals(1, bundle.getEntry().size());
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
} }
} }

View File

@ -3571,14 +3571,11 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
private void testSearchWithEmptyParameter(String url) throws IOException { private void testSearchWithEmptyParameter(String url) throws IOException {
HttpGet get = new HttpGet(ourServerBase + url); HttpGet get = new HttpGet(ourServerBase + url);
CloseableHttpResponse resp = ourHttpClient.execute(get); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
try {
assertEquals(200, resp.getStatusLine().getStatusCode()); assertEquals(200, resp.getStatusLine().getStatusCode());
String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8);
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString);
assertEquals(1, bundle.getEntry().size()); assertEquals(1, bundle.getEntry().size());
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
} }
} }