3790 remove legacy search builder (#3791)
* Remove legacy SB * Add changelog * Fix more messages * Fix missed hapi message * Fix up more messages
This commit is contained in:
parent
94a1cda512
commit
05ebf0286d
|
@ -122,19 +122,19 @@ ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destina
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
||||||
ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidInclude=Invalid {0} parameter value: "{1}". {2}
|
ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidInclude=Invalid {0} parameter value: "{1}". {2}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
|
ca.uhn.fhir.jpa.search.builder.QueryStack.sourceParamDisabled=The _source parameter is disabled on this server
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
|
ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
|
ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.matchesFound=Matches found
|
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.matchesFound=Matches found
|
||||||
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.noMatchesFound=No Matches found
|
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.noMatchesFound=No Matches found
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
|
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter
|
ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter
|
||||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken.textModifierDisabledForServer=The :text modifier is disabled on this server
|
ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForServer=The :text modifier is disabled on this server
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.successMsg=Cascaded delete to {0} resources: {1}
|
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.successMsg=Cascaded delete to {0} resources: {1}
|
||||||
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascading deletes are not active for this request. You can enable cascading deletes by using the "_cascade=delete" URL parameter.
|
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascading deletes are not active for this request. You can enable cascading deletes by using the "_cascade=delete" URL parameter.
|
||||||
|
@ -166,8 +166,8 @@ ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionId=Unknown p
|
||||||
ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionName=Unknown partition name: {0}
|
ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionName=Unknown partition name: {0}
|
||||||
|
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
|
ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
|
||||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
|
ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidResourceType=Invalid/unsupported resource type: "{0}"
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request
|
ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: change
|
||||||
|
issue: 3790
|
||||||
|
title: "The Legacy Search Builder has been removed in favour of the new Search Builder. The DaoConfig will retain its setter for deprecation purposes,
|
||||||
|
but that will also be removed after the next release."
|
|
@ -0,0 +1,51 @@
|
||||||
|
When upgrading to this release, there are a few important notes to be aware of:
|
||||||
|
|
||||||
|
* This release removes the Legacy Search Builder. If you upgrade to this release, the new Search Builder will automatically be used.
|
||||||
|
|
||||||
|
* This release will break existing implementations which use the subscription delete feature. The place where the extension needs to be installed on the Subscription resource has now changed. While it used to be on the top-level Subscription resource, the extension should now be added to the Subscription's `channel` element.
|
||||||
|
|
||||||
|
Here is how the subscription should have looked before:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"resourceType": "Subscription",
|
||||||
|
"id": "1",
|
||||||
|
"status": "active",
|
||||||
|
"reason": "Monitor resource persistence events",
|
||||||
|
"criteria": "Patient",
|
||||||
|
"channel": {
|
||||||
|
|
||||||
|
"type": "rest-hook",
|
||||||
|
"payload": "application/json"
|
||||||
|
},
|
||||||
|
"extension": [
|
||||||
|
{
|
||||||
|
"url": "http://hapifhir.io/fhir/StructureDefinition/subscription-send-delete-messages",
|
||||||
|
"valueBoolean": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And here is how it should now look:
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"resourceType": "Subscription",
|
||||||
|
"id": "1",
|
||||||
|
"status": "active",
|
||||||
|
"reason": "Monitor resource persistence events",
|
||||||
|
"criteria": "Patient",
|
||||||
|
"channel": {
|
||||||
|
"extension": [
|
||||||
|
{
|
||||||
|
"url": "http://hapifhir.io/fhir/StructureDefinition/subscription-send-delete-messages",
|
||||||
|
"valueBoolean": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "rest-hook",
|
||||||
|
"payload": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.dao.HistoryBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
|
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
|
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
|
||||||
|
@ -46,18 +45,6 @@ import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
|
||||||
import ca.uhn.fhir.jpa.dao.index.JpaIdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.JpaIdHelperService;
|
||||||
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
||||||
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkExpandSvc;
|
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkExpandSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderCoords;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderDate;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderFactory;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderNumber;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderQuantity;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderResourceId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderString;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderTag;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderUri;
|
|
||||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictFinderService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictFinderService;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
|
@ -627,65 +614,6 @@ public class JpaConfig {
|
||||||
return new UriPredicateBuilder(theSearchBuilder);
|
return new UriPredicateBuilder(theSearchBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderCoords newPredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderCoords(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderDate newPredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderDate(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderNumber newPredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderNumber(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderQuantity newPredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderQuantity(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderReference newPredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
|
||||||
return new PredicateBuilderReference(theSearchBuilder, thePredicateBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderResourceId newPredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderResourceId(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderString newPredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderString(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderTag newPredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderTag(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderToken newPredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
|
||||||
return new PredicateBuilderToken(theSearchBuilder, thePredicateBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
public PredicateBuilderUri newPredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return new PredicateBuilderUri(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
|
@ -696,9 +624,6 @@ public class JpaConfig {
|
||||||
@Bean(name = SEARCH_BUILDER)
|
@Bean(name = SEARCH_BUILDER)
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
public ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType, DaoConfig theDaoConfig) {
|
public ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType, DaoConfig theDaoConfig) {
|
||||||
if (theDaoConfig.isUseLegacySearchBuilder()) {
|
|
||||||
return new LegacySearchBuilder(theDao, theResourceName, theResourceType);
|
|
||||||
}
|
|
||||||
return new SearchBuilder(theDao, theResourceName, theResourceType);
|
return new SearchBuilder(theDao, theResourceName, theResourceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,11 +715,6 @@ public class JpaConfig {
|
||||||
return new CacheWarmingSvcImpl();
|
return new CacheWarmingSvcImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PredicateBuilderFactory predicateBuilderFactory(ApplicationContext theApplicationContext) {
|
|
||||||
return new PredicateBuilderFactory(theApplicationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IndexNamePrefixLayoutStrategy indexLayoutStrategy() {
|
public IndexNamePrefixLayoutStrategy indexLayoutStrategy() {
|
||||||
return new IndexNamePrefixLayoutStrategy();
|
return new IndexNamePrefixLayoutStrategy();
|
||||||
|
|
|
@ -50,7 +50,8 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.dao.LegacySearchBuilder.toPredicateArray;
|
import static ca.uhn.fhir.jpa.search.builder.SearchBuilder.toPredicateArray;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HistoryBuilder is responsible for building history queries
|
* The HistoryBuilder is responsible for building history queries
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,213 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.querystack.QueryStack;
|
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.MathContext;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
abstract class BasePredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(BasePredicateBuilder.class);
|
|
||||||
final CriteriaBuilder myCriteriaBuilder;
|
|
||||||
final QueryStack myQueryStack;
|
|
||||||
final Class<? extends IBaseResource> myResourceType;
|
|
||||||
final String myResourceName;
|
|
||||||
final SearchParameterMap myParams;
|
|
||||||
@Autowired
|
|
||||||
FhirContext myContext;
|
|
||||||
@Autowired
|
|
||||||
DaoConfig myDaoConfig;
|
|
||||||
boolean myDontUseHashesForSearch;
|
|
||||||
@Autowired
|
|
||||||
private PartitionSettings myPartitionSettings;
|
|
||||||
|
|
||||||
BasePredicateBuilder(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
myCriteriaBuilder = theSearchBuilder.getBuilder();
|
|
||||||
myQueryStack = theSearchBuilder.getQueryStack();
|
|
||||||
myResourceType = theSearchBuilder.getResourceType();
|
|
||||||
myResourceName = theSearchBuilder.getResourceName();
|
|
||||||
myParams = theSearchBuilder.getParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
private void postConstruct() {
|
|
||||||
myDontUseHashesForSearch = myDaoConfig.getDisableHashBasedSearches();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateParamMissingForReference(String theResourceName, String theParamName, boolean theMissing, RequestPartitionId theRequestPartitionId) {
|
|
||||||
From<?, SearchParamPresentEntity> paramPresentJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.PRESENCE, null);
|
|
||||||
|
|
||||||
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
|
|
||||||
Long hash = SearchParamPresentEntity.calculateHashPresence(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName, !theMissing);
|
|
||||||
|
|
||||||
List<Predicate> predicates = new ArrayList<>();
|
|
||||||
predicates.add(myCriteriaBuilder.equal(hashPresence, hash));
|
|
||||||
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, paramPresentJoin, predicates);
|
|
||||||
|
|
||||||
myQueryStack.addPredicatesWithImplicitTypeSelection(predicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, From<?, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
|
|
||||||
if (!theRequestPartitionId.isAllPartitions()) {
|
|
||||||
if (theRequestPartitionId.isDefaultPartition()) {
|
|
||||||
myQueryStack.addPredicate(myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue")));
|
|
||||||
} else {
|
|
||||||
myQueryStack.addPredicate(theJoin.get("myPartitionIdValue").in(theRequestPartitionId.getPartitionIds()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
|
|
||||||
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
|
|
||||||
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate, RequestPartitionId theRequestPartitionId) {
|
|
||||||
List<Predicate> andPredicates = new ArrayList<>();
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, theFrom, andPredicates);
|
|
||||||
|
|
||||||
if (myDontUseHashesForSearch) {
|
|
||||||
Predicate resourceTypePredicate = myCriteriaBuilder.equal(theFrom.get("myResourceType"), theResourceName);
|
|
||||||
Predicate paramNamePredicate = myCriteriaBuilder.equal(theFrom.get("myParamName"), theParamName);
|
|
||||||
andPredicates.add(resourceTypePredicate);
|
|
||||||
andPredicates.add(paramNamePredicate);
|
|
||||||
andPredicates.add(thePredicate);
|
|
||||||
} else {
|
|
||||||
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName);
|
|
||||||
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
|
|
||||||
andPredicates.add(hashIdentityPredicate);
|
|
||||||
andPredicates.add(thePredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return myCriteriaBuilder.and(toArray(andPredicates));
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartitionSettings getPartitionSettings() {
|
|
||||||
return myPartitionSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate createPredicateNumeric(String theResourceName,
|
|
||||||
String theParamName,
|
|
||||||
From<?, ? extends BaseResourceIndexedSearchParam> theFrom,
|
|
||||||
CriteriaBuilder builder,
|
|
||||||
IQueryParameterType theParam,
|
|
||||||
ParamPrefixEnum thePrefix,
|
|
||||||
BigDecimal theValue,
|
|
||||||
final Expression<BigDecimal> thePath,
|
|
||||||
String invalidMessageName, RequestPartitionId theRequestPartitionId) {
|
|
||||||
Predicate num;
|
|
||||||
// Per discussions with Grahame Grieve and James Agnew on 11/13/19, modified logic for EQUAL and NOT_EQUAL operators below so as to
|
|
||||||
// use exact value matching. The "fuzz amount" matching is still used with the APPROXIMATE operator.
|
|
||||||
switch (thePrefix) {
|
|
||||||
case GREATERTHAN:
|
|
||||||
num = builder.gt(thePath, theValue);
|
|
||||||
break;
|
|
||||||
case GREATERTHAN_OR_EQUALS:
|
|
||||||
num = builder.ge(thePath, theValue);
|
|
||||||
break;
|
|
||||||
case LESSTHAN:
|
|
||||||
num = builder.lt(thePath, theValue);
|
|
||||||
break;
|
|
||||||
case LESSTHAN_OR_EQUALS:
|
|
||||||
num = builder.le(thePath, theValue);
|
|
||||||
break;
|
|
||||||
case EQUAL:
|
|
||||||
num = builder.equal(thePath, theValue);
|
|
||||||
break;
|
|
||||||
case NOT_EQUAL:
|
|
||||||
num = builder.notEqual(thePath, theValue);
|
|
||||||
break;
|
|
||||||
case APPROXIMATE:
|
|
||||||
BigDecimal mul = SearchFuzzUtil.calculateFuzzAmount(thePrefix, theValue);
|
|
||||||
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
|
|
||||||
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
|
|
||||||
Predicate lowPred;
|
|
||||||
Predicate highPred;
|
|
||||||
lowPred = builder.ge(thePath.as(BigDecimal.class), low);
|
|
||||||
highPred = builder.le(thePath.as(BigDecimal.class), high);
|
|
||||||
num = builder.and(lowPred, highPred);
|
|
||||||
ourLog.trace("Searching for {} <= val <= {}", low, high);
|
|
||||||
break;
|
|
||||||
case ENDS_BEFORE:
|
|
||||||
case STARTS_AFTER:
|
|
||||||
default:
|
|
||||||
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
|
|
||||||
throw new InvalidRequestException(Msg.code(1069) + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theParamName == null) {
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPartitionIdPredicate(RequestPartitionId theRequestPartitionId, From<?, ? extends BasePartitionable> theJoin, List<Predicate> theCodePredicates) {
|
|
||||||
if (!theRequestPartitionId.isAllPartitions()) {
|
|
||||||
Predicate partitionPredicate;
|
|
||||||
if (theRequestPartitionId.isDefaultPartition()) {
|
|
||||||
partitionPredicate = myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue").as(Integer.class));
|
|
||||||
} else {
|
|
||||||
partitionPredicate = theJoin.get("myPartitionIdValue").as(Integer.class).in(theRequestPartitionId.getPartitionIds());
|
|
||||||
}
|
|
||||||
myQueryStack.addPredicate(partitionPredicate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String createLeftAndRightMatchLikeExpression(String likeExpression) {
|
|
||||||
return "%" + likeExpression.replace("%", "[%]") + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
static String createLeftMatchLikeExpression(String likeExpression) {
|
|
||||||
return likeExpression.replace("%", "[%]") + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
static String createRightMatchLikeExpression(String likeExpression) {
|
|
||||||
return "%" + likeExpression.replace("%", "[%]");
|
|
||||||
}
|
|
||||||
|
|
||||||
static Predicate[] toArray(List<Predicate> thePredicates) {
|
|
||||||
return thePredicates.toArray(new Predicate[0]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface IPredicateBuilder {
|
|
||||||
@Nullable
|
|
||||||
Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId);
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class IndexJoins {
|
|
||||||
Map<SearchBuilderJoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
|
|
||||||
|
|
||||||
public void put(SearchBuilderJoinKey theKey, Join<?, ?> theJoin) {
|
|
||||||
myIndexJoins.put(theKey, theJoin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Join<?,?> get(SearchBuilderJoinKey theKey) {
|
|
||||||
return myIndexJoins.get(theKey);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Subquery;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class PredicateBuilder {
|
|
||||||
private final PredicateBuilderCoords myPredicateBuilderCoords;
|
|
||||||
private final PredicateBuilderDate myPredicateBuilderDate;
|
|
||||||
private final PredicateBuilderNumber myPredicateBuilderNumber;
|
|
||||||
private final PredicateBuilderQuantity myPredicateBuilderQuantity;
|
|
||||||
private final PredicateBuilderReference myPredicateBuilderReference;
|
|
||||||
private final PredicateBuilderResourceId myPredicateBuilderResourceId;
|
|
||||||
private final PredicateBuilderString myPredicateBuilderString;
|
|
||||||
private final PredicateBuilderTag myPredicateBuilderTag;
|
|
||||||
private final PredicateBuilderToken myPredicateBuilderToken;
|
|
||||||
private final PredicateBuilderUri myPredicateBuilderUri;
|
|
||||||
|
|
||||||
public PredicateBuilder(LegacySearchBuilder theSearchBuilder, PredicateBuilderFactory thePredicateBuilderFactory) {
|
|
||||||
myPredicateBuilderCoords = thePredicateBuilderFactory.newPredicateBuilderCoords(theSearchBuilder);
|
|
||||||
myPredicateBuilderDate = thePredicateBuilderFactory.newPredicateBuilderDate(theSearchBuilder);
|
|
||||||
myPredicateBuilderNumber = thePredicateBuilderFactory.newPredicateBuilderNumber(theSearchBuilder);
|
|
||||||
myPredicateBuilderQuantity = thePredicateBuilderFactory.newPredicateBuilderQuantity(theSearchBuilder);
|
|
||||||
myPredicateBuilderReference = thePredicateBuilderFactory.newPredicateBuilderReference(theSearchBuilder, this);
|
|
||||||
myPredicateBuilderResourceId = thePredicateBuilderFactory.newPredicateBuilderResourceId(theSearchBuilder);
|
|
||||||
myPredicateBuilderString = thePredicateBuilderFactory.newPredicateBuilderString(theSearchBuilder);
|
|
||||||
myPredicateBuilderTag = thePredicateBuilderFactory.newPredicateBuilderTag(theSearchBuilder);
|
|
||||||
myPredicateBuilderToken = thePredicateBuilderFactory.newPredicateBuilderToken(theSearchBuilder, this);
|
|
||||||
myPredicateBuilderUri = thePredicateBuilderFactory.newPredicateBuilderUri(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateCoords(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
|
|
||||||
myPredicateBuilderCoords.addPredicate(theResourceName, theSearchParam, theNextAnd, null, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addPredicateDate(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderDate.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addPredicateNumber(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderNumber.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addPredicateQuantity(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderQuantity.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateString(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
|
|
||||||
myPredicateBuilderString.addPredicate(theResourceName, theSearchParam, theNextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addPredicateString(String theResourceName,RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderString.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateTag(List<List<IQueryParameterType>> theAndOrParams, String theParamName, RequestPartitionId theRequestPartitionId) {
|
|
||||||
myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addPredicateToken(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderToken.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addPredicateUri(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theSingletonList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderUri.addPredicate(theResourceName, theSearchParam, theSingletonList, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
|
||||||
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderReference.createLinkSubquery(theParameterName, theTargetResourceType, theOrValues, theRequest, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<?, ResourceLink> theJoin) {
|
|
||||||
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, RequestPartitionId theRequestPartitionId) {
|
|
||||||
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theSearchParam, theBuilder, theStringJoin, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection<Predicate> createPredicateToken(List<IQueryParameterType> theTokens, String theResourceName, RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theTokenJoin, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theSearchParam, theBuilder, theTokenJoin, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> theDateJoin, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderDate.createPredicateDate(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate createPredicateQuantity(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> theDateJoin, RequestPartitionId theRequestPartitionId) {
|
|
||||||
return myPredicateBuilderQuantity.createPredicateQuantity(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,179 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
|
|
||||||
import ca.uhn.fhir.jpa.util.CoordCalculator;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Location;
|
|
||||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
|
||||||
import ca.uhn.fhir.rest.param.SpecialParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import org.hibernate.search.engine.spatial.GeoBoundingBox;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderCoords extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderCoords.class);
|
|
||||||
|
|
||||||
public PredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate createPredicateCoords(IQueryParameterType theParam,
|
|
||||||
String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamCoords> theFrom,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
String latitudeValue;
|
|
||||||
String longitudeValue;
|
|
||||||
Double distanceKm = 0.0;
|
|
||||||
|
|
||||||
if (theParam instanceof TokenParam) { // DSTU3
|
|
||||||
TokenParam param = (TokenParam) theParam;
|
|
||||||
String value = param.getValue();
|
|
||||||
String[] parts = value.split(":");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1038) + "Invalid position format '" + value + "'. Required format is 'latitude:longitude'");
|
|
||||||
}
|
|
||||||
latitudeValue = parts[0];
|
|
||||||
longitudeValue = parts[1];
|
|
||||||
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1039) + "Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
|
|
||||||
}
|
|
||||||
QuantityParam distanceParam = myParams.getNearDistanceParam();
|
|
||||||
if (distanceParam != null) {
|
|
||||||
distanceKm = distanceParam.getValue().doubleValue();
|
|
||||||
}
|
|
||||||
} else if (theParam instanceof SpecialParam) { // R4
|
|
||||||
SpecialParam param = (SpecialParam) theParam;
|
|
||||||
String value = param.getValue();
|
|
||||||
String[] parts = value.split("\\|");
|
|
||||||
if (parts.length < 2 || parts.length > 4) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1040) + "Invalid position format '" + value + "'. Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'");
|
|
||||||
}
|
|
||||||
latitudeValue = parts[0];
|
|
||||||
longitudeValue = parts[1];
|
|
||||||
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1041) + "Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
|
|
||||||
}
|
|
||||||
if (parts.length >= 3) {
|
|
||||||
String distanceString = parts[2];
|
|
||||||
if (!isBlank(distanceString)) {
|
|
||||||
distanceKm = Double.valueOf(distanceString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1042) + "Invalid position type: " + theParam.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate latitudePredicate;
|
|
||||||
Predicate longitudePredicate;
|
|
||||||
if (distanceKm == 0.0) {
|
|
||||||
latitudePredicate = theBuilder.equal(theFrom.get("myLatitude"), latitudeValue);
|
|
||||||
longitudePredicate = theBuilder.equal(theFrom.get("myLongitude"), longitudeValue);
|
|
||||||
} else if (distanceKm < 0.0) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1043) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be >= 0.0");
|
|
||||||
} else if (distanceKm > CoordCalculator.MAX_SUPPORTED_DISTANCE_KM) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1044) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be <= " + CoordCalculator.MAX_SUPPORTED_DISTANCE_KM);
|
|
||||||
} else {
|
|
||||||
double latitudeDegrees = Double.parseDouble(latitudeValue);
|
|
||||||
double longitudeDegrees = Double.parseDouble(longitudeValue);
|
|
||||||
|
|
||||||
GeoBoundingBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
|
|
||||||
latitudePredicate = latitudePredicateFromBox(theBuilder, theFrom, box);
|
|
||||||
longitudePredicate = longitudePredicateFromBox(theBuilder, theFrom, box);
|
|
||||||
}
|
|
||||||
Predicate singleCode = theBuilder.and(latitudePredicate, longitudePredicate);
|
|
||||||
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), theFrom, singleCode, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, GeoBoundingBox theBox) {
|
|
||||||
return theBuilder.and(
|
|
||||||
theBuilder.greaterThanOrEqualTo(theFrom.get("myLatitude"), theBox.bottomRight().latitude()),
|
|
||||||
theBuilder.lessThanOrEqualTo(theFrom.get("myLatitude"), theBox.topLeft().latitude())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Predicate longitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, GeoBoundingBox theBox) {
|
|
||||||
if (theBox.bottomRight().longitude() < theBox.topLeft().longitude()) {
|
|
||||||
return theBuilder.or(
|
|
||||||
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.bottomRight().longitude()),
|
|
||||||
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.topLeft().longitude())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return theBuilder.and(
|
|
||||||
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.topLeft().longitude()),
|
|
||||||
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.bottomRight().longitude())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation theOperation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
From<?, ResourceIndexedSearchParamCoords> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COORDS, theSearchParam.getName());
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
|
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
|
|
||||||
Predicate singleCode = createPredicateCoords(nextOr,
|
|
||||||
theResourceName,
|
|
||||||
theSearchParam,
|
|
||||||
myCriteriaBuilder,
|
|
||||||
join,
|
|
||||||
theRequestPartitionId);
|
|
||||||
codePredicates.add(singleCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,317 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
|
||||||
import ca.uhn.fhir.rest.param.DateParam;
|
|
||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
|
||||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderDate extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderDate.class);
|
|
||||||
|
|
||||||
|
|
||||||
public PredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
String paramName = theSearchParam.getName();
|
|
||||||
boolean newJoin = false;
|
|
||||||
|
|
||||||
Map<String, From<?, ResourceIndexedSearchParamDate>> joinMap = myQueryStack.getJoinMap();
|
|
||||||
String key = theResourceName + " " + paramName;
|
|
||||||
|
|
||||||
From<?, ResourceIndexedSearchParamDate> join = joinMap.get(key);
|
|
||||||
|
|
||||||
if (join == null) {
|
|
||||||
join = myQueryStack.createJoin(SearchBuilderJoinEnum.DATE, paramName);
|
|
||||||
joinMap.put(key, join);
|
|
||||||
newJoin = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
Boolean missing = theList.get(0).getMissing();
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, paramName, missing, join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
Predicate p = createPredicateDate(nextOr,
|
|
||||||
myCriteriaBuilder,
|
|
||||||
join,
|
|
||||||
operation
|
|
||||||
);
|
|
||||||
codePredicates.add(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
|
|
||||||
if (newJoin) {
|
|
||||||
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, orPredicates, theRequestPartitionId);
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(identityAndValuePredicate);
|
|
||||||
} else {
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(orPredicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
return orPredicates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate createPredicateDate(IQueryParameterType theParam,
|
|
||||||
String theResourceName,
|
|
||||||
String theParamName,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamDate> theFrom,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
Predicate predicateDate = createPredicateDate(theParam,
|
|
||||||
theBuilder,
|
|
||||||
theFrom,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, predicateDate, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate createPredicateDate(IQueryParameterType theParam,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamDate> theFrom,
|
|
||||||
SearchFilterParser.CompareOperation theOperation) {
|
|
||||||
|
|
||||||
Predicate p;
|
|
||||||
if (theParam instanceof DateParam) {
|
|
||||||
DateParam date = (DateParam) theParam;
|
|
||||||
if (!date.isEmpty()) {
|
|
||||||
if (theOperation == SearchFilterParser.CompareOperation.ne) {
|
|
||||||
date = new DateParam(ParamPrefixEnum.EQUAL, date.getValueAsString());
|
|
||||||
}
|
|
||||||
DateRangeParam range = new DateRangeParam(date);
|
|
||||||
p = createPredicateDateFromRange(theBuilder,
|
|
||||||
theFrom,
|
|
||||||
range,
|
|
||||||
theOperation);
|
|
||||||
} else {
|
|
||||||
// TODO: handle missing date param?
|
|
||||||
p = null;
|
|
||||||
}
|
|
||||||
} else if (theParam instanceof DateRangeParam) {
|
|
||||||
DateRangeParam range = (DateRangeParam) theParam;
|
|
||||||
p = createPredicateDateFromRange(theBuilder,
|
|
||||||
theFrom,
|
|
||||||
range,
|
|
||||||
theOperation);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1001) + "Invalid token type: " + theParam.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isNullOrDayPrecision(DateParam theDateParam) {
|
|
||||||
return theDateParam == null || theDateParam.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamDate> theFrom,
|
|
||||||
DateRangeParam theRange,
|
|
||||||
SearchFilterParser.CompareOperation operation) {
|
|
||||||
Date lowerBoundInstant = theRange.getLowerBoundAsInstant();
|
|
||||||
Date upperBoundInstant = theRange.getUpperBoundAsInstant();
|
|
||||||
|
|
||||||
DateParam lowerBound = theRange.getLowerBound();
|
|
||||||
DateParam upperBound = theRange.getUpperBound();
|
|
||||||
Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
|
|
||||||
Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
|
|
||||||
Comparable genericLowerBound;
|
|
||||||
Comparable genericUpperBound;
|
|
||||||
/**
|
|
||||||
* If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
|
|
||||||
* then we attempt to use the ordinal field for date comparisons instead of the date field.
|
|
||||||
*/
|
|
||||||
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getModelConfig().getUseOrdinalDatesForDayPrecisionSearches();
|
|
||||||
|
|
||||||
Predicate lt = null;
|
|
||||||
Predicate gt = null;
|
|
||||||
Predicate lb = null;
|
|
||||||
Predicate ub = null;
|
|
||||||
String lowValueField;
|
|
||||||
String highValueField;
|
|
||||||
|
|
||||||
if (isOrdinalComparison) {
|
|
||||||
lowValueField = "myValueLowDateOrdinal";
|
|
||||||
highValueField = "myValueHighDateOrdinal";
|
|
||||||
genericLowerBound = lowerBoundAsOrdinal;
|
|
||||||
genericUpperBound = upperBoundAsOrdinal;
|
|
||||||
} else {
|
|
||||||
lowValueField = "myValueLow";
|
|
||||||
highValueField = "myValueHigh";
|
|
||||||
genericLowerBound = lowerBoundInstant;
|
|
||||||
genericUpperBound = upperBoundInstant;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation == SearchFilterParser.CompareOperation.lt) {
|
|
||||||
// use lower bound first
|
|
||||||
if (lowerBoundInstant != null) {
|
|
||||||
// the value has been reduced one in this case
|
|
||||||
lb = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
lb = theBuilder.or(lb, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericLowerBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (upperBoundInstant != null) {
|
|
||||||
ub = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
ub = theBuilder.or(ub, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(Msg.code(1002) + "lowerBound and upperBound value not correctly specified for compare theOperation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
|
||||||
// use lower bound first
|
|
||||||
if (lowerBoundInstant != null) {
|
|
||||||
lb = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
lb = theBuilder.or(lb, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericLowerBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (upperBoundInstant != null) {
|
|
||||||
ub = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
ub = theBuilder.or(ub, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(Msg.code(1003) + "lowerBound and upperBound value not correctly specified for compare theOperation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
|
||||||
// use upper bound first, e.g value between 6 and 10
|
|
||||||
// gt7 true, 10>7, gt11 false, 10>11 false, gt5 true, 10>5
|
|
||||||
if (upperBoundInstant != null) {
|
|
||||||
ub = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
ub = theBuilder.or(ub, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (lowerBoundInstant != null) {
|
|
||||||
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
lb = theBuilder.or(lb, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(Msg.code(1004) + "upperBound and lowerBound value not correctly specified for compare theOperation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
|
||||||
// use upper bound first, e.g value between 6 and 10
|
|
||||||
// gt7 true, 10>7, gt11 false, 10>11 false, gt5 true, 10>5
|
|
||||||
if (upperBoundInstant != null) {
|
|
||||||
ub = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
ub = theBuilder.or(ub, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (lowerBoundInstant != null) {
|
|
||||||
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
|
|
||||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
|
||||||
lb = theBuilder.or(lb, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(Msg.code(1005) + "upperBound and lowerBound value not correctly specified for compare theOperation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ne) {
|
|
||||||
if ((lowerBoundInstant == null) ||
|
|
||||||
(upperBoundInstant == null)) {
|
|
||||||
throw new InvalidRequestException(Msg.code(1006) + "lowerBound and/or upperBound value not correctly specified for compare operation");
|
|
||||||
}
|
|
||||||
lt = theBuilder.lessThan(theFrom.get(lowValueField), genericLowerBound);
|
|
||||||
gt = theBuilder.greaterThan(theFrom.get(highValueField), genericUpperBound);
|
|
||||||
lb = theBuilder.or(lt, gt);
|
|
||||||
} else if ((operation == SearchFilterParser.CompareOperation.eq) || (operation == null)) {
|
|
||||||
if (lowerBoundInstant != null) {
|
|
||||||
gt = theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
|
||||||
lt = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
|
|
||||||
if (lowerBound.getPrefix() == ParamPrefixEnum.STARTS_AFTER || lowerBound.getPrefix() == ParamPrefixEnum.EQUAL) {
|
|
||||||
lb = gt;
|
|
||||||
} else {
|
|
||||||
lb = theBuilder.or(gt, lt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (upperBoundInstant != null) {
|
|
||||||
gt = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
|
|
||||||
lt = theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
|
|
||||||
|
|
||||||
|
|
||||||
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
|
|
||||||
ub = lt;
|
|
||||||
} else {
|
|
||||||
ub = theBuilder.or(gt, lt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(Msg.code(1007) + String.format("Unsupported operator specified, operator=%s",
|
|
||||||
operation.name()));
|
|
||||||
}
|
|
||||||
if (isOrdinalComparison) {
|
|
||||||
ourLog.trace("Ordinal date range is {} - {} ", lowerBoundAsOrdinal, upperBoundAsOrdinal);
|
|
||||||
} else {
|
|
||||||
ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lb != null && ub != null) {
|
|
||||||
return (theBuilder.and(lb, ub));
|
|
||||||
} else if (lb != null) {
|
|
||||||
return (lb);
|
|
||||||
} else {
|
|
||||||
return (ub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class PredicateBuilderFactory {
|
|
||||||
|
|
||||||
private final ApplicationContext myApplicationContext;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public PredicateBuilderFactory(ApplicationContext theApplicationContext) {
|
|
||||||
myApplicationContext = theApplicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderCoords newPredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderCoords.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderDate newPredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderDate.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderNumber newPredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderNumber.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderQuantity newPredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderQuantity.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderReference newPredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderReference.class, theSearchBuilder, thePredicateBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderResourceId newPredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderResourceId.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderString newPredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderString.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderTag newPredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderTag.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderToken newPredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderToken.class, theSearchBuilder, thePredicateBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PredicateBuilderUri newPredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
return myApplicationContext.getBean(PredicateBuilderUri.class, theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.param.NumberParam;
|
|
||||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderNumber.class);
|
|
||||||
|
|
||||||
public PredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
From<?, ResourceIndexedSearchParamNumber> join = myQueryStack.createJoin(SearchBuilderJoinEnum.NUMBER, theSearchParam.getName());
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
|
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
|
|
||||||
if (nextOr instanceof NumberParam) {
|
|
||||||
NumberParam param = (NumberParam) nextOr;
|
|
||||||
|
|
||||||
BigDecimal value = param.getValue();
|
|
||||||
if (value == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Expression<BigDecimal> fromObj = join.get("myValue");
|
|
||||||
ParamPrefixEnum prefix = defaultIfNull(param.getPrefix(), ParamPrefixEnum.EQUAL);
|
|
||||||
if (operation == SearchFilterParser.CompareOperation.ne) {
|
|
||||||
prefix = ParamPrefixEnum.NOT_EQUAL;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.lt) {
|
|
||||||
prefix = ParamPrefixEnum.LESSTHAN;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
|
||||||
prefix = ParamPrefixEnum.LESSTHAN_OR_EQUALS;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
|
||||||
prefix = ParamPrefixEnum.GREATERTHAN;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
|
||||||
prefix = ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.eq) {
|
|
||||||
prefix = ParamPrefixEnum.EQUAL;
|
|
||||||
} else if (operation != null) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(999) + "Invalid operator specified for number type");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
String invalidMessageName = "invalidNumberPrefix";
|
|
||||||
|
|
||||||
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theSearchParam.getName(), join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName, theRequestPartitionId);
|
|
||||||
Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), join, predicateNumeric, theRequestPartitionId);
|
|
||||||
codePredicates.add(predicateOuter);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1000) + "Invalid token type: " + nextOr.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
|
|
||||||
return predicate;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
|
|
||||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
|
||||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
|
|
||||||
public PredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation theOperation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
From<?, ResourceIndexedSearchParamQuantity> join = myQueryStack.createJoin(SearchBuilderJoinEnum.QUANTITY, theSearchParam.getName());
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
|
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theSearchParam.getName(), myCriteriaBuilder, join, theOperation, theRequestPartitionId);
|
|
||||||
codePredicates.add(singleCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate createPredicateQuantity(IQueryParameterType theParam,
|
|
||||||
String theResourceName,
|
|
||||||
String theParamName,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamQuantity> theFrom,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
return createPredicateQuantity(theParam,
|
|
||||||
theResourceName,
|
|
||||||
theParamName,
|
|
||||||
theBuilder,
|
|
||||||
theFrom,
|
|
||||||
null,
|
|
||||||
theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate createPredicateQuantity(IQueryParameterType theParam,
|
|
||||||
String theResourceName,
|
|
||||||
String theParamName,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamQuantity> theFrom,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
String systemValue;
|
|
||||||
String unitsValue;
|
|
||||||
ParamPrefixEnum cmpValue = null;
|
|
||||||
BigDecimal valueValue;
|
|
||||||
|
|
||||||
if (operation == SearchFilterParser.CompareOperation.ne) {
|
|
||||||
cmpValue = ParamPrefixEnum.NOT_EQUAL;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.lt) {
|
|
||||||
cmpValue = ParamPrefixEnum.LESSTHAN;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
|
||||||
cmpValue = ParamPrefixEnum.LESSTHAN_OR_EQUALS;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
|
||||||
cmpValue = ParamPrefixEnum.GREATERTHAN;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
|
||||||
cmpValue = ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.eq) {
|
|
||||||
cmpValue = ParamPrefixEnum.EQUAL;
|
|
||||||
} else if (operation != null) {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1045) + "Invalid operator specified for quantity type");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theParam instanceof BaseQuantityDt) {
|
|
||||||
BaseQuantityDt param = (BaseQuantityDt) theParam;
|
|
||||||
systemValue = param.getSystemElement().getValueAsString();
|
|
||||||
unitsValue = param.getUnitsElement().getValueAsString();
|
|
||||||
if (operation == null) {
|
|
||||||
cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
|
|
||||||
}
|
|
||||||
valueValue = param.getValueElement().getValue();
|
|
||||||
} else if (theParam instanceof QuantityParam) {
|
|
||||||
QuantityParam param = (QuantityParam) theParam;
|
|
||||||
systemValue = param.getSystem();
|
|
||||||
unitsValue = param.getUnits();
|
|
||||||
if (operation == null) {
|
|
||||||
cmpValue = param.getPrefix();
|
|
||||||
}
|
|
||||||
valueValue = param.getValue();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1046) + "Invalid quantity type: " + theParam.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myDontUseHashesForSearch) {
|
|
||||||
Predicate system = null;
|
|
||||||
if (!isBlank(systemValue)) {
|
|
||||||
system = theBuilder.equal(theFrom.get("mySystem"), systemValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate code = null;
|
|
||||||
if (!isBlank(unitsValue)) {
|
|
||||||
code = theBuilder.equal(theFrom.get("myUnits"), unitsValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
cmpValue = defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);
|
|
||||||
final Expression<BigDecimal> path = theFrom.get("myValue");
|
|
||||||
String invalidMessageName = "invalidQuantityPrefix";
|
|
||||||
|
|
||||||
Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName, theRequestPartitionId);
|
|
||||||
|
|
||||||
Predicate singleCode;
|
|
||||||
if (system == null && code == null) {
|
|
||||||
singleCode = num;
|
|
||||||
} else if (system == null) {
|
|
||||||
singleCode = theBuilder.and(code, num);
|
|
||||||
} else if (code == null) {
|
|
||||||
singleCode = theBuilder.and(system, num);
|
|
||||||
} else {
|
|
||||||
singleCode = theBuilder.and(system, code, num);
|
|
||||||
}
|
|
||||||
|
|
||||||
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate hashPredicate;
|
|
||||||
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
|
|
||||||
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue);
|
|
||||||
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash);
|
|
||||||
} else if (!isBlank(unitsValue)) {
|
|
||||||
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, unitsValue);
|
|
||||||
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash);
|
|
||||||
} else {
|
|
||||||
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName);
|
|
||||||
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
cmpValue = defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);
|
|
||||||
final Expression<BigDecimal> path = theFrom.get("myValue");
|
|
||||||
String invalidMessageName = "invalidQuantityPrefix";
|
|
||||||
|
|
||||||
Predicate numericPredicate = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName, theRequestPartitionId);
|
|
||||||
|
|
||||||
return theBuilder.and(hashPredicate, numericPredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,135 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderResourceId extends BasePredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderResourceId.class);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
IIdHelperService myIdHelperService;
|
|
||||||
|
|
||||||
public PredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
Predicate nextPredicate = createPredicate(theResourceName, theValues, theOperation, theRequestPartitionId);
|
|
||||||
|
|
||||||
if (nextPredicate != null) {
|
|
||||||
myQueryStack.addPredicate(nextPredicate);
|
|
||||||
return nextPredicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Predicate createPredicate(String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
|
||||||
Predicate nextPredicate = null;
|
|
||||||
|
|
||||||
Set<ResourcePersistentId> allOrPids = null;
|
|
||||||
|
|
||||||
for (List<? extends IQueryParameterType> nextValue : theValues) {
|
|
||||||
Set<ResourcePersistentId> orPids = new HashSet<>();
|
|
||||||
boolean haveValue = false;
|
|
||||||
for (IQueryParameterType next : nextValue) {
|
|
||||||
String value = next.getValueAsQueryToken(myContext);
|
|
||||||
if (value != null && value.startsWith("|")) {
|
|
||||||
value = value.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
IdType valueAsId = new IdType(value);
|
|
||||||
if (isNotBlank(value)) {
|
|
||||||
haveValue = true;
|
|
||||||
try {
|
|
||||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, theResourceName, valueAsId.getIdPart());
|
|
||||||
orPids.add(pid);
|
|
||||||
} catch (ResourceNotFoundException e) {
|
|
||||||
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest
|
|
||||||
ourLog.debug("Resource ID {} was requested but does not exist", valueAsId.getIdPart());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (haveValue) {
|
|
||||||
if (allOrPids == null) {
|
|
||||||
allOrPids = orPids;
|
|
||||||
} else {
|
|
||||||
allOrPids.retainAll(orPids);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allOrPids != null && allOrPids.isEmpty()) {
|
|
||||||
|
|
||||||
// This will never match
|
|
||||||
nextPredicate = myCriteriaBuilder.equal(myQueryStack.getResourcePidColumn(), -1);
|
|
||||||
|
|
||||||
} else if (allOrPids != null) {
|
|
||||||
|
|
||||||
SearchFilterParser.CompareOperation operation = defaultIfNull(theOperation, SearchFilterParser.CompareOperation.eq);
|
|
||||||
assert operation == SearchFilterParser.CompareOperation.eq || operation == SearchFilterParser.CompareOperation.ne;
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
switch (operation) {
|
|
||||||
default:
|
|
||||||
case eq:
|
|
||||||
codePredicates.add(myQueryStack.getResourcePidColumn().in(ResourcePersistentId.toLongList(allOrPids)));
|
|
||||||
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
|
|
||||||
break;
|
|
||||||
case ne:
|
|
||||||
codePredicates.add(myQueryStack.getResourcePidColumn().in(ResourcePersistentId.toLongList(allOrPids)).not());
|
|
||||||
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextPredicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
|
||||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
|
||||||
import ca.uhn.fhir.util.StringUtil;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderString extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
public PredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation theOperation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
From<?, ResourceIndexedSearchParamString> join = myQueryStack.createJoin(SearchBuilderJoinEnum.STRING, theSearchParam.getName());
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
|
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
Predicate singleCode = createPredicateString(nextOr, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId);
|
|
||||||
codePredicates.add(singleCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate createPredicateString(IQueryParameterType theParameter,
|
|
||||||
String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamString> theFrom,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
return createPredicateString(theParameter,
|
|
||||||
theResourceName,
|
|
||||||
theSearchParam,
|
|
||||||
theBuilder,
|
|
||||||
theFrom,
|
|
||||||
null,
|
|
||||||
theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate createPredicateString(IQueryParameterType theParameter,
|
|
||||||
String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamString> theFrom,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
String rawSearchTerm;
|
|
||||||
String paramName = theSearchParam.getName();
|
|
||||||
if (theParameter instanceof TokenParam) {
|
|
||||||
TokenParam id = (TokenParam) theParameter;
|
|
||||||
if (!id.isText()) {
|
|
||||||
throw new IllegalStateException(Msg.code(1047) + "Trying to process a text search on a non-text token parameter");
|
|
||||||
}
|
|
||||||
rawSearchTerm = id.getValue();
|
|
||||||
} else if (theParameter instanceof StringParam) {
|
|
||||||
StringParam id = (StringParam) theParameter;
|
|
||||||
rawSearchTerm = id.getValue();
|
|
||||||
if (id.isContains()) {
|
|
||||||
if (!myDaoConfig.isAllowContainsSearches()) {
|
|
||||||
throw new MethodNotAllowedException(Msg.code(1048) + ":contains modifier is disabled on this server");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rawSearchTerm = theSearchParam.encode(rawSearchTerm);
|
|
||||||
}
|
|
||||||
} else if (theParameter instanceof IPrimitiveDatatype<?>) {
|
|
||||||
IPrimitiveDatatype<?> id = (IPrimitiveDatatype<?>) theParameter;
|
|
||||||
rawSearchTerm = id.getValueAsString();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1049) + "Invalid token type: " + theParameter.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
|
|
||||||
throw new InvalidRequestException(Msg.code(1050) + "Parameter[" + paramName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
|
|
||||||
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myDontUseHashesForSearch) {
|
|
||||||
String likeExpression = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm);
|
|
||||||
if (myDaoConfig.isAllowContainsSearches()) {
|
|
||||||
if (theParameter instanceof StringParam) {
|
|
||||||
if (((StringParam) theParameter).isContains()) {
|
|
||||||
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression);
|
|
||||||
} else {
|
|
||||||
likeExpression = createLeftMatchLikeExpression(likeExpression);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
likeExpression = createLeftMatchLikeExpression(likeExpression);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
likeExpression = createLeftMatchLikeExpression(likeExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) {
|
|
||||||
Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm);
|
|
||||||
singleCode = theBuilder.and(singleCode, exactCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
|
|
||||||
if (exactMatch) {
|
|
||||||
// Exact match
|
|
||||||
Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, rawSearchTerm);
|
|
||||||
return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash);
|
|
||||||
} else {
|
|
||||||
// Normalized Match
|
|
||||||
String normalizedString = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm);
|
|
||||||
String likeExpression;
|
|
||||||
if ((theParameter instanceof StringParam) &&
|
|
||||||
(((((StringParam) theParameter).isContains()) &&
|
|
||||||
(myDaoConfig.isAllowContainsSearches())) ||
|
|
||||||
(operation == SearchFilterParser.CompareOperation.co))) {
|
|
||||||
likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
|
|
||||||
} else if ((operation != SearchFilterParser.CompareOperation.ne) &&
|
|
||||||
(operation != SearchFilterParser.CompareOperation.gt) &&
|
|
||||||
(operation != SearchFilterParser.CompareOperation.lt) &&
|
|
||||||
(operation != SearchFilterParser.CompareOperation.ge) &&
|
|
||||||
(operation != SearchFilterParser.CompareOperation.le)) {
|
|
||||||
if (operation == SearchFilterParser.CompareOperation.ew) {
|
|
||||||
likeExpression = createRightMatchLikeExpression(normalizedString);
|
|
||||||
} else {
|
|
||||||
likeExpression = createLeftMatchLikeExpression(normalizedString);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
likeExpression = normalizedString;
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate predicate;
|
|
||||||
if ((operation == null) ||
|
|
||||||
(operation == SearchFilterParser.CompareOperation.sw)) {
|
|
||||||
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, normalizedString);
|
|
||||||
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
|
|
||||||
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = theBuilder.and(hashCode, singleCode);
|
|
||||||
} else if ((operation == SearchFilterParser.CompareOperation.ew) ||
|
|
||||||
(operation == SearchFilterParser.CompareOperation.co)) {
|
|
||||||
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.eq) {
|
|
||||||
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, normalizedString);
|
|
||||||
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
|
|
||||||
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), normalizedString);
|
|
||||||
predicate = theBuilder.and(hashCode, singleCode);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ne) {
|
|
||||||
Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
|
||||||
Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.lt) {
|
|
||||||
Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
|
||||||
Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
|
||||||
Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
|
||||||
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1051) + "Don't yet know how to handle operation " + operation + " on a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
return predicate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,196 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
|
||||||
import ca.uhn.fhir.rest.param.UriParam;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Path;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import javax.persistence.criteria.Subquery;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderTag extends BasePredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderTag.class);
|
|
||||||
|
|
||||||
public PredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicateTag(List<List<IQueryParameterType>> theList, String theParamName, RequestPartitionId theRequestPartitionId) {
|
|
||||||
TagTypeEnum tagType;
|
|
||||||
if (Constants.PARAM_TAG.equals(theParamName)) {
|
|
||||||
tagType = TagTypeEnum.TAG;
|
|
||||||
} else if (Constants.PARAM_PROFILE.equals(theParamName)) {
|
|
||||||
tagType = TagTypeEnum.PROFILE;
|
|
||||||
} else if (Constants.PARAM_SECURITY.equals(theParamName)) {
|
|
||||||
tagType = TagTypeEnum.SECURITY_LABEL;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1030) + "Param name: " + theParamName); // shouldn't happen
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Pair<String, String>> notTags = Lists.newArrayList();
|
|
||||||
for (List<? extends IQueryParameterType> nextAndParams : theList) {
|
|
||||||
for (IQueryParameterType nextOrParams : nextAndParams) {
|
|
||||||
if (nextOrParams instanceof TokenParam) {
|
|
||||||
TokenParam param = (TokenParam) nextOrParams;
|
|
||||||
if (param.getModifier() == TokenParamModifier.NOT) {
|
|
||||||
if (isNotBlank(param.getSystem()) || isNotBlank(param.getValue())) {
|
|
||||||
notTags.add(Pair.of(param.getSystem(), param.getValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (List<? extends IQueryParameterType> nextAndParams : theList) {
|
|
||||||
boolean haveTags = false;
|
|
||||||
for (IQueryParameterType nextParamUncasted : nextAndParams) {
|
|
||||||
if (nextParamUncasted instanceof TokenParam) {
|
|
||||||
TokenParam nextParam = (TokenParam) nextParamUncasted;
|
|
||||||
if (isNotBlank(nextParam.getValue())) {
|
|
||||||
haveTags = true;
|
|
||||||
} else if (isNotBlank(nextParam.getSystem())) {
|
|
||||||
throw new InvalidRequestException(Msg.code(1031) + "Invalid " + theParamName + " parameter (must supply a value/code and not just a system): " + nextParam.getValueAsQueryToken(myContext));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UriParam nextParam = (UriParam) nextParamUncasted;
|
|
||||||
if (isNotBlank(nextParam.getValue())) {
|
|
||||||
haveTags = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!haveTags) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean paramInverted = false;
|
|
||||||
List<Pair<String, String>> tokens = Lists.newArrayList();
|
|
||||||
for (IQueryParameterType nextOrParams : nextAndParams) {
|
|
||||||
String code;
|
|
||||||
String system;
|
|
||||||
if (nextOrParams instanceof TokenParam) {
|
|
||||||
TokenParam nextParam = (TokenParam) nextOrParams;
|
|
||||||
code = nextParam.getValue();
|
|
||||||
system = nextParam.getSystem();
|
|
||||||
if (nextParam.getModifier() == TokenParamModifier.NOT) {
|
|
||||||
paramInverted = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UriParam nextParam = (UriParam) nextOrParams;
|
|
||||||
code = nextParam.getValue();
|
|
||||||
system = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotBlank(code)) {
|
|
||||||
tokens.add(Pair.of(system, code));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramInverted) {
|
|
||||||
ourLog.debug("Searching for _tag:not");
|
|
||||||
|
|
||||||
Subquery<Long> subQ = myQueryStack.subqueryForTagNegation();
|
|
||||||
Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
|
|
||||||
subQ.select(subQfrom.get("myResourceId").as(Long.class));
|
|
||||||
|
|
||||||
myQueryStack.addPredicate(
|
|
||||||
myCriteriaBuilder.not(
|
|
||||||
myCriteriaBuilder.in(
|
|
||||||
myQueryStack.get("myId")
|
|
||||||
).value(subQ)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
Subquery<Long> defJoin = subQ.subquery(Long.class);
|
|
||||||
Root<TagDefinition> defJoinFrom = defJoin.from(TagDefinition.class);
|
|
||||||
defJoin.select(defJoinFrom.get("myId").as(Long.class));
|
|
||||||
|
|
||||||
subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin));
|
|
||||||
|
|
||||||
Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myCriteriaBuilder, tagType, tokens);
|
|
||||||
defJoin.where(tagListPredicate);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
From<?, ResourceTag> tagJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.RESOURCE_TAGS, null);
|
|
||||||
From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
|
|
||||||
|
|
||||||
Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens);
|
|
||||||
List<Predicate> predicates = Lists.newArrayList(tagListPredicate);
|
|
||||||
|
|
||||||
if (theRequestPartitionId != null) {
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, tagJoin, predicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
myQueryStack.addPredicates(predicates);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
|
|
||||||
Predicate typePredicate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType);
|
|
||||||
|
|
||||||
List<Predicate> orPredicates = Lists.newArrayList();
|
|
||||||
for (Pair<String, String> next : theTokens) {
|
|
||||||
Predicate codePredicate = theBuilder.equal(theDefJoin.get("myCode"), next.getRight());
|
|
||||||
if (isNotBlank(next.getLeft())) {
|
|
||||||
Predicate systemPredicate = theBuilder.equal(theDefJoin.get("mySystem"), next.getLeft());
|
|
||||||
orPredicates.add(theBuilder.and(typePredicate, systemPredicate, codePredicate));
|
|
||||||
} else {
|
|
||||||
orPredicates.add(theBuilder.and(typePredicate, codePredicate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return theBuilder.or(toArray(orPredicates));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,393 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
|
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
|
|
||||||
import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
|
|
||||||
import ca.uhn.fhir.rest.param.NumberParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
|
||||||
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
|
|
||||||
import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPredicate;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Path;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public
|
|
||||||
class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
private final PredicateBuilder myPredicateBuilder;
|
|
||||||
@Autowired
|
|
||||||
private ITermReadSvc myTerminologySvc;
|
|
||||||
@Autowired
|
|
||||||
private ModelConfig myModelConfig;
|
|
||||||
|
|
||||||
public PredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
myPredicateBuilder = thePredicateBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation theOperation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
From<?, ResourceIndexedSearchParamToken> join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theSearchParam.getName());
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
|
|
||||||
List<IQueryParameterType> tokens = new ArrayList<>();
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
|
|
||||||
if (nextOr instanceof TokenParam) {
|
|
||||||
TokenParam id = (TokenParam) nextOr;
|
|
||||||
if (id.isText()) {
|
|
||||||
|
|
||||||
// Check whether the :text modifier is actually enabled here
|
|
||||||
boolean tokenTextIndexingEnabled = BaseSearchParamExtractor.tokenTextIndexingEnabledForSearchParam(myModelConfig, theSearchParam);
|
|
||||||
if (!tokenTextIndexingEnabled) {
|
|
||||||
String msg;
|
|
||||||
if (myModelConfig.isSuppressStringIndexingInTokens()) {
|
|
||||||
msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForServer");
|
|
||||||
} else {
|
|
||||||
msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForSearchParam");
|
|
||||||
}
|
|
||||||
throw new MethodNotAllowedException(Msg.code(1032) + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
myPredicateBuilder.addPredicateString(theResourceName, theSearchParam, theList, theOperation, theRequestPartitionId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.add(nextOr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
From<?, ResourceIndexedSearchParamToken> join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theSearchParam.getName());
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
|
|
||||||
|
|
||||||
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId);
|
|
||||||
assert singleCode != null;
|
|
||||||
codePredicates.addAll(singleCode);
|
|
||||||
|
|
||||||
Predicate spPredicate = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(spPredicate);
|
|
||||||
|
|
||||||
return spPredicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
|
|
||||||
String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamToken> theFrom,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
return createPredicateToken(
|
|
||||||
theParameters,
|
|
||||||
theResourceName,
|
|
||||||
theSearchParam,
|
|
||||||
theBuilder,
|
|
||||||
theFrom,
|
|
||||||
null,
|
|
||||||
theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
|
|
||||||
String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
CriteriaBuilder theBuilder,
|
|
||||||
From<?, ResourceIndexedSearchParamToken> theFrom,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
final List<FhirVersionIndependentConcept> codes = new ArrayList<>();
|
|
||||||
String paramName = theSearchParam.getName();
|
|
||||||
|
|
||||||
TokenParamModifier modifier = null;
|
|
||||||
for (IQueryParameterType nextParameter : theParameters) {
|
|
||||||
|
|
||||||
String code;
|
|
||||||
String system;
|
|
||||||
if (nextParameter instanceof TokenParam) {
|
|
||||||
TokenParam id = (TokenParam) nextParameter;
|
|
||||||
system = id.getSystem();
|
|
||||||
code = (id.getValue());
|
|
||||||
modifier = id.getModifier();
|
|
||||||
} else if (nextParameter instanceof BaseIdentifierDt) {
|
|
||||||
BaseIdentifierDt id = (BaseIdentifierDt) nextParameter;
|
|
||||||
system = id.getSystemElement().getValueAsString();
|
|
||||||
code = (id.getValueElement().getValue());
|
|
||||||
} else if (nextParameter instanceof BaseCodingDt) {
|
|
||||||
BaseCodingDt id = (BaseCodingDt) nextParameter;
|
|
||||||
system = id.getSystemElement().getValueAsString();
|
|
||||||
code = (id.getCodeElement().getValue());
|
|
||||||
} else if (nextParameter instanceof NumberParam) {
|
|
||||||
NumberParam number = (NumberParam) nextParameter;
|
|
||||||
system = null;
|
|
||||||
code = number.getValueAsQueryToken(myContext);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1033) + "Invalid token type: " + nextParameter.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
|
|
||||||
throw new InvalidRequestException(Msg.code(1034) + "Parameter[" + paramName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
|
|
||||||
throw new InvalidRequestException(Msg.code(1035) + "Parameter[" + paramName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Process token modifiers (:in, :below, :above)
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (modifier == TokenParamModifier.IN) {
|
|
||||||
codes.addAll(myTerminologySvc.expandValueSetIntoConceptList(null, code));
|
|
||||||
} else if (modifier == TokenParamModifier.ABOVE) {
|
|
||||||
system = determineSystemIfMissing(theSearchParam, code, system);
|
|
||||||
validateHaveSystemAndCodeForToken(paramName, code, system);
|
|
||||||
codes.addAll(myTerminologySvc.findCodesAbove(system, code));
|
|
||||||
} else if (modifier == TokenParamModifier.BELOW) {
|
|
||||||
system = determineSystemIfMissing(theSearchParam, code, system);
|
|
||||||
validateHaveSystemAndCodeForToken(paramName, code, system);
|
|
||||||
codes.addAll(myTerminologySvc.findCodesBelow(system, code));
|
|
||||||
} else {
|
|
||||||
codes.add(new FhirVersionIndependentConcept(system, code));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FhirVersionIndependentConcept> sortedCodesList = codes
|
|
||||||
.stream()
|
|
||||||
.filter(t -> t.getCode() != null || t.getSystem() != null)
|
|
||||||
.sorted()
|
|
||||||
.distinct()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (codes.isEmpty()) {
|
|
||||||
// This will never match anything
|
|
||||||
return Collections.singletonList(new BooleanStaticAssertionPredicate((CriteriaBuilderImpl) theBuilder, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> retVal = new ArrayList<>();
|
|
||||||
|
|
||||||
// System only
|
|
||||||
List<FhirVersionIndependentConcept> systemOnlyCodes = sortedCodesList.stream().filter(t -> isBlank(t.getCode())).collect(Collectors.toList());
|
|
||||||
if (!systemOnlyCodes.isEmpty()) {
|
|
||||||
retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY, theRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code only
|
|
||||||
List<FhirVersionIndependentConcept> codeOnlyCodes = sortedCodesList.stream().filter(t -> t.getSystem() == null).collect(Collectors.toList());
|
|
||||||
if (!codeOnlyCodes.isEmpty()) {
|
|
||||||
retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY, theRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// System and code
|
|
||||||
List<FhirVersionIndependentConcept> systemAndCodeCodes = sortedCodesList.stream().filter(t -> isNotBlank(t.getCode()) && t.getSystem() != null).collect(Collectors.toList());
|
|
||||||
if (!systemAndCodeCodes.isEmpty()) {
|
|
||||||
retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE, theRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String determineSystemIfMissing(RuntimeSearchParam theSearchParam, String code, String theSystem) {
|
|
||||||
String retVal = theSystem;
|
|
||||||
if (retVal == null) {
|
|
||||||
if (theSearchParam != null) {
|
|
||||||
Set<String> valueSetUris = Sets.newHashSet();
|
|
||||||
for (String nextPath : theSearchParam.getPathsSplit()) {
|
|
||||||
if (!nextPath.startsWith(myResourceType + ".")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, nextPath);
|
|
||||||
if (def instanceof BaseRuntimeDeclaredChildDefinition) {
|
|
||||||
String valueSet = ((BaseRuntimeDeclaredChildDefinition) def).getBindingValueSet();
|
|
||||||
if (isNotBlank(valueSet)) {
|
|
||||||
valueSetUris.add(valueSet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (valueSetUris.size() == 1) {
|
|
||||||
String valueSet = valueSetUris.iterator().next();
|
|
||||||
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
|
|
||||||
.setFailOnMissingCodeSystem(false);
|
|
||||||
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSetIntoConceptList(options, valueSet);
|
|
||||||
for (FhirVersionIndependentConcept nextCandidate : candidateCodes) {
|
|
||||||
if (nextCandidate.getCode().equals(code)) {
|
|
||||||
retVal = nextCandidate.getSystem();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateHaveSystemAndCodeForToken(String theParamName, String theCode, String theSystem) {
|
|
||||||
String systemDesc = defaultIfBlank(theSystem, "(missing)");
|
|
||||||
String codeDesc = defaultIfBlank(theCode, "(missing)");
|
|
||||||
if (isBlank(theCode)) {
|
|
||||||
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
|
|
||||||
throw new InvalidRequestException(Msg.code(1036) + msg);
|
|
||||||
}
|
|
||||||
if (isBlank(theSystem)) {
|
|
||||||
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
|
|
||||||
throw new InvalidRequestException(Msg.code(1037) + msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate addPredicate(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamToken> theFrom, List<FhirVersionIndependentConcept> theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode, RequestPartitionId theRequestPartitionId) {
|
|
||||||
if (myDontUseHashesForSearch) {
|
|
||||||
final Path<String> systemExpression = theFrom.get("mySystem");
|
|
||||||
final Path<String> valueExpression = theFrom.get("myValue");
|
|
||||||
|
|
||||||
List<Predicate> orPredicates = new ArrayList<>();
|
|
||||||
switch (theTokenMode) {
|
|
||||||
case SYSTEM_ONLY: {
|
|
||||||
List<String> systems = theTokens.stream().map(t -> t.getSystem()).collect(Collectors.toList());
|
|
||||||
Predicate orPredicate = systemExpression.in(systems);
|
|
||||||
orPredicates.add(orPredicate);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case VALUE_ONLY:
|
|
||||||
List<String> codes = theTokens.stream().map(t -> t.getCode()).collect(Collectors.toList());
|
|
||||||
Predicate orPredicate = valueExpression.in(codes);
|
|
||||||
orPredicates.add(orPredicate);
|
|
||||||
break;
|
|
||||||
case SYSTEM_AND_VALUE:
|
|
||||||
for (FhirVersionIndependentConcept next : theTokens) {
|
|
||||||
orPredicates.add(theBuilder.and(
|
|
||||||
toEqualOrIsNullPredicate(systemExpression, next.getSystem()),
|
|
||||||
toEqualOrIsNullPredicate(valueExpression, next.getCode())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate or = theBuilder.or(orPredicates.toArray(new Predicate[0]));
|
|
||||||
if (theModifier == TokenParamModifier.NOT) {
|
|
||||||
or = theBuilder.not(or);
|
|
||||||
}
|
|
||||||
|
|
||||||
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, or, theRequestPartitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: A null system value means "match any system", but
|
|
||||||
* an empty-string system value means "match values that
|
|
||||||
* explicitly have no system".
|
|
||||||
*/
|
|
||||||
Expression<Long> hashField;
|
|
||||||
List<Long> values;
|
|
||||||
switch (theTokenMode) {
|
|
||||||
case SYSTEM_ONLY:
|
|
||||||
hashField = theFrom.get("myHashSystem").as(Long.class);
|
|
||||||
values = theTokens
|
|
||||||
.stream()
|
|
||||||
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystem(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getSystem()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
break;
|
|
||||||
case VALUE_ONLY:
|
|
||||||
hashField = theFrom.get("myHashValue").as(Long.class);
|
|
||||||
values = theTokens
|
|
||||||
.stream()
|
|
||||||
.map(t -> ResourceIndexedSearchParamToken.calculateHashValue(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getCode()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
break;
|
|
||||||
case SYSTEM_AND_VALUE:
|
|
||||||
default:
|
|
||||||
hashField = theFrom.get("myHashSystemAndValue").as(Long.class);
|
|
||||||
values = theTokens
|
|
||||||
.stream()
|
|
||||||
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getSystem(), t.getCode()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: At one point we had an IF-ELSE here that did an equals if there was only 1 value, and an IN if there
|
|
||||||
* was more than 1. This caused a performance regression for some reason in Postgres though. So maybe simpler
|
|
||||||
* is better..
|
|
||||||
*/
|
|
||||||
Predicate predicate = hashField.in(values);
|
|
||||||
|
|
||||||
if (theModifier == TokenParamModifier.NOT) {
|
|
||||||
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName));
|
|
||||||
Predicate disjunctionPredicate = theBuilder.not(predicate);
|
|
||||||
predicate = theBuilder.and(identityPredicate, disjunctionPredicate);
|
|
||||||
}
|
|
||||||
return predicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> Expression<Boolean> toEqualOrIsNullPredicate(Path<T> theExpression, T theCode) {
|
|
||||||
if (theCode == null) {
|
|
||||||
return myCriteriaBuilder.isNull(theExpression);
|
|
||||||
}
|
|
||||||
return myCriteriaBuilder.equal(theExpression, theCode);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
import ca.uhn.fhir.rest.param.UriParam;
|
|
||||||
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuilder {
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderUri.class);
|
|
||||||
@Autowired
|
|
||||||
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
|
|
||||||
|
|
||||||
public PredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
|
|
||||||
super(theSearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Predicate addPredicate(String theResourceName,
|
|
||||||
RuntimeSearchParam theSearchParam,
|
|
||||||
List<? extends IQueryParameterType> theList,
|
|
||||||
SearchFilterParser.CompareOperation operation,
|
|
||||||
RequestPartitionId theRequestPartitionId) {
|
|
||||||
|
|
||||||
String paramName = theSearchParam.getName();
|
|
||||||
From<?, ResourceIndexedSearchParamUri> join = myQueryStack.createJoin(SearchBuilderJoinEnum.URI, paramName);
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
|
||||||
addPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), join, theRequestPartitionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
|
||||||
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
|
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
|
||||||
|
|
||||||
if (nextOr instanceof UriParam) {
|
|
||||||
UriParam param = (UriParam) nextOr;
|
|
||||||
|
|
||||||
String value = param.getValue();
|
|
||||||
if (value == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param.getQualifier() == UriParamQualifierEnum.ABOVE) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* :above is an inefficient query- It means that the user is supplying a more specific URL (say
|
|
||||||
* http://example.com/foo/bar/baz) and that we should match on any URLs that are less
|
|
||||||
* specific but otherwise the same. For example http://example.com and http://example.com/foo would both
|
|
||||||
* match.
|
|
||||||
*
|
|
||||||
* We do this by querying the DB for all candidate URIs and then manually checking each one. This isn't
|
|
||||||
* very efficient, but this is also probably not a very common type of query to do.
|
|
||||||
*
|
|
||||||
* If we ever need to make this more efficient, lucene could certainly be used as an optimization.
|
|
||||||
*/
|
|
||||||
ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, paramName);
|
|
||||||
Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, paramName);
|
|
||||||
List<String> toFind = new ArrayList<>();
|
|
||||||
for (String next : candidates) {
|
|
||||||
if (value.length() >= next.length()) {
|
|
||||||
if (value.startsWith(next)) {
|
|
||||||
toFind.add(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toFind.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind);
|
|
||||||
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId);
|
|
||||||
codePredicates.add(hashAndUriPredicate);
|
|
||||||
|
|
||||||
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
|
|
||||||
|
|
||||||
Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
|
|
||||||
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId);
|
|
||||||
codePredicates.add(hashAndUriPredicate);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (myDontUseHashesForSearch) {
|
|
||||||
Predicate predicate = myCriteriaBuilder.equal(join.get("myUri").as(String.class), value);
|
|
||||||
codePredicates.add(predicate);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Predicate uriPredicate = null;
|
|
||||||
if (operation == null || operation == SearchFilterParser.CompareOperation.eq) {
|
|
||||||
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, value);
|
|
||||||
Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri);
|
|
||||||
codePredicates.add(hashPredicate);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ne) {
|
|
||||||
uriPredicate = myCriteriaBuilder.notEqual(join.get("myUri").as(String.class), value);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.co) {
|
|
||||||
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value));
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
|
||||||
uriPredicate = myCriteriaBuilder.greaterThan(join.get("myUri").as(String.class), value);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.lt) {
|
|
||||||
uriPredicate = myCriteriaBuilder.lessThan(join.get("myUri").as(String.class), value);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
|
||||||
uriPredicate = myCriteriaBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
|
||||||
uriPredicate = myCriteriaBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value);
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.sw) {
|
|
||||||
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
|
|
||||||
} else if (operation == SearchFilterParser.CompareOperation.ew) {
|
|
||||||
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1070) + String.format("Unsupported operator specified in _filter clause, %s",
|
|
||||||
operation.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uriPredicate != null) {
|
|
||||||
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName);
|
|
||||||
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity);
|
|
||||||
codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(Msg.code(1071) + "Invalid URI type: " + nextOr.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we haven't found any of the requested URIs in the candidates, then we'll
|
|
||||||
* just add a predicate that can never match
|
|
||||||
*/
|
|
||||||
if (codePredicates.isEmpty()) {
|
|
||||||
Predicate predicate = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class));
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate orPredicate = myCriteriaBuilder.or(toArray(codePredicates));
|
|
||||||
|
|
||||||
Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName,
|
|
||||||
paramName,
|
|
||||||
join,
|
|
||||||
orPredicate,
|
|
||||||
theRequestPartitionId);
|
|
||||||
myQueryStack.addPredicateWithImplicitTypeSelection(outerPredicate);
|
|
||||||
return outerPredicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum SearchBuilderJoinEnum {
|
|
||||||
DATE,
|
|
||||||
NUMBER,
|
|
||||||
QUANTITY,
|
|
||||||
REFERENCE,
|
|
||||||
STRING,
|
|
||||||
TOKEN,
|
|
||||||
URI,
|
|
||||||
COORDS,
|
|
||||||
HAS,
|
|
||||||
FORCED_ID, PRESENCE, COMPOSITE_UNIQUE, RESOURCE_TAGS, PROVENANCE
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
|
||||||
|
|
||||||
public class SearchBuilderJoinKey {
|
|
||||||
private final SearchBuilderJoinEnum myJoinType;
|
|
||||||
private final String myParamName;
|
|
||||||
|
|
||||||
public SearchBuilderJoinKey(String theParamName, SearchBuilderJoinEnum theJoinType) {
|
|
||||||
super();
|
|
||||||
myParamName = theParamName;
|
|
||||||
myJoinType = theJoinType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object theObj) {
|
|
||||||
if (!(theObj instanceof SearchBuilderJoinKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SearchBuilderJoinKey obj = (SearchBuilderJoinKey) theObj;
|
|
||||||
return new EqualsBuilder()
|
|
||||||
.append(myParamName, obj.myParamName)
|
|
||||||
.append(myJoinType, obj.myJoinType)
|
|
||||||
.isEquals();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return new HashCodeBuilder()
|
|
||||||
.append(myParamName)
|
|
||||||
.append(myJoinType)
|
|
||||||
.toHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum SearchBuilderTokenModeEnum {
|
|
||||||
SYSTEM_ONLY,
|
|
||||||
VALUE_ONLY,
|
|
||||||
SYSTEM_AND_VALUE
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate.querystack;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.IndexJoins;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.AbstractQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import javax.persistence.criteria.Order;
|
|
||||||
import javax.persistence.criteria.Path;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import javax.persistence.criteria.Subquery;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
abstract class QueryRootEntry {
|
|
||||||
private final ArrayList<Predicate> myPredicates = new ArrayList<>();
|
|
||||||
private final IndexJoins myIndexJoins = new IndexJoins();
|
|
||||||
private final CriteriaBuilder myCriteriaBuilder;
|
|
||||||
private boolean myHasImplicitTypeSelection;
|
|
||||||
private Map<String, From<?, ResourceIndexedSearchParamDate>> myJoinMap;
|
|
||||||
|
|
||||||
QueryRootEntry(CriteriaBuilder theCriteriaBuilder) {
|
|
||||||
myCriteriaBuilder = theCriteriaBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isHasImplicitTypeSelection() {
|
|
||||||
return myHasImplicitTypeSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHasImplicitTypeSelection(boolean theHasImplicitTypeSelection) {
|
|
||||||
myHasImplicitTypeSelection = theHasImplicitTypeSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<Join<?, ?>> getIndexJoin(SearchBuilderJoinKey theKey) {
|
|
||||||
return Optional.ofNullable(myIndexJoins.get(theKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicate(Predicate thePredicate) {
|
|
||||||
myPredicates.add(thePredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPredicates(List<Predicate> thePredicates) {
|
|
||||||
myPredicates.addAll(thePredicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate addNeverMatchingPredicate() {
|
|
||||||
Predicate predicate = myCriteriaBuilder.equal(getResourcePidColumn(), -1L);
|
|
||||||
clearPredicates();
|
|
||||||
addPredicate(predicate);
|
|
||||||
return predicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate[] getPredicateArray() {
|
|
||||||
return myPredicates.toArray(new Predicate[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void putIndex(SearchBuilderJoinKey theKey, Join<?, ?> theJoin) {
|
|
||||||
myIndexJoins.put(theKey, theJoin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearPredicates() {
|
|
||||||
myPredicates.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Predicate> getPredicates() {
|
|
||||||
return Collections.unmodifiableList(myPredicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
<Y> Path<Y> get(String theAttributeName) {
|
|
||||||
return getRoot().get(theAttributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
AbstractQuery<Long> pop() {
|
|
||||||
Predicate[] predicateArray = getPredicateArray();
|
|
||||||
if (predicateArray.length == 1) {
|
|
||||||
getQueryRoot().where(predicateArray[0]);
|
|
||||||
} else {
|
|
||||||
getQueryRoot().where(myCriteriaBuilder.and(predicateArray));
|
|
||||||
}
|
|
||||||
|
|
||||||
return getQueryRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, From<?, ResourceIndexedSearchParamDate>> getJoinMap() {
|
|
||||||
Map<String, From<?, ResourceIndexedSearchParamDate>> retVal = myJoinMap;
|
|
||||||
if (retVal==null) {
|
|
||||||
retVal = new HashMap<>();
|
|
||||||
myJoinMap = retVal;
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract void orderBy(List<Order> theOrders);
|
|
||||||
|
|
||||||
abstract Expression<Date> getLastUpdatedColumn();
|
|
||||||
|
|
||||||
abstract <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName);
|
|
||||||
|
|
||||||
abstract AbstractQuery<Long> getQueryRoot();
|
|
||||||
|
|
||||||
abstract Root<?> getRoot();
|
|
||||||
|
|
||||||
abstract Expression<Long> getResourcePidColumn();
|
|
||||||
|
|
||||||
abstract Subquery<Long> subqueryForTagNegation();
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate.querystack;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.AbstractQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Order;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import javax.persistence.criteria.Subquery;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class QueryRootEntryIndexTable extends QueryRootEntry {
|
|
||||||
private final Subquery<Long> myQuery;
|
|
||||||
private Root<? extends BaseResourceIndex> myRoot;
|
|
||||||
private SearchBuilderJoinEnum myParamType;
|
|
||||||
private Expression<Long> myResourcePidColumn;
|
|
||||||
|
|
||||||
public QueryRootEntryIndexTable(CriteriaBuilder theCriteriaBuilder, QueryRootEntry theParent) {
|
|
||||||
super(theCriteriaBuilder);
|
|
||||||
|
|
||||||
AbstractQuery<Long> queryRoot = theParent.getQueryRoot();
|
|
||||||
myQuery = queryRoot.subquery(Long.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void orderBy(List<Order> theOrders) {
|
|
||||||
throw new IllegalStateException(Msg.code(1072));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Expression<Date> getLastUpdatedColumn() {
|
|
||||||
return getRoot().get("myUpdated").as(Date.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
|
|
||||||
if (myParamType == null) {
|
|
||||||
switch (theType) {
|
|
||||||
case REFERENCE:
|
|
||||||
myRoot = myQuery.from(ResourceLink.class);
|
|
||||||
myResourcePidColumn = myRoot.get("mySourceResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.REFERENCE;
|
|
||||||
break;
|
|
||||||
case NUMBER:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamNumber.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.NUMBER;
|
|
||||||
break;
|
|
||||||
case DATE:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamDate.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.DATE;
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamString.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.STRING;
|
|
||||||
break;
|
|
||||||
case TOKEN:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamToken.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.TOKEN;
|
|
||||||
break;
|
|
||||||
case QUANTITY:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamQuantity.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.QUANTITY;
|
|
||||||
break;
|
|
||||||
case URI:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamUri.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.URI;
|
|
||||||
break;
|
|
||||||
case COORDS:
|
|
||||||
myRoot = myQuery.from(ResourceIndexedSearchParamCoords.class);
|
|
||||||
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
|
|
||||||
myParamType = SearchBuilderJoinEnum.COORDS;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(Msg.code(1073));
|
|
||||||
}
|
|
||||||
|
|
||||||
myQuery.select(myResourcePidColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
Validate.isTrue(theType == myParamType, "Wanted %s but got %s for %s", myParamType, theType, theSearchParameterName);
|
|
||||||
return (From<?, T>) myRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
AbstractQuery<Long> getQueryRoot() {
|
|
||||||
Validate.isTrue(myQuery != null);
|
|
||||||
return myQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Root<?> getRoot() {
|
|
||||||
Validate.isTrue(myRoot != null);
|
|
||||||
return myRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Expression<Long> getResourcePidColumn() {
|
|
||||||
Validate.isTrue(myResourcePidColumn != null);
|
|
||||||
return myResourcePidColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subquery<Long> subqueryForTagNegation() {
|
|
||||||
throw new IllegalStateException(Msg.code(1074));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,207 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate.querystack;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.AbstractQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import javax.persistence.criteria.JoinType;
|
|
||||||
import javax.persistence.criteria.Order;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import javax.persistence.criteria.Subquery;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
class QueryRootEntryResourceTable extends QueryRootEntry {
|
|
||||||
|
|
||||||
private final CriteriaBuilder myCriteriaBuilder;
|
|
||||||
private final AbstractQuery<Long> myQuery;
|
|
||||||
private final SearchParameterMap mySearchParameterMap;
|
|
||||||
private final RequestPartitionId myRequestPartitionId;
|
|
||||||
private final String myResourceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will ddd a predicate to make sure we only include non-deleted resources, and only include
|
|
||||||
* resources of the right type.
|
|
||||||
*
|
|
||||||
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
|
|
||||||
* need an explicit predicate for it.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
AbstractQuery<Long> pop() {
|
|
||||||
|
|
||||||
if (!isHasImplicitTypeSelection()) {
|
|
||||||
if (mySearchParameterMap.getEverythingMode() == null) {
|
|
||||||
addPredicate(myCriteriaBuilder.equal(getRoot().get("myResourceType"), myResourceType));
|
|
||||||
}
|
|
||||||
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myDeleted")));
|
|
||||||
if (!myRequestPartitionId.isAllPartitions()) {
|
|
||||||
if (!myRequestPartitionId.isDefaultPartition()) {
|
|
||||||
addPredicate(getRoot().get("myPartitionIdValue").as(Integer.class).in(myRequestPartitionId.getPartitionIds()));
|
|
||||||
} else {
|
|
||||||
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myPartitionIdValue").as(Integer.class)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Root<ResourceTable> myResourceTableRoot;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Root query constructor
|
|
||||||
*/
|
|
||||||
QueryRootEntryResourceTable(CriteriaBuilder theCriteriaBuilder, boolean theDistinct, boolean theCountQuery, SearchParameterMap theSearchParameterMap, String theResourceType, RequestPartitionId theRequestPartitionId) {
|
|
||||||
super(theCriteriaBuilder);
|
|
||||||
myCriteriaBuilder = theCriteriaBuilder;
|
|
||||||
mySearchParameterMap = theSearchParameterMap;
|
|
||||||
myRequestPartitionId = theRequestPartitionId;
|
|
||||||
myResourceType = theResourceType;
|
|
||||||
|
|
||||||
CriteriaQuery<Long> query = myCriteriaBuilder.createQuery(Long.class);
|
|
||||||
myResourceTableRoot = query.from(ResourceTable.class);
|
|
||||||
|
|
||||||
if (theCountQuery) {
|
|
||||||
query.multiselect(myCriteriaBuilder.countDistinct(myResourceTableRoot));
|
|
||||||
} else if (theDistinct) {
|
|
||||||
query.distinct(true).multiselect(get("myId").as(Long.class));
|
|
||||||
} else {
|
|
||||||
query.multiselect(get("myId").as(Long.class));
|
|
||||||
}
|
|
||||||
myQuery = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subquery constructor
|
|
||||||
*/
|
|
||||||
QueryRootEntryResourceTable(CriteriaBuilder theCriteriaBuilder, QueryRootEntry theParent, SearchParameterMap theSearchParameterMap, String theResourceType, RequestPartitionId theRequestPartitionId) {
|
|
||||||
super(theCriteriaBuilder);
|
|
||||||
myCriteriaBuilder = theCriteriaBuilder;
|
|
||||||
mySearchParameterMap = theSearchParameterMap;
|
|
||||||
myRequestPartitionId = theRequestPartitionId;
|
|
||||||
myResourceType = theResourceType;
|
|
||||||
|
|
||||||
AbstractQuery<Long> queryRoot = theParent.getQueryRoot();
|
|
||||||
Subquery<Long> query = queryRoot.subquery(Long.class);
|
|
||||||
myQuery = query;
|
|
||||||
myResourceTableRoot = myQuery.from(ResourceTable.class);
|
|
||||||
query.select(myResourceTableRoot.get("myId").as(Long.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void orderBy(List<Order> theOrders) {
|
|
||||||
assert myQuery instanceof CriteriaQuery;
|
|
||||||
|
|
||||||
((CriteriaQuery<?>)myQuery).orderBy(theOrders);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Expression<Date> getLastUpdatedColumn() {
|
|
||||||
return myResourceTableRoot.get("myUpdated").as(Date.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
<T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
|
|
||||||
Join<?,?> join = null;
|
|
||||||
switch (theType) {
|
|
||||||
case DATE:
|
|
||||||
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case NUMBER:
|
|
||||||
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case QUANTITY:
|
|
||||||
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case REFERENCE:
|
|
||||||
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case URI:
|
|
||||||
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case TOKEN:
|
|
||||||
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case COORDS:
|
|
||||||
join = myResourceTableRoot.join("myParamsCoords", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case HAS:
|
|
||||||
join = myResourceTableRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case PROVENANCE:
|
|
||||||
join = myResourceTableRoot.join("myProvenance", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case FORCED_ID:
|
|
||||||
join = myResourceTableRoot.join("myForcedId", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case PRESENCE:
|
|
||||||
join = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case COMPOSITE_UNIQUE:
|
|
||||||
join = myResourceTableRoot.join("myParamsComboStringUnique", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case RESOURCE_TAGS:
|
|
||||||
join = myResourceTableRoot.join("myTags", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSearchParameterName, theType);
|
|
||||||
putIndex(key, join);
|
|
||||||
|
|
||||||
return (From<?, T>) join;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
AbstractQuery<Long> getQueryRoot() {
|
|
||||||
return myQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Root<ResourceTable> getRoot() {
|
|
||||||
return myResourceTableRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Expression<Long> getResourcePidColumn() {
|
|
||||||
return myResourceTableRoot.get("myId").as(Long.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subquery<Long> subqueryForTagNegation() {
|
|
||||||
return myQuery.subquery(Long.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,303 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate.querystack;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.AbstractQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import javax.persistence.criteria.Order;
|
|
||||||
import javax.persistence.criteria.Path;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import javax.persistence.criteria.Subquery;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents a SQL SELECT statement that is selecting for resource PIDs, ie.
|
|
||||||
* the <code>RES_ID</code> column on the <code>HFJ_RESOURCE</code> ({@link ca.uhn.fhir.jpa.model.entity.ResourceTable})
|
|
||||||
* table.
|
|
||||||
* <p>
|
|
||||||
* We add predicates (WHERE A=B) to it, and can join other tables to it as well. At the root of the query
|
|
||||||
* we are typically doing a <code>select RES_ID from HFJ_RESOURCE where (....)</code> and this class
|
|
||||||
* is used to build the <i>where</i> clause. In the case of subqueries though, we may be performing a
|
|
||||||
* select on a different table since many tables have a column with a FK dependency on RES_ID.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class QueryStack {
|
|
||||||
|
|
||||||
private final Stack<QueryRootEntry> myQueryRootStack = new Stack<>();
|
|
||||||
private final CriteriaBuilder myCriteriaBuilder;
|
|
||||||
private final SearchParameterMap mySearchParameterMap;
|
|
||||||
private final RequestPartitionId myRequestPartitionId;
|
|
||||||
private final String myResourceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public QueryStack(CriteriaBuilder theCriteriaBuilder, String theResourceType, SearchParameterMap theSearchParameterMap, RequestPartitionId theRequestPartitionId) {
|
|
||||||
assert theCriteriaBuilder != null;
|
|
||||||
assert isNotBlank(theResourceType);
|
|
||||||
assert theSearchParameterMap != null;
|
|
||||||
assert theRequestPartitionId != null;
|
|
||||||
myCriteriaBuilder = theCriteriaBuilder;
|
|
||||||
mySearchParameterMap = theSearchParameterMap;
|
|
||||||
myRequestPartitionId = theRequestPartitionId;
|
|
||||||
myResourceType = theResourceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
|
|
||||||
* will be added to this select clause until {@link #pop()} is called.
|
|
||||||
* <p>
|
|
||||||
* This method must only be called when the stack is empty.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public void pushResourceTableQuery() {
|
|
||||||
assert myQueryRootStack.isEmpty();
|
|
||||||
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new <code>select DISTINCT RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
|
|
||||||
* will be added to this select clause until {@link #pop()} is called.
|
|
||||||
* <p>
|
|
||||||
* This method must only be called when the stack is empty.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public void pushResourceTableDistinctQuery() {
|
|
||||||
assert myQueryRootStack.isEmpty();
|
|
||||||
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, true, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new <code>select count(RES_ID) from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
|
|
||||||
* will be added to this select clause until {@link #pop()} is called.
|
|
||||||
* <p>
|
|
||||||
* This method must only be called when the stack is empty.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public void pushResourceTableCountQuery() {
|
|
||||||
assert myQueryRootStack.isEmpty();
|
|
||||||
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, true, mySearchParameterMap, myResourceType, myRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
|
|
||||||
* will be added to this select clause until {@link #pop()} is called.
|
|
||||||
* <p>
|
|
||||||
* This method must only be called when the stack is NOT empty.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public void pushResourceTableSubQuery(String theResourceType) {
|
|
||||||
assert !myQueryRootStack.isEmpty();
|
|
||||||
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, top(), mySearchParameterMap, theResourceType, myRequestPartitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new <code>select RES_ID from (....)</code> to the stack, where the specific table being selected on will be
|
|
||||||
* determined based on the first call to {@link #createJoin(SearchBuilderJoinEnum, String)}. All predicates added
|
|
||||||
* to the {@literal QueryRootStack} will be added to this select clause until {@link #pop()} is called.
|
|
||||||
* <p>
|
|
||||||
* This method must only be called when the stack is NOT empty.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public void pushIndexTableSubQuery() {
|
|
||||||
assert !myQueryRootStack.isEmpty();
|
|
||||||
myQueryRootStack.push(new QueryRootEntryIndexTable(myCriteriaBuilder, top()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method must be called once all predicates have been added
|
|
||||||
*/
|
|
||||||
public AbstractQuery<Long> pop() {
|
|
||||||
QueryRootEntry element = myQueryRootStack.pop();
|
|
||||||
return element.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new SQL join from the current select statement to another table, using the resource PID as the
|
|
||||||
* joining key
|
|
||||||
*/
|
|
||||||
public <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
|
|
||||||
return top().createJoin(theType, theSearchParameterName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a join that was previously created by a call to {@link #createJoin(SearchBuilderJoinEnum, String)},
|
|
||||||
* if one exists for the given key.
|
|
||||||
*/
|
|
||||||
public Optional<Join<?, ?>> getExistingJoin(SearchBuilderJoinKey theKey) {
|
|
||||||
return top().getIndexJoin(theKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an attribute (aka a column) from the current select statement.
|
|
||||||
*
|
|
||||||
* @param theAttributeName Must be the name of a java field for the entity/table being selected on
|
|
||||||
*/
|
|
||||||
public <Y> Path<Y> get(String theAttributeName) {
|
|
||||||
return top().get(theAttributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a predicate to the current select statement
|
|
||||||
*/
|
|
||||||
public void addPredicate(Predicate thePredicate) {
|
|
||||||
top().addPredicate(thePredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a predicate and marks it as having implicit type selection in it. In other words, call this method if a
|
|
||||||
* this predicate will ensure:
|
|
||||||
* <ul>
|
|
||||||
* <li>Only Resource PIDs for the correct resource type will be selected</li>
|
|
||||||
* <li>Only Resource PIDs for non-deleted resources will be selected</li>
|
|
||||||
* </ul>
|
|
||||||
* Setting this flag is a performance optimization, since it avoids the need for us to explicitly
|
|
||||||
* add predicates for the two conditions above.
|
|
||||||
*/
|
|
||||||
public void addPredicateWithImplicitTypeSelection(Predicate thePredicate) {
|
|
||||||
setHasImplicitTypeSelection();
|
|
||||||
addPredicate(thePredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds predicates and marks them as having implicit type selection in it. In other words, call this method if a
|
|
||||||
* this predicate will ensure:
|
|
||||||
* <ul>
|
|
||||||
* <li>Only Resource PIDs for the correct resource type will be selected</li>
|
|
||||||
* <li>Only Resource PIDs for non-deleted resources will be selected</li>
|
|
||||||
* </ul>
|
|
||||||
* Setting this flag is a performance optimization, since it avoids the need for us to explicitly
|
|
||||||
* add predicates for the two conditions above.
|
|
||||||
*/
|
|
||||||
public void addPredicatesWithImplicitTypeSelection(List<Predicate> thePredicates) {
|
|
||||||
setHasImplicitTypeSelection();
|
|
||||||
addPredicates(thePredicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds predicate(s) to the current select statement
|
|
||||||
*/
|
|
||||||
public void addPredicates(List<Predicate> thePredicates) {
|
|
||||||
top().addPredicates(thePredicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all predicates from the current select statement
|
|
||||||
*/
|
|
||||||
public void clearPredicates() {
|
|
||||||
top().clearPredicates();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch all the current predicates
|
|
||||||
* <p>
|
|
||||||
* TODO This should really be package protected, but it is called externally in one spot - We need to clean that up
|
|
||||||
* at some point.
|
|
||||||
*/
|
|
||||||
public List<Predicate> getPredicates() {
|
|
||||||
return top().getPredicates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setHasImplicitTypeSelection() {
|
|
||||||
top().setHasImplicitTypeSelection(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #setHasImplicitTypeSelection()
|
|
||||||
*/
|
|
||||||
public void clearHasImplicitTypeSelection() {
|
|
||||||
top().setHasImplicitTypeSelection(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return myQueryRootStack.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an SQL <code>order by</code> expression
|
|
||||||
*/
|
|
||||||
public void orderBy(List<Order> theOrders) {
|
|
||||||
top().orderBy(theOrders);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the column for the current table root that corresponds to the resource's lastUpdated time
|
|
||||||
*/
|
|
||||||
public Expression<Date> getLastUpdatedColumn() {
|
|
||||||
return top().getLastUpdatedColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the column for the current table root that corresponds to the resource's PID
|
|
||||||
*/
|
|
||||||
public Expression<Long> getResourcePidColumn() {
|
|
||||||
return top().getResourcePidColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subquery<Long> subqueryForTagNegation() {
|
|
||||||
return top().subqueryForTagNegation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private QueryRootEntry top() {
|
|
||||||
Validate.isTrue(!myQueryRootStack.empty());
|
|
||||||
return myQueryRootStack.peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO This class should avoid leaking the internal query root, but we need to do so for how composite search params are
|
|
||||||
* currently implemented. These only half work in the first place so I'm not going to worry about the fact that
|
|
||||||
* they rely on a leaky abstraction right now.. But when we get around to implementing composites properly,
|
|
||||||
* let's not continue this. JA 2020-05-12
|
|
||||||
*/
|
|
||||||
public Root<?> getRootForComposite() {
|
|
||||||
return top().getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a predicate that will never match any resources
|
|
||||||
*/
|
|
||||||
public Predicate addNeverMatchingPredicate() {
|
|
||||||
return top().addNeverMatchingPredicate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, From<?, ResourceIndexedSearchParamDate>> getJoinMap() {
|
|
||||||
return top().getJoinMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@
|
||||||
* in {@link ca.uhn.fhir.jpa.dao.search.ExtendedHSearchIndexExtractor#extract(org.hl7.fhir.instance.model.api.IBaseResource, ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams)} ()}
|
* in {@link ca.uhn.fhir.jpa.dao.search.ExtendedHSearchIndexExtractor#extract(org.hl7.fhir.instance.model.api.IBaseResource, ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams)} ()}
|
||||||
*
|
*
|
||||||
* <h2>Implementation</h2>
|
* <h2>Implementation</h2>
|
||||||
* Both {@link ca.uhn.fhir.jpa.search.builder.SearchBuilder} and {@link ca.uhn.fhir.jpa.dao.LegacySearchBuilder} delegate the
|
* {@link ca.uhn.fhir.jpa.search.builder.SearchBuilder} delegates the
|
||||||
* search to {@link ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl#doSearch} when active.
|
* search to {@link ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl#doSearch} when active.
|
||||||
* The fulltext search runs first and interprets any search parameters it understands, returning a pid list.
|
* The fulltext search runs first and interprets any search parameters it understands, returning a pid list.
|
||||||
* This pid list is used as a narrowing where clause against the remaining unprocessed search parameters in a jdbc query.
|
* This pid list is used as a narrowing where clause against the remaining unprocessed search parameters in a jdbc query.
|
||||||
|
|
|
@ -103,7 +103,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.dao.LegacySearchBuilder.toPredicateArray;
|
import static ca.uhn.fhir.jpa.search.builder.SearchBuilder.toPredicateArray;
|
||||||
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
|
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
|
@ -35,7 +35,6 @@ import ca.uhn.fhir.jpa.dao.BaseStorageDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
|
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
|
@ -43,6 +42,7 @@ import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
|
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
|
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
|
||||||
|
@ -419,13 +419,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!myDaoRegistry.isResourceTypeSupported(paramType)) {
|
if (!myDaoRegistry.isResourceTypeSupported(paramType)) {
|
||||||
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", paramType);
|
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidResourceType", paramType);
|
||||||
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
|
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
|
||||||
throw new InvalidRequestException(Msg.code(2017) + msg);
|
throw new InvalidRequestException(Msg.code(2017) + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotBlank(paramTargetType) && !myDaoRegistry.isResourceTypeSupported(paramTargetType)) {
|
if (isNotBlank(paramTargetType) && !myDaoRegistry.isResourceTypeSupported(paramTargetType)) {
|
||||||
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", paramTargetType);
|
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidResourceType", paramTargetType);
|
||||||
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
|
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
|
||||||
throw new InvalidRequestException(Msg.code(2016) + msg);
|
throw new InvalidRequestException(Msg.code(2016) + msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
|
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
@ -1149,7 +1147,7 @@ public class QueryStack {
|
||||||
|
|
||||||
private Condition createPredicateSource(@Nullable DbColumn theSourceJoinColumn, List<? extends IQueryParameterType> theList) {
|
private Condition createPredicateSource(@Nullable DbColumn theSourceJoinColumn, List<? extends IQueryParameterType> theList) {
|
||||||
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
|
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
|
||||||
String msg = myFhirContext.getLocalizer().getMessage(LegacySearchBuilder.class, "sourceParamDisabled");
|
String msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "sourceParamDisabled");
|
||||||
throw new InvalidRequestException(Msg.code(1216) + msg);
|
throw new InvalidRequestException(Msg.code(1216) + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1321,13 +1319,12 @@ public class QueryStack {
|
||||||
if (!tokenTextIndexingEnabled) {
|
if (!tokenTextIndexingEnabled) {
|
||||||
String msg;
|
String msg;
|
||||||
if (myModelConfig.isSuppressStringIndexingInTokens()) {
|
if (myModelConfig.isSuppressStringIndexingInTokens()) {
|
||||||
msg = myFhirContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForServer");
|
msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "textModifierDisabledForServer");
|
||||||
} else {
|
} else {
|
||||||
msg = myFhirContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForSearchParam");
|
msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "textModifierDisabledForSearchParam");
|
||||||
}
|
}
|
||||||
throw new MethodNotAllowedException(Msg.code(1219) + msg);
|
throw new MethodNotAllowedException(Msg.code(1219) + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId, theSqlBuilder);
|
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId, theSqlBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,10 +146,11 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
||||||
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude())),
|
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude())),
|
||||||
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude()))
|
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude()))
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
return ComboCondition.and(
|
return ComboCondition.and(
|
||||||
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude())),
|
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude())),
|
||||||
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude()))
|
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.search.builder.predicate;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFuzzUtil;
|
import ca.uhn.fhir.jpa.dao.predicate.SearchFuzzUtil;
|
||||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||||
|
@ -105,7 +104,7 @@ public class NumberPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
String paramValue = theActualParam.getValueAsQueryToken(theFhirContext);
|
String paramValue = theActualParam.getValueAsQueryToken(theFhirContext);
|
||||||
String msg = theIndexTable.getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, theInvalidValueKey, operation, paramValue);
|
String msg = theIndexTable.getFhirContext().getLocalizer().getMessage(NumberPredicateBuilder.class, theInvalidValueKey, operation, paramValue);
|
||||||
throw new InvalidRequestException(Msg.code(1235) + msg);
|
throw new InvalidRequestException(Msg.code(1235) + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,10 +37,10 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||||
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
|
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
|
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
||||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
|
@ -672,20 +672,19 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) {
|
private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) {
|
||||||
String searchParamName = theResourceName + ":" + theParamName;
|
String searchParamName = theResourceName + ":" + theParamName;
|
||||||
String msg = getFhirContext().getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
|
String msg = getFhirContext().getLocalizer().getMessage(ResourceLinkPredicateBuilder.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
|
||||||
return new InvalidRequestException(msg);
|
return new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
|
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
|
||||||
IQueryParameterType qp = toParameterType(theParam);
|
IQueryParameterType qp = toParameterType(theParam);
|
||||||
|
|
||||||
qp.setValueAsQueryToken(getFhirContext(), theParam.getName(), theQualifier, theValueAsQueryToken);
|
qp.setValueAsQueryToken(getFhirContext(), theParam.getName(), theQualifier, theValueAsQueryToken);
|
||||||
return qp;
|
return qp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
|
private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
|
||||||
String msg = getFhirContext().getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
|
String msg = getFhirContext().getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidResourceType", theResourceType);
|
||||||
throw new InvalidRequestException(Msg.code(1250) + msg);
|
throw new InvalidRequestException(Msg.code(1250) + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||||
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
@ -49,9 +48,7 @@ import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|
||||||
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
|
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.Condition;
|
import com.healthmarketscience.sqlbuilder.Condition;
|
||||||
|
@ -340,11 +337,11 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
||||||
String systemDesc = defaultIfBlank(theSystem, "(missing)");
|
String systemDesc = defaultIfBlank(theSystem, "(missing)");
|
||||||
String codeDesc = defaultIfBlank(theCode, "(missing)");
|
String codeDesc = defaultIfBlank(theCode, "(missing)");
|
||||||
if (isBlank(theCode)) {
|
if (isBlank(theCode)) {
|
||||||
String msg = getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
|
String msg = getFhirContext().getLocalizer().getMessage(TokenPredicateBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
|
||||||
throw new InvalidRequestException(Msg.code(1239) + msg);
|
throw new InvalidRequestException(Msg.code(1239) + msg);
|
||||||
}
|
}
|
||||||
if (isBlank(theSystem)) {
|
if (isBlank(theSystem)) {
|
||||||
String msg = getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
|
String msg = getFhirContext().getLocalizer().getMessage(TokenPredicateBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
|
||||||
throw new InvalidRequestException(Msg.code(1240) + msg);
|
throw new InvalidRequestException(Msg.code(1240) + msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.TypedQuery;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
public class LegacySearchBuilderTest {
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIncludeIterator() {
|
|
||||||
BaseHapiFhirDao<?> mockDao = mock(BaseHapiFhirDao.class);
|
|
||||||
LegacySearchBuilder searchBuilder = new LegacySearchBuilder(mockDao, null, null);
|
|
||||||
searchBuilder.setDaoConfigForUnitTest(new DaoConfig());
|
|
||||||
searchBuilder.setParamsForUnitTest(new SearchParameterMap());
|
|
||||||
EntityManager mockEntityManager = mock(EntityManager.class);
|
|
||||||
searchBuilder.setEntityManagerForUnitTest(mockEntityManager);
|
|
||||||
|
|
||||||
Set<ResourcePersistentId> pidSet = new HashSet<>();
|
|
||||||
pidSet.add(new ResourcePersistentId(1L));
|
|
||||||
pidSet.add(new ResourcePersistentId(2L));
|
|
||||||
|
|
||||||
TypedQuery mockQuery = mock(TypedQuery.class);
|
|
||||||
when(mockEntityManager.createQuery(any(), any())).thenReturn(mockQuery);
|
|
||||||
List<Long> resultList = new ArrayList<>();
|
|
||||||
Long link = 1L;
|
|
||||||
ResourceTable target = new ResourceTable();
|
|
||||||
target.setId(1L);
|
|
||||||
resultList.add(link);
|
|
||||||
when(mockQuery.getResultList()).thenReturn(resultList);
|
|
||||||
|
|
||||||
LegacySearchBuilder.IncludesIterator includesIterator = searchBuilder.new IncludesIterator(pidSet, null);
|
|
||||||
// hasNext() should return false if the pid added was already on our list going in.
|
|
||||||
assertFalse(includesIterator.hasNext());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilderTest;
|
import ca.uhn.fhir.jpa.dao.predicate.SearchFuzzUtil;
|
||||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
public class SearchFuzzUtilTest {
|
public class SearchFuzzUtilTest {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(LegacySearchBuilderTest.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(SearchFuzzUtilTest.class);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCalculateMultiplierEqualNoDecimal() {
|
public void testCalculateMultiplierEqualNoDecimal() {
|
|
@ -1,89 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao.predicate;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.util.CoordCalculator;
|
|
||||||
import ca.uhn.fhir.jpa.util.CoordCalculatorTest;
|
|
||||||
import org.hibernate.search.engine.spatial.GeoBoundingBox;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.From;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
|
||||||
import static org.hamcrest.Matchers.lessThan;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
public class PredicateBuilderCoordsTest {
|
|
||||||
PredicateBuilderCoords myPredicateBuilderCoords;
|
|
||||||
private LegacySearchBuilder mySearchBuilder;
|
|
||||||
private CriteriaBuilder myBuilder;
|
|
||||||
private From myFrom;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void before() {
|
|
||||||
mySearchBuilder = mock(LegacySearchBuilder.class);
|
|
||||||
myBuilder = mock(CriteriaBuilder.class);
|
|
||||||
myFrom = mock(From.class);
|
|
||||||
myPredicateBuilderCoords = new PredicateBuilderCoords(mySearchBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLongitudePredicateFromBox() {
|
|
||||||
GeoBoundingBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_CHIN, CoordCalculatorTest.LONGITUDE_CHIN, CoordCalculatorTest.DISTANCE_TAVEUNI);
|
|
||||||
assertThat(box.bottomRight().longitude(), greaterThan(box.topLeft().longitude()));
|
|
||||||
|
|
||||||
ArgumentCaptor<Predicate> andLeft = ArgumentCaptor.forClass(Predicate.class);
|
|
||||||
ArgumentCaptor<Predicate> andRight = ArgumentCaptor.forClass(Predicate.class);
|
|
||||||
|
|
||||||
ArgumentCaptor<Double> gteValue = ArgumentCaptor.forClass(Double.class);
|
|
||||||
ArgumentCaptor<Double> lteValue = ArgumentCaptor.forClass(Double.class);
|
|
||||||
|
|
||||||
Predicate gte = mock(Predicate.class);
|
|
||||||
Predicate lte = mock(Predicate.class);
|
|
||||||
when(myBuilder.greaterThanOrEqualTo(any(), gteValue.capture())).thenReturn(gte);
|
|
||||||
when(myBuilder.lessThanOrEqualTo(any(),lteValue.capture())).thenReturn(lte);
|
|
||||||
myPredicateBuilderCoords.longitudePredicateFromBox(myBuilder, myFrom, box);
|
|
||||||
verify(myBuilder).and(andLeft.capture(), andRight.capture());
|
|
||||||
assertEquals(andLeft.getValue(), gte);
|
|
||||||
assertEquals(andRight.getValue(), lte);
|
|
||||||
assertEquals(gteValue.getValue(), box.topLeft().longitude());
|
|
||||||
assertEquals(lteValue.getValue(), box.bottomRight().longitude());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAntiMeridianLongitudePredicateFromBox() {
|
|
||||||
GeoBoundingBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_TAVEUNI, CoordCalculatorTest.LONGITIDE_TAVEUNI, CoordCalculatorTest.DISTANCE_TAVEUNI);
|
|
||||||
assertThat(box.bottomRight().longitude(), lessThan(box.topLeft().longitude()));
|
|
||||||
assertTrue(box.bottomRight().longitude() < box.topLeft().longitude());
|
|
||||||
|
|
||||||
ArgumentCaptor<Predicate> orLeft = ArgumentCaptor.forClass(Predicate.class);
|
|
||||||
ArgumentCaptor<Predicate> orRight = ArgumentCaptor.forClass(Predicate.class);
|
|
||||||
|
|
||||||
ArgumentCaptor<Double> gteValue = ArgumentCaptor.forClass(Double.class);
|
|
||||||
ArgumentCaptor<Double> lteValue = ArgumentCaptor.forClass(Double.class);
|
|
||||||
|
|
||||||
Predicate gte = mock(Predicate.class);
|
|
||||||
Predicate lte = mock(Predicate.class);
|
|
||||||
when(myBuilder.greaterThanOrEqualTo(any(), gteValue.capture())).thenReturn(gte);
|
|
||||||
when(myBuilder.lessThanOrEqualTo(any(),lteValue.capture())).thenReturn(lte);
|
|
||||||
myPredicateBuilderCoords.longitudePredicateFromBox(myBuilder, myFrom, box);
|
|
||||||
verify(myBuilder).or(orLeft.capture(), orRight.capture());
|
|
||||||
assertEquals(orLeft.getValue(), gte);
|
|
||||||
assertEquals(orRight.getValue(), lte);
|
|
||||||
assertEquals(gteValue.getValue(), box.bottomRight().longitude());
|
|
||||||
assertEquals(lteValue.getValue(), box.topLeft().longitude());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -67,7 +67,6 @@ public class ResourceProviderHasParamR4Test extends BaseResourceProviderR4Test {
|
||||||
myDaoConfig.setAllowMultipleDelete(true);
|
myDaoConfig.setAllowMultipleDelete(true);
|
||||||
myClient.registerInterceptor(myCapturingInterceptor);
|
myClient.registerInterceptor(myCapturingInterceptor);
|
||||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||||
//myDaoConfig.setUseLegacySearchBuilder(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|
|
@ -5,8 +5,8 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
||||||
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
|
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.util.BaseIterator;
|
import ca.uhn.fhir.jpa.util.BaseIterator;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
|
@ -32,7 +32,7 @@ public class BaseSearchSvc {
|
||||||
@Mock
|
@Mock
|
||||||
protected PlatformTransactionManager myTxManager;
|
protected PlatformTransactionManager myTxManager;
|
||||||
@Mock
|
@Mock
|
||||||
protected LegacySearchBuilder mySearchBuilder;
|
protected SearchBuilder mySearchBuilder;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
protected IFhirResourceDao<?> myCallingDao;
|
protected IFhirResourceDao<?> myCallingDao;
|
||||||
|
|
|
@ -505,7 +505,7 @@ public class DaoConfig {
|
||||||
* @since 5.3.0
|
* @since 5.3.0
|
||||||
*/
|
*/
|
||||||
public boolean isUseLegacySearchBuilder() {
|
public boolean isUseLegacySearchBuilder() {
|
||||||
return myUseLegacySearchBuilder;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -515,9 +515,10 @@ public class DaoConfig {
|
||||||
* <p>Note that this method will be removed in HAPI FHIR 5.4.0</p>
|
* <p>Note that this method will be removed in HAPI FHIR 5.4.0</p>
|
||||||
*
|
*
|
||||||
* @since 5.3.0
|
* @since 5.3.0
|
||||||
|
* @deprecated in 6.1.0, this toggle will be removed in 6.2.0 as the Legacy Search Builder has been removed.
|
||||||
*/
|
*/
|
||||||
public void setUseLegacySearchBuilder(boolean theUseLegacySearchBuilder) {
|
public void setUseLegacySearchBuilder(boolean theUseLegacySearchBuilder) {
|
||||||
myUseLegacySearchBuilder = theUseLegacySearchBuilder;
|
//Nop
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue