Refactor query builder for Reference

This commit is contained in:
juan.marchionatto 2022-04-14 09:48:58 -04:00
parent c29bb462c5
commit 85e0442082
3 changed files with 76 additions and 55 deletions

View File

@ -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()) );
}
}

View File

@ -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; }

View File

@ -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);