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.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors; 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_EXACT;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_NORMALIZED; 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.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.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_CODE_NORM;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_PARAM_NAME; 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.SYSTEM;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_VALUE; 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.QTY_VALUE_NORM;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.SEARCH_PARAM_ROOT; import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.SEARCH_PARAM_ROOT;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -110,12 +111,12 @@ public class ExtendedLuceneClauseBuilder {
} else if (nextOr instanceof TokenParam) { } else if (nextOr instanceof TokenParam) {
TokenParam nextOrToken = (TokenParam) nextOr; TokenParam nextOrToken = (TokenParam) nextOr;
nextValueTrimmed = nextOrToken.getValue(); nextValueTrimmed = nextOrToken.getValue();
} else if (nextOr instanceof ReferenceParam) { // } else if (nextOr instanceof ReferenceParam) {
ReferenceParam referenceParam = (ReferenceParam) nextOr; // ReferenceParam referenceParam = (ReferenceParam) nextOr;
nextValueTrimmed = referenceParam.getValue(); // nextValueTrimmed = referenceParam.getValue();
if (nextValueTrimmed.contains("/_history")) { // if (nextValueTrimmed.contains("/_history")) {
nextValueTrimmed = nextValueTrimmed.substring(0, nextValueTrimmed.indexOf("/_history")); // nextValueTrimmed = nextValueTrimmed.substring(0, nextValueTrimmed.indexOf("/_history"));
} // }
} else { } else {
throw new IllegalArgumentException(Msg.code(1088) + "Unsupported full-text param type: " + nextOr.getClass()); 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) { public void addReferenceUnchainedSearch(String theSearchParamName, List<List<IQueryParameterType>> theReferenceAndOrTerms) {
String fieldPath = SEARCH_PARAM_ROOT + "." + theSearchParamName + ".reference.value"; addAndOrSearchClauses(theSearchParamName, theReferenceAndOrTerms, this::addReferenceOrClauses);
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));
}
} }
/** /**
@ -476,29 +468,52 @@ public class ExtendedLuceneClauseBuilder {
* Differences with DB search: * Differences with DB search:
* _ is not all-normalized-or-all-not. Each parameter is applied on quantity or normalized quantity depending on UCUM fitness * _ 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 * _ 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) { public void addQuantityUnmodifiedSearch(String theSearchParamName, List<List<IQueryParameterType>> theQuantityAndOrTerms) {
addAndOrSearchClauses(theSearchParamName, theQuantityAndOrTerms, this::addQuantityOrClauses);
}
for (List<IQueryParameterType> nextAnd : theQuantityAndOrTerms) {
BooleanPredicateClausesStep<?> quantityTerms = myPredicateFactory.bool(); private BooleanPredicateClausesStep<?> addReferenceOrClauses(String theSearchParamName, IQueryParameterType theParamType) {
quantityTerms.minimumShouldMatchNumber(1); 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) { for (IQueryParameterType paramType : nextAnd) {
BooleanPredicateClausesStep<?> orTerms = theAddOrClausesFunction.apply(theSearchParamName, paramType);
terms.should(orTerms);
}
myRootClause.must(terms);
}
}
private BooleanPredicateClausesStep<?> addQuantityOrClauses(String theSearchParamName, IQueryParameterType theParamType) {
BooleanPredicateClausesStep<?> orQuantityTerms = myPredicateFactory.bool(); BooleanPredicateClausesStep<?> orQuantityTerms = myPredicateFactory.bool();
addQuantityOrClauses(theSearchParamName, paramType, orQuantityTerms);
quantityTerms.should(orQuantityTerms);
}
myRootClause.must(quantityTerms);
}
}
private void addQuantityOrClauses(String theSearchParamName,
IQueryParameterType theParamType, BooleanPredicateClausesStep<?> theQuantityTerms) {
QuantityParam qtyParam = QuantityParam.toQuantityParam(theParamType); QuantityParam qtyParam = QuantityParam.toQuantityParam(theParamType);
ParamPrefixEnum activePrefix = qtyParam.getPrefix() == null ? ParamPrefixEnum.EQUAL : qtyParam.getPrefix(); ParamPrefixEnum activePrefix = qtyParam.getPrefix() == null ? ParamPrefixEnum.EQUAL : qtyParam.getPrefix();
@ -508,28 +523,36 @@ public class ExtendedLuceneClauseBuilder {
QuantityParam canonicalQty = UcumServiceUtil.toCanonicalQuantityOrNull(qtyParam); QuantityParam canonicalQty = UcumServiceUtil.toCanonicalQuantityOrNull(qtyParam);
if (canonicalQty != null) { if (canonicalQty != null) {
String valueFieldPath = fieldPath + "." + QTY_VALUE_NORM; String valueFieldPath = fieldPath + "." + QTY_VALUE_NORM;
setPrefixedQuantityPredicate(theQuantityTerms, activePrefix, canonicalQty, valueFieldPath); setPrefixedQuantityPredicate(orQuantityTerms, activePrefix, canonicalQty, valueFieldPath);
theQuantityTerms.must(myPredicateFactory.match() orQuantityTerms.must(myPredicateFactory.match()
.field(fieldPath + "." + QTY_CODE_NORM) .field(fieldPath + "." + QTY_CODE_NORM)
.matching(canonicalQty.getUnits())); .matching(canonicalQty.getUnits()));
return; return orQuantityTerms;
} }
} }
// not NORMALIZED_QUANTITY_SEARCH_SUPPORTED or non-canonicalizable parameter // not NORMALIZED_QUANTITY_SEARCH_SUPPORTED or non-canonicalizable parameter
String valueFieldPath = fieldPath + "." + QTY_VALUE; addQuantityTerms(orQuantityTerms, activePrefix, qtyParam, fieldPath);
setPrefixedQuantityPredicate(theQuantityTerms, activePrefix, qtyParam, valueFieldPath); return orQuantityTerms;
if ( isNotBlank(qtyParam.getSystem()) ) {
theQuantityTerms.must(
myPredicateFactory.match()
.field(fieldPath + "." + QTY_SYSTEM).matching(qtyParam.getSystem()) );
} }
if ( isNotBlank(qtyParam.getUnits()) ) {
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( theQuantityTerms.must(
myPredicateFactory.match() myPredicateFactory.match()
.field(fieldPath + "." + QTY_CODE).matching(qtyParam.getUnits()) ); .field(theFieldPath + "." + SYSTEM).matching(theQtyParam.getSystem()) );
}
if ( isNotBlank(theQtyParam.getUnits()) ) {
theQuantityTerms.must(
myPredicateFactory.match()
.field(theFieldPath + "." + CODE).matching(theQtyParam.getUnits()) );
} }
} }

View File

@ -33,8 +33,6 @@ import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import static org.hl7.fhir.r4.model.Observation.SP_VALUE_QUANTITY;
public class HibernateSearchIndexWriter { public class HibernateSearchIndexWriter {
private static final Logger ourLog = LoggerFactory.getLogger(HibernateSearchIndexWriter.class); private static final Logger ourLog = LoggerFactory.getLogger(HibernateSearchIndexWriter.class);
public static final String IDX_STRING_NORMALIZED = "norm"; 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 SEARCH_PARAM_ROOT = "sp";
public static final String QTY_PARAM_NAME = "quantity"; public static final String QTY_PARAM_NAME = "quantity";
public static final String QTY_CODE = "code"; public static final String CODE = "code";
public static final String QTY_SYSTEM = "system"; public static final String SYSTEM = "system";
public static final String QTY_VALUE = "value"; public static final String VALUE = "value";
public static final String QTY_CODE_NORM = "code-norm"; public static final String QTY_CODE_NORM = "code-norm";
public static final String QTY_VALUE_NORM = "value-norm"; public static final String QTY_VALUE_NORM = "value-norm";
@ -127,9 +125,9 @@ public class HibernateSearchIndexWriter {
DocumentElement nestedQtyNode = nestedSpNode.addObject(QTY_PARAM_NAME); DocumentElement nestedQtyNode = nestedSpNode.addObject(QTY_PARAM_NAME);
ourLog.trace("Adding Search Param Quantity: {} -- {}", theSearchParam, theValue); ourLog.trace("Adding Search Param Quantity: {} -- {}", theSearchParam, theValue);
nestedQtyNode.addValue(QTY_CODE, theValue.getCode()); nestedQtyNode.addValue(CODE, theValue.getCode());
nestedQtyNode.addValue(QTY_SYSTEM, theValue.getSystem()); nestedQtyNode.addValue(SYSTEM, theValue.getSystem());
nestedQtyNode.addValue(QTY_VALUE, theValue.getValue()); nestedQtyNode.addValue(VALUE, theValue.getValue());
if ( ! myModelConfig.getNormalizedQuantitySearchLevel().storageOrSearchSupported()) { return; } 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_EXACT;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.IDX_STRING_NORMALIZED; 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.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_CODE_NORM;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_SYSTEM; import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.SYSTEM;
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_VALUE; 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.QTY_VALUE_NORM;
/** /**
@ -178,9 +178,9 @@ public class SearchParamTextPropertyBinder implements PropertyBinder, PropertyBr
//quantity //quantity
String quantityPathGlob = "*.quantity"; String quantityPathGlob = "*.quantity";
nestedSpField.objectFieldTemplate("quantityTemplate", ObjectStructure.FLATTENED).matchingPathGlob(quantityPathGlob); nestedSpField.objectFieldTemplate("quantityTemplate", ObjectStructure.FLATTENED).matchingPathGlob(quantityPathGlob);
nestedSpField.fieldTemplate(QTY_SYSTEM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_SYSTEM); nestedSpField.fieldTemplate(SYSTEM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + SYSTEM);
nestedSpField.fieldTemplate(QTY_CODE, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_CODE); nestedSpField.fieldTemplate(CODE, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + CODE);
nestedSpField.fieldTemplate(QTY_VALUE, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_VALUE); nestedSpField.fieldTemplate(VALUE, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + VALUE);
nestedSpField.fieldTemplate(QTY_CODE_NORM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_CODE_NORM); nestedSpField.fieldTemplate(QTY_CODE_NORM, keywordFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_CODE_NORM);
nestedSpField.fieldTemplate(QTY_VALUE_NORM, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_VALUE_NORM); nestedSpField.fieldTemplate(QTY_VALUE_NORM, bigDecimalFieldType).matchingPathGlob(quantityPathGlob + "." + QTY_VALUE_NORM);