Freetext search source parameter support (#3641)
* Handle uri, _tag, _security and _profile freetext search parameters * Fix combined 'and' and 'or' clauses and add tests Use collection instead of typed array to eliminate warnings * Eliminate predicate nesting level * Implement _source freetext search * Implement suggestions Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
This commit is contained in:
parent
fb6e5cf6ce
commit
4eb099b900
|
@ -35,6 +35,7 @@ import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.util.DateUtils;
|
import ca.uhn.fhir.util.DateUtils;
|
||||||
import ca.uhn.fhir.util.StringUtil;
|
import ca.uhn.fhir.util.StringUtil;
|
||||||
|
@ -50,6 +51,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -70,6 +72,7 @@ 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.QTY_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 ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.URI_VALUE;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class ExtendedLuceneClauseBuilder {
|
public class ExtendedLuceneClauseBuilder {
|
||||||
|
@ -623,4 +626,18 @@ public class ExtendedLuceneClauseBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addUriUnmodifiedSearch(String theParamName, List<List<IQueryParameterType>> theUriUnmodifiedAndOrTerms) {
|
||||||
|
for (List<IQueryParameterType> nextAnd : theUriUnmodifiedAndOrTerms) {
|
||||||
|
|
||||||
|
List<PredicateFinalStep> predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
for (IQueryParameterType paramType : nextAnd) {
|
||||||
|
predicates.add(myPredicateFactory.match()
|
||||||
|
.field(String.join(".", SEARCH_PARAM_ROOT, theParamName, URI_VALUE))
|
||||||
|
.matching( ((UriParam) paramType).getValue() ));
|
||||||
|
}
|
||||||
|
|
||||||
|
myRootClause.must(orPredicateOrSingle(predicates));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,16 @@ package ca.uhn.fhir.jpa.dao.search;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||||
import ca.uhn.fhir.jpa.model.search.ExtendedLuceneIndexData;
|
import ca.uhn.fhir.jpa.model.search.ExtendedLuceneIndexData;
|
||||||
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
|
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
|
||||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||||
|
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
|
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
|
||||||
|
import ca.uhn.fhir.util.MetaUtil;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||||
|
@ -42,7 +44,8 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract search params for advanced lucene indexing.
|
* Extract search params for advanced lucene indexing.
|
||||||
|
@ -91,6 +94,20 @@ public class ExtendedLuceneIndexExtractor {
|
||||||
theNewParams.myQuantityParams.forEach(nextParam ->
|
theNewParams.myQuantityParams.forEach(nextParam ->
|
||||||
retVal.addQuantityIndexData(nextParam.getParamName(), nextParam.getUnits(), nextParam.getSystem(), nextParam.getValue().doubleValue()));
|
retVal.addQuantityIndexData(nextParam.getParamName(), nextParam.getUnits(), nextParam.getSystem(), nextParam.getValue().doubleValue()));
|
||||||
|
|
||||||
|
theResource.getMeta().getTag().forEach(tag ->
|
||||||
|
retVal.addTokenIndexData("_tag", new CodingDt(tag.getSystem(), tag.getCode()).setDisplay(tag.getDisplay())));
|
||||||
|
|
||||||
|
theResource.getMeta().getSecurity().forEach(sec ->
|
||||||
|
retVal.addTokenIndexData("_security", new CodingDt(sec.getSystem(), sec.getCode()).setDisplay(sec.getDisplay())));
|
||||||
|
|
||||||
|
theResource.getMeta().getProfile().forEach(prof ->
|
||||||
|
retVal.addUriIndexData("_profile", prof.getValue()));
|
||||||
|
|
||||||
|
String source = MetaUtil.getSource(myContext, theResource.getMeta());
|
||||||
|
if (isNotBlank(source)) {
|
||||||
|
retVal.addUriIndexData("_source", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!theNewParams.myLinks.isEmpty()) {
|
if (!theNewParams.myLinks.isEmpty()) {
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
@ -48,7 +49,7 @@ public class ExtendedLuceneSearchBuilder {
|
||||||
/**
|
/**
|
||||||
* These params have complicated semantics, or are best resolved at the JPA layer for now.
|
* These params have complicated semantics, or are best resolved at the JPA layer for now.
|
||||||
*/
|
*/
|
||||||
public static final Set<String> ourUnsafeSearchParmeters = Sets.newHashSet("_id", "_tag", "_meta");
|
public static final Set<String> ourUnsafeSearchParmeters = Sets.newHashSet("_id", "_meta");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Are any of the queries supported by our indexing?
|
* Are any of the queries supported by our indexing?
|
||||||
|
@ -111,6 +112,11 @@ public class ExtendedLuceneSearchBuilder {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
} else if (param instanceof UriParam) {
|
||||||
|
if (EMPTY_MODIFIER.equals(modifier)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -137,8 +143,8 @@ public class ExtendedLuceneSearchBuilder {
|
||||||
|
|
||||||
List<List<IQueryParameterType>> tokenUnmodifiedAndOrTerms = theParams.removeByNameUnmodified(nextParam);
|
List<List<IQueryParameterType>> tokenUnmodifiedAndOrTerms = theParams.removeByNameUnmodified(nextParam);
|
||||||
builder.addTokenUnmodifiedSearch(nextParam, tokenUnmodifiedAndOrTerms);
|
builder.addTokenUnmodifiedSearch(nextParam, tokenUnmodifiedAndOrTerms);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STRING:
|
case STRING:
|
||||||
List<List<IQueryParameterType>> stringTextAndOrTerms = theParams.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_TOKEN_TEXT);
|
List<List<IQueryParameterType>> stringTextAndOrTerms = theParams.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_TOKEN_TEXT);
|
||||||
builder.addStringTextSearch(nextParam, stringTextAndOrTerms);
|
builder.addStringTextSearch(nextParam, stringTextAndOrTerms);
|
||||||
|
@ -168,6 +174,10 @@ public class ExtendedLuceneSearchBuilder {
|
||||||
builder.addDateUnmodifiedSearch(nextParam, dateAndOrTerms);
|
builder.addDateUnmodifiedSearch(nextParam, dateAndOrTerms);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case URI:
|
||||||
|
List<List<IQueryParameterType>> uriUnmodifiedAndOrTerms = theParams.removeByNameUnmodified(nextParam);
|
||||||
|
builder.addUriUnmodifiedSearch(nextParam, uriUnmodifiedAndOrTerms);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// ignore unsupported param types/modifiers. They will be processed up in SearchBuilder.
|
// ignore unsupported param types/modifiers. They will be processed up in SearchBuilder.
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ public class ExtendedLuceneIndexData {
|
||||||
final SetMultimap<String, String> mySearchParamStrings = HashMultimap.create();
|
final SetMultimap<String, String> mySearchParamStrings = HashMultimap.create();
|
||||||
final SetMultimap<String, IBaseCoding> mySearchParamTokens = HashMultimap.create();
|
final SetMultimap<String, IBaseCoding> mySearchParamTokens = HashMultimap.create();
|
||||||
final SetMultimap<String, String> mySearchParamLinks = HashMultimap.create();
|
final SetMultimap<String, String> mySearchParamLinks = HashMultimap.create();
|
||||||
|
final SetMultimap<String, String> mySearchParamUri = HashMultimap.create();
|
||||||
final SetMultimap<String, DateSearchIndexData> mySearchParamDates = HashMultimap.create();
|
final SetMultimap<String, DateSearchIndexData> mySearchParamDates = HashMultimap.create();
|
||||||
final SetMultimap<String, QuantitySearchIndexData> mySearchParamQuantities = HashMultimap.create();
|
final SetMultimap<String, QuantitySearchIndexData> mySearchParamQuantities = HashMultimap.create();
|
||||||
private String myForcedId;
|
private String myForcedId;
|
||||||
|
@ -94,6 +95,7 @@ public class ExtendedLuceneIndexData {
|
||||||
Multimaps.asMap(mySearchParamQuantities).forEach(ifNotContained(indexWriter::writeQuantityIndex));
|
Multimaps.asMap(mySearchParamQuantities).forEach(ifNotContained(indexWriter::writeQuantityIndex));
|
||||||
// TODO MB Use RestSearchParameterTypeEnum to define templates.
|
// TODO MB Use RestSearchParameterTypeEnum to define templates.
|
||||||
mySearchParamDates.forEach(ifNotContained(indexWriter::writeDateIndex));
|
mySearchParamDates.forEach(ifNotContained(indexWriter::writeDateIndex));
|
||||||
|
Multimaps.asMap(mySearchParamUri).forEach(ifNotContained(indexWriter::writeUriIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addStringIndexData(String theSpName, String theText) {
|
public void addStringIndexData(String theSpName, String theText) {
|
||||||
|
@ -115,6 +117,10 @@ public class ExtendedLuceneIndexData {
|
||||||
mySearchParamTokens.put(theSpName, theNextValue);
|
mySearchParamTokens.put(theSpName, theNextValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addUriIndexData(String theSpName, String theValue) {
|
||||||
|
mySearchParamUri.put(theSpName, theValue);
|
||||||
|
}
|
||||||
|
|
||||||
public void addResourceLinkIndexData(String theSpName, String theTargetResourceId) {
|
public void addResourceLinkIndexData(String theSpName, String theTargetResourceId) {
|
||||||
mySearchParamLinks.put(theSpName, theTargetResourceId);
|
mySearchParamLinks.put(theSpName, theTargetResourceId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
@ -50,6 +48,8 @@ public class HibernateSearchIndexWriter {
|
||||||
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";
|
||||||
|
|
||||||
|
public static final String URI_VALUE = "uri-value";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
final HibernateSearchElementCache myNodeCache;
|
final HibernateSearchElementCache myNodeCache;
|
||||||
|
@ -151,5 +151,11 @@ public class HibernateSearchIndexWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void writeUriIndex(String theParamName, Collection<String> theUriValueCollection) {
|
||||||
|
DocumentElement uriNode = myNodeCache.getObjectElement(SEARCH_PARAM_ROOT).addObject(theParamName);
|
||||||
|
for (String uriSearchIndexValue : theUriValueCollection) {
|
||||||
|
ourLog.trace("Adding Search Param Uri: {} -- {}", theParamName, uriSearchIndexValue);
|
||||||
|
uriNode.addValue(URI_VALUE, uriSearchIndexValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_CODE_N
|
||||||
import static ca.uhn.fhir.jpa.model.search.HibernateSearchIndexWriter.QTY_SYSTEM;
|
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.QTY_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.URI_VALUE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows hibernate search to index
|
* Allows hibernate search to index
|
||||||
|
@ -175,6 +176,9 @@ public class SearchParamTextPropertyBinder implements PropertyBinder, PropertyBr
|
||||||
// reference
|
// reference
|
||||||
spfield.fieldTemplate("reference-value", keywordFieldType).matchingPathGlob("*.reference.value").multiValued();
|
spfield.fieldTemplate("reference-value", keywordFieldType).matchingPathGlob("*.reference.value").multiValued();
|
||||||
|
|
||||||
|
// uri
|
||||||
|
spfield.fieldTemplate("uriValueTemplate", keywordFieldType).matchingPathGlob("*." + URI_VALUE).multiValued();
|
||||||
|
|
||||||
//quantity
|
//quantity
|
||||||
String quantityPathGlob = "*.quantity";
|
String quantityPathGlob = "*.quantity";
|
||||||
nestedSpField.objectFieldTemplate("quantityTemplate", ObjectStructure.FLATTENED).matchingPathGlob(quantityPathGlob);
|
nestedSpField.objectFieldTemplate("quantityTemplate", ObjectStructure.FLATTENED).matchingPathGlob(quantityPathGlob);
|
||||||
|
|
|
@ -140,7 +140,7 @@ public class MatchUrlService {
|
||||||
type.setValuesAsQueryTokens(myFhirContext, nextParamName, (paramList));
|
type.setValuesAsQueryTokens(myFhirContext, nextParamName, (paramList));
|
||||||
paramMap.add(nextParamName, type);
|
paramMap.add(nextParamName, type);
|
||||||
} else if (Constants.PARAM_SOURCE.equals(nextParamName)) {
|
} else if (Constants.PARAM_SOURCE.equals(nextParamName)) {
|
||||||
IQueryParameterAnd<?> param = JpaParamUtil.parseQueryParams(myFhirContext, RestSearchParameterTypeEnum.TOKEN, nextParamName, paramList);
|
IQueryParameterAnd<?> param = JpaParamUtil.parseQueryParams(myFhirContext, RestSearchParameterTypeEnum.URI, nextParamName, paramList);
|
||||||
paramMap.add(nextParamName, param);
|
paramMap.add(nextParamName, param);
|
||||||
} else if (JpaConstants.PARAM_DELETE_EXPUNGE.equals(nextParamName)) {
|
} else if (JpaConstants.PARAM_DELETE_EXPUNGE.equals(nextParamName)) {
|
||||||
paramMap.setDeleteExpunge(true);
|
paramMap.setDeleteExpunge(true);
|
||||||
|
|
|
@ -68,7 +68,6 @@ import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.hl7.fhir.r4.model.ValueSet;
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
@ -92,6 +91,7 @@ import javax.persistence.EntityManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -597,8 +597,8 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testStringCaseFolding() {
|
void testStringCaseFolding() {
|
||||||
IIdType kelly = myTestDataBuilder.createPatient(myTestDataBuilder.withGiven("Kelly"));
|
IIdType kelly = myTestDataBuilder.createPatient(asArray(myTestDataBuilder.withGiven("Kelly")));
|
||||||
IIdType keely = myTestDataBuilder.createPatient(myTestDataBuilder.withGiven("Kélly"));
|
IIdType keely = myTestDataBuilder.createPatient(asArray(myTestDataBuilder.withGiven("Kélly")));
|
||||||
|
|
||||||
// un-modified, :contains, and :text are all ascii normalized, and case-folded
|
// un-modified, :contains, and :text are all ascii normalized, and case-folded
|
||||||
myTestDaoSearch.assertSearchFinds("lowercase matches capitalized", "/Patient?name=kelly", kelly, keely);
|
myTestDaoSearch.assertSearchFinds("lowercase matches capitalized", "/Patient?name=kelly", kelly, keely);
|
||||||
|
@ -915,7 +915,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
@Test
|
@Test
|
||||||
public void simpleTokenSkipsSql() {
|
public void simpleTokenSkipsSql() {
|
||||||
|
|
||||||
IIdType id = myTestDataBuilder.createObservation(myTestDataBuilder.withObservationCode("http://example.com/", "theCode"));
|
IIdType id = myTestDataBuilder.createObservation(List.of(myTestDataBuilder.withObservationCode("http://example.com/", "theCode")));
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
|
|
||||||
List<String> ids = myTestDaoSearch.searchForIds("Observation?code=theCode");
|
List<String> ids = myTestDaoSearch.searchForIds("Observation?code=theCode");
|
||||||
|
@ -929,7 +929,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
@Test
|
@Test
|
||||||
public void sortStillRequiresSql() {
|
public void sortStillRequiresSql() {
|
||||||
|
|
||||||
IIdType id = myTestDataBuilder.createObservation(myTestDataBuilder.withObservationCode("http://example.com/", "theCode"));
|
IIdType id = myTestDataBuilder.createObservation(List.of(myTestDataBuilder.withObservationCode("http://example.com/", "theCode")));
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
|
|
||||||
List<String> ids = myTestDaoSearch.searchForIds("Observation?code=theCode&_sort=code");
|
List<String> ids = myTestDaoSearch.searchForIds("Observation?code=theCode&_sort=code");
|
||||||
|
@ -944,7 +944,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
@Test
|
@Test
|
||||||
public void deletedResourceNotFound() {
|
public void deletedResourceNotFound() {
|
||||||
|
|
||||||
IIdType id = myTestDataBuilder.createObservation(myTestDataBuilder.withObservationCode("http://example.com/", "theCode"));
|
IIdType id = myTestDataBuilder.createObservation(List.of(myTestDataBuilder.withObservationCode("http://example.com/", "theCode")));
|
||||||
myObservationDao.delete(id);
|
myObservationDao.delete(id);
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
|
|
||||||
|
@ -958,9 +958,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void forcedIdSurvivesWithNoSql() {
|
public void forcedIdSurvivesWithNoSql() {
|
||||||
IIdType id = myTestDataBuilder.createObservation(
|
IIdType id = myTestDataBuilder.createObservation(List.of(
|
||||||
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
myTestDataBuilder.withId("forcedid"));
|
myTestDataBuilder.withId("forcedid")));
|
||||||
assertThat(id.getIdPart(), equalTo("forcedid"));
|
assertThat(id.getIdPart(), equalTo("forcedid"));
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
|
|
||||||
|
@ -981,9 +981,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void tagsSurvive() {
|
public void tagsSurvive() {
|
||||||
IIdType id = myTestDataBuilder.createObservation(
|
IIdType id = myTestDataBuilder.createObservation(List.of(
|
||||||
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
myTestDataBuilder.withTag("http://example.com", "aTag"));
|
myTestDataBuilder.withTag("http://example.com", "aTag")));
|
||||||
|
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
List<IBaseResource> observations = myTestDaoSearch.searchForResources("Observation?code=theCode");
|
List<IBaseResource> observations = myTestDaoSearch.searchForResources("Observation?code=theCode");
|
||||||
|
@ -1476,10 +1476,10 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
// myLogbackLevelOverrideExtension.setLogLevel(DaoTestDataBuilder.class, Level.DEBUG);
|
// myLogbackLevelOverrideExtension.setLogLevel(DaoTestDataBuilder.class, Level.DEBUG);
|
||||||
|
|
||||||
// this configuration must generate a combo-value-quantity entry with both quantity objects
|
// this configuration must generate a combo-value-quantity entry with both quantity objects
|
||||||
myResourceId = myTestDataBuilder.createObservation(
|
myResourceId = myTestDataBuilder.createObservation(List.of(
|
||||||
myTestDataBuilder.withQuantityAtPath("valueQuantity", 0.02, UCUM_CODESYSTEM_URL, "10*6/L"),
|
myTestDataBuilder.withQuantityAtPath("valueQuantity", 0.02, UCUM_CODESYSTEM_URL, "10*6/L"),
|
||||||
myTestDataBuilder.withQuantityAtPath("component.valueQuantity", 0.06, UCUM_CODESYSTEM_URL, "10*6/L")
|
myTestDataBuilder.withQuantityAtPath("component.valueQuantity", 0.06, UCUM_CODESYSTEM_URL, "10*6/L")
|
||||||
);
|
));
|
||||||
|
|
||||||
// myLogbackLevelOverrideExtension.resetLevel(DaoTestDataBuilder.class);
|
// myLogbackLevelOverrideExtension.resetLevel(DaoTestDataBuilder.class);
|
||||||
|
|
||||||
|
@ -1532,16 +1532,148 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private IIdType withObservationWithValueQuantity(double theValue) {
|
private IIdType withObservationWithValueQuantity(double theValue) {
|
||||||
myResourceId = myTestDataBuilder.createObservation(myTestDataBuilder.withElementAt("valueQuantity",
|
myResourceId = myTestDataBuilder.createObservation(List.of(myTestDataBuilder.withElementAt("valueQuantity",
|
||||||
myTestDataBuilder.withPrimitiveAttribute("value", theValue),
|
myTestDataBuilder.withPrimitiveAttribute("value", theValue),
|
||||||
myTestDataBuilder.withPrimitiveAttribute("system", UCUM_CODESYSTEM_URL),
|
myTestDataBuilder.withPrimitiveAttribute("system", UCUM_CODESYSTEM_URL),
|
||||||
myTestDataBuilder.withPrimitiveAttribute("code", "mm[Hg]")
|
myTestDataBuilder.withPrimitiveAttribute("code", "mm[Hg]")
|
||||||
));
|
)));
|
||||||
return myResourceId;
|
return myResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
public class TagTypesSearch {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void enableResourceStorage() {
|
||||||
|
myDaoConfig.setStoreResourceInLuceneIndex(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void resetResourceStorage() {
|
||||||
|
myDaoConfig.setStoreResourceInLuceneIndex(new DaoConfig().isStoreResourceInLuceneIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tagTagSearch() {
|
||||||
|
String id = myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withTag("http://example.com", "aTag"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_tag=http://example.com|aTag");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tagSecuritySearch() {
|
||||||
|
String id = myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withSecurity("http://example.com", "security-label"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_security=http://example.com|security-label");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tokenAndOrCombinedSearch() {
|
||||||
|
String id = myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withSecurity("http://example.com", "security-label"),
|
||||||
|
myTestDataBuilder.withSecurity("http://example.com", "other-security-label"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation" +
|
||||||
|
"?_security=http://example.com|non-existing-security-label,http://example.com|security-label" +
|
||||||
|
"&_security=http://example.com|other-non-existing-security-label,http://example.com|other-security-label");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tokenAndOrCombinedSearch_failing_and_with_multiple_or() {
|
||||||
|
myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withTag("http://example.com", "aTag"),
|
||||||
|
myTestDataBuilder.withTag("http://example.com", "anotherTag"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation" +
|
||||||
|
"?_tag=http://example.com|not-existing-tag,http://example.com|one-more-not-existing-tag" +
|
||||||
|
"&_tag=http://example.com|other-not-existing-tag,http://example.com|anotherTag");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tokenAndOrCombinedSearch_failing_and_with_single_or() {
|
||||||
|
myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withTag("http://example.com", "aTag"),
|
||||||
|
myTestDataBuilder.withTag("http://example.com", "anotherTag"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation" +
|
||||||
|
"?_tag=http://example.com|not-existing-tag" +
|
||||||
|
"&_tag=http://example.com|other-not-existing-tag,http://example.com|anotherTag");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void uriAndOrCombinedSearch() {
|
||||||
|
String id = myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withProfile("http://example.com/theProfile"),
|
||||||
|
myTestDataBuilder.withProfile("http://example.com/anotherProfile"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation" +
|
||||||
|
"?_profile=http://example.com/non-existing-profile,http://example.com/theProfile" +
|
||||||
|
"&_profile=http://example.com/other-non-existing-profile,http://example.com/anotherProfile");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tagProfileSearch() {
|
||||||
|
String id = myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withProfile("http://example.com/theProfile"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_profile=http://example.com/theProfile");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tagSourceSearch() {
|
||||||
|
String id = myTestDataBuilder.createObservation(List.of(
|
||||||
|
myTestDataBuilder.withObservationCode("http://example.com/", "theCode"),
|
||||||
|
myTestDataBuilder.withSource(myFhirContext, "http://example.com/theSource"))).getIdPart();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_source=http://example.com/theSource");
|
||||||
|
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||||
|
assertThat(allIds, contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||||
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.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
|
import ca.uhn.fhir.util.MetaUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
|
@ -34,6 +35,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -145,10 +147,26 @@ public interface ITestDataBuilder {
|
||||||
return t -> t.getMeta().addTag().setSystem(theSystem).setCode(theCode).setDisplay(theCode);
|
return t -> t.getMeta().addTag().setSystem(theSystem).setCode(theCode).setDisplay(theCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default Consumer<IBaseResource> withSecurity(String theSystem, String theCode) {
|
||||||
|
return t -> t.getMeta().addSecurity().setSystem(theSystem).setCode(theCode).setDisplay(theCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Consumer<IBaseResource> withProfile(String theProfile) {
|
||||||
|
return t -> t.getMeta().addProfile(theProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Consumer<IBaseResource> withSource(FhirContext theContext, String theSource) {
|
||||||
|
return t -> MetaUtil.setSource(theContext, t.getMeta(), theSource);
|
||||||
|
}
|
||||||
|
|
||||||
default IIdType createObservation(Consumer<IBaseResource>... theModifiers) {
|
default IIdType createObservation(Consumer<IBaseResource>... theModifiers) {
|
||||||
return createResource("Observation", theModifiers);
|
return createResource("Observation", theModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default IIdType createObservation(Collection<Consumer<IBaseResource>> theModifiers) {
|
||||||
|
return createResource("Observation", theModifiers.toArray(new Consumer[0]));
|
||||||
|
}
|
||||||
|
|
||||||
default IBaseResource buildPatient(Consumer<IBaseResource>... theModifiers) {
|
default IBaseResource buildPatient(Consumer<IBaseResource>... theModifiers) {
|
||||||
return buildResource("Patient", theModifiers);
|
return buildResource("Patient", theModifiers);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue