mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-03-09 14:33:32 +00:00
Refactor query builder for Reference
This commit is contained in:
parent
c29bb462c5
commit
85e0442082
@ -55,17 +55,18 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.CODE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_EXACT;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_NORMALIZED;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_TEXT;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.NESTED_SEARCH_PARAM_ROOT;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_CODE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_CODE_NORM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_PARAM_NAME;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_SYSTEM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_VALUE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.SYSTEM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.VALUE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_VALUE_NORM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.SEARCH_PARAM_ROOT;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
@ -110,12 +111,12 @@ public class ExtendedLuceneClauseBuilder {
|
||||
} else if (nextOr instanceof TokenParam) {
|
||||
TokenParam nextOrToken = (TokenParam) nextOr;
|
||||
nextValueTrimmed = nextOrToken.getValue();
|
||||
} else if (nextOr instanceof ReferenceParam) {
|
||||
ReferenceParam referenceParam = (ReferenceParam) nextOr;
|
||||
nextValueTrimmed = referenceParam.getValue();
|
||||
if (nextValueTrimmed.contains("/_history")) {
|
||||
nextValueTrimmed = nextValueTrimmed.substring(0, nextValueTrimmed.indexOf("/_history"));
|
||||
}
|
||||
// } else if (nextOr instanceof ReferenceParam) {
|
||||
// ReferenceParam referenceParam = (ReferenceParam) nextOr;
|
||||
// nextValueTrimmed = referenceParam.getValue();
|
||||
// if (nextValueTrimmed.contains("/_history")) {
|
||||
// nextValueTrimmed = nextValueTrimmed.substring(0, nextValueTrimmed.indexOf("/_history"));
|
||||
// }
|
||||
} else {
|
||||
throw new IllegalArgumentException(Msg.code(1088) + "Unsupported full-text param type: " + nextOr.getClass());
|
||||
}
|
||||
@ -271,18 +272,9 @@ public class ExtendedLuceneClauseBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addReferenceUnchainedSearch(String theSearchParamName, List<List<IQueryParameterType>> theReferenceAndOrTerms) {
|
||||
String fieldPath = SEARCH_PARAM_ROOT + "." + theSearchParamName + ".reference.value";
|
||||
for (List<? extends IQueryParameterType> nextAnd : theReferenceAndOrTerms) {
|
||||
Set<String> terms = extractOrStringParams(nextAnd);
|
||||
ourLog.trace("reference unchained search {}", terms);
|
||||
|
||||
List<? extends PredicateFinalStep> orTerms = terms.stream()
|
||||
.map(s -> myPredicateFactory.match().field(fieldPath).matching(s))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
myRootClause.must(orPredicateOrSingle(orTerms));
|
||||
}
|
||||
addAndOrSearchClauses(theSearchParamName, theReferenceAndOrTerms, this::addReferenceOrClauses);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -476,29 +468,52 @@ public class ExtendedLuceneClauseBuilder {
|
||||
* Differences with DB search:
|
||||
* _ is not all-normalized-or-all-not. Each parameter is applied on quantity or normalized quantity depending on UCUM fitness
|
||||
* _ respects ranges for equal and approximate qualifiers
|
||||
*
|
||||
* Strategy: For each parameter, if it can be canonicalized, it is, and used against 'normalized-value-quantity' index
|
||||
* otherwise it is applied as-is to 'value-quantity'
|
||||
*/
|
||||
public void addQuantityUnmodifiedSearch(String theSearchParamName, List<List<IQueryParameterType>> theQuantityAndOrTerms) {
|
||||
addAndOrSearchClauses(theSearchParamName, theQuantityAndOrTerms, this::addQuantityOrClauses);
|
||||
}
|
||||
|
||||
for (List<IQueryParameterType> nextAnd : theQuantityAndOrTerms) {
|
||||
BooleanPredicateClausesStep<?> quantityTerms = myPredicateFactory.bool();
|
||||
quantityTerms.minimumShouldMatchNumber(1);
|
||||
|
||||
private BooleanPredicateClausesStep<?> addReferenceOrClauses(String theSearchParamName, IQueryParameterType theParamType) {
|
||||
BooleanPredicateClausesStep<?> orTerms = myPredicateFactory.bool();
|
||||
|
||||
String fieldPath = SEARCH_PARAM_ROOT + "." + theSearchParamName + ".reference.value";
|
||||
|
||||
ReferenceParam referenceParam = (ReferenceParam) theParamType;
|
||||
String nextValueTrimmed = referenceParam.getValue();
|
||||
if (nextValueTrimmed.contains("/_history")) {
|
||||
nextValueTrimmed = nextValueTrimmed.substring(0, nextValueTrimmed.indexOf("/_history"));
|
||||
}
|
||||
if (isNotBlank(nextValueTrimmed)) {
|
||||
orTerms.must(myPredicateFactory.match().field(fieldPath).matching(nextValueTrimmed));
|
||||
}
|
||||
|
||||
return orTerms;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles "and" clauses (outer loop) and "or" (inner loop) generically using the received param-type specific function
|
||||
*/
|
||||
private void addAndOrSearchClauses(String theSearchParamName, List<List<IQueryParameterType>> theAndOrTerms,
|
||||
BiFunction<String, IQueryParameterType, BooleanPredicateClausesStep<?>> theAddOrClausesFunction) {
|
||||
|
||||
for (List<IQueryParameterType> nextAnd : theAndOrTerms) {
|
||||
BooleanPredicateClausesStep<?> terms = myPredicateFactory.bool();
|
||||
terms.minimumShouldMatchNumber(1);
|
||||
|
||||
for (IQueryParameterType paramType : nextAnd) {
|
||||
BooleanPredicateClausesStep<?> orQuantityTerms = myPredicateFactory.bool();
|
||||
addQuantityOrClauses(theSearchParamName, paramType, orQuantityTerms);
|
||||
quantityTerms.should(orQuantityTerms);
|
||||
BooleanPredicateClausesStep<?> orTerms = theAddOrClausesFunction.apply(theSearchParamName, paramType);
|
||||
terms.should(orTerms);
|
||||
}
|
||||
|
||||
myRootClause.must(quantityTerms);
|
||||
myRootClause.must(terms);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addQuantityOrClauses(String theSearchParamName,
|
||||
IQueryParameterType theParamType, BooleanPredicateClausesStep<?> theQuantityTerms) {
|
||||
private BooleanPredicateClausesStep<?> addQuantityOrClauses(String theSearchParamName, IQueryParameterType theParamType) {
|
||||
BooleanPredicateClausesStep<?> orQuantityTerms = myPredicateFactory.bool();
|
||||
|
||||
QuantityParam qtyParam = QuantityParam.toQuantityParam(theParamType);
|
||||
ParamPrefixEnum activePrefix = qtyParam.getPrefix() == null ? ParamPrefixEnum.EQUAL : qtyParam.getPrefix();
|
||||
@ -508,28 +523,36 @@ public class ExtendedLuceneClauseBuilder {
|
||||
QuantityParam canonicalQty = UcumServiceUtil.toCanonicalQuantityOrNull(qtyParam);
|
||||
if (canonicalQty != null) {
|
||||
String valueFieldPath = fieldPath + "." + QTY_VALUE_NORM;
|
||||
setPrefixedQuantityPredicate(theQuantityTerms, activePrefix, canonicalQty, valueFieldPath);
|
||||
theQuantityTerms.must(myPredicateFactory.match()
|
||||
setPrefixedQuantityPredicate(orQuantityTerms, activePrefix, canonicalQty, valueFieldPath);
|
||||
orQuantityTerms.must(myPredicateFactory.match()
|
||||
.field(fieldPath + "." + QTY_CODE_NORM)
|
||||
.matching(canonicalQty.getUnits()));
|
||||
return;
|
||||
return orQuantityTerms;
|
||||
}
|
||||
}
|
||||
|
||||
// not NORMALIZED_QUANTITY_SEARCH_SUPPORTED or non-canonicalizable parameter
|
||||
String valueFieldPath = fieldPath + "." + QTY_VALUE;
|
||||
setPrefixedQuantityPredicate(theQuantityTerms, activePrefix, qtyParam, valueFieldPath);
|
||||
addQuantityTerms(orQuantityTerms, activePrefix, qtyParam, fieldPath);
|
||||
return orQuantityTerms;
|
||||
}
|
||||
|
||||
if ( isNotBlank(qtyParam.getSystem()) ) {
|
||||
|
||||
private void addQuantityTerms(BooleanPredicateClausesStep<?> theQuantityTerms,
|
||||
ParamPrefixEnum theActivePrefix, QuantityParam theQtyParam, String theFieldPath) {
|
||||
|
||||
String valueFieldPath = theFieldPath + "." + VALUE;
|
||||
setPrefixedQuantityPredicate(theQuantityTerms, theActivePrefix, theQtyParam, valueFieldPath);
|
||||
|
||||
if ( isNotBlank(theQtyParam.getSystem()) ) {
|
||||
theQuantityTerms.must(
|
||||
myPredicateFactory.match()
|
||||
.field(fieldPath + "." + QTY_SYSTEM).matching(qtyParam.getSystem()) );
|
||||
.field(theFieldPath + "." + SYSTEM).matching(theQtyParam.getSystem()) );
|
||||
}
|
||||
|
||||
if ( isNotBlank(qtyParam.getUnits()) ) {
|
||||
if ( isNotBlank(theQtyParam.getUnits()) ) {
|
||||
theQuantityTerms.must(
|
||||
myPredicateFactory.match()
|
||||
.field(fieldPath + "." + QTY_CODE).matching(qtyParam.getUnits()) );
|
||||
.field(theFieldPath + "." + CODE).matching(theQtyParam.getUnits()) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.hl7.fhir.r4.model.Observation.SP_VALUE_QUANTITY;
|
||||
|
||||
public class HibernateSearchIndexWriter {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HibernateSearchIndexWriter.class);
|
||||
public static final String IDX_STRING_NORMALIZED = "norm";
|
||||
@ -44,9 +42,9 @@ public class HibernateSearchIndexWriter {
|
||||
public static final String SEARCH_PARAM_ROOT = "sp";
|
||||
|
||||
public static final String QTY_PARAM_NAME = "quantity";
|
||||
public static final String QTY_CODE = "code";
|
||||
public static final String QTY_SYSTEM = "system";
|
||||
public static final String QTY_VALUE = "value";
|
||||
public static final String CODE = "code";
|
||||
public static final String SYSTEM = "system";
|
||||
public static final String VALUE = "value";
|
||||
public static final String QTY_CODE_NORM = "code-norm";
|
||||
public static final String QTY_VALUE_NORM = "value-norm";
|
||||
|
||||
@ -127,9 +125,9 @@ public class HibernateSearchIndexWriter {
|
||||
DocumentElement nestedQtyNode = nestedSpNode.addObject(QTY_PARAM_NAME);
|
||||
|
||||
ourLog.trace("Adding Search Param Quantity: {} -- {}", theSearchParam, theValue);
|
||||
nestedQtyNode.addValue(QTY_CODE, theValue.getCode());
|
||||
nestedQtyNode.addValue(QTY_SYSTEM, theValue.getSystem());
|
||||
nestedQtyNode.addValue(QTY_VALUE, theValue.getValue());
|
||||
nestedQtyNode.addValue(CODE, theValue.getCode());
|
||||
nestedQtyNode.addValue(SYSTEM, theValue.getSystem());
|
||||
nestedQtyNode.addValue(VALUE, theValue.getValue());
|
||||
|
||||
if ( ! myModelConfig.getNormalizedQuantitySearchLevel().storageOrSearchSupported()) { return; }
|
||||
|
||||
|
@ -43,10 +43,10 @@ import java.time.Instant;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_EXACT;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_NORMALIZED;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_TEXT;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_CODE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.CODE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_CODE_NORM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_SYSTEM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_VALUE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.SYSTEM;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.VALUE;
|
||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_VALUE_NORM;
|
||||
|
||||
/**
|
||||
@ -178,9 +178,9 @@ public class SearchParamTextPropertyBinder implements PropertyBinder, PropertyBr
|
||||
//quantity
|
||||
String quantityPathGlob = "*.quantity";
|
||||
nestedSpField.objectFieldTemplate("quantityTemplate", ObjectStructure.FLATTENED).matchingPathGlob(quantityPathGlob);
|
||||
nestedSpField.fieldTemplate(QTY_SYSTEM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_SYSTEM);
|
||||
nestedSpField.fieldTemplate(QTY_CODE, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_CODE);
|
||||
nestedSpField.fieldTemplate(QTY_VALUE, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_VALUE);
|
||||
nestedSpField.fieldTemplate(SYSTEM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + SYSTEM);
|
||||
nestedSpField.fieldTemplate(CODE, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + CODE);
|
||||
nestedSpField.fieldTemplate(VALUE, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + VALUE);
|
||||
nestedSpField.fieldTemplate(QTY_CODE_NORM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_CODE_NORM);
|
||||
nestedSpField.fieldTemplate(QTY_VALUE_NORM, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_VALUE_NORM);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user