Add non unique combo search params (#2809)

* Start work on nonunique combo search params

* Work on SQL

* Version bump

* A fix

* Test fixes

* Fixes

* Undo version bump

* Test fixes

* Resolve fixme
This commit is contained in:
James Agnew 2021-07-21 19:14:14 -04:00 committed by GitHub
parent 6af022062f
commit 6d37749be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1076 additions and 333 deletions

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.context;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2021 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 ComboSearchParamType {
UNIQUE,
NON_UNIQUE
}

View File

@ -416,7 +416,7 @@ class ModelScanner {
if (theResourceDef.isStandardType()) {
url = "http://hl7.org/fhir/SearchParameter/" + theResourceDef.getName().toLowerCase() + "-" + searchParam.name();
}
RuntimeSearchParam param = new RuntimeSearchParam(null, url, searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE, false, components, base);
RuntimeSearchParam param = new RuntimeSearchParam(null, url, searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE, null, components, base);
theResourceDef.addSearchParam(param);
nameToParam.put(param.getName(), param);
}

View File

@ -10,6 +10,7 @@ import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -55,7 +56,7 @@ public class RuntimeSearchParam {
private final RuntimeSearchParamStatusEnum myStatus;
private final String myUri;
private final Map<String, List<IBaseExtension<?, ?>>> myExtensions = new HashMap<>();
private final boolean myUnique;
private final ComboSearchParamType myComboSearchParamType;
private final List<Component> myComponents;
private IPhoneticEncoder myPhoneticEncoder;
@ -64,20 +65,20 @@ public class RuntimeSearchParam {
*/
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType,
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
this(theId, theUri, theName, theDescription, thePath, theParamType, theProvidesMembershipInCompartments, theTargets, theStatus, false, Collections.emptyList(), theBase);
this(theId, theUri, theName, theDescription, thePath, theParamType, theProvidesMembershipInCompartments, theTargets, theStatus, null, Collections.emptyList(), theBase);
}
/**
* Copy constructor
*/
public RuntimeSearchParam(RuntimeSearchParam theSp) {
this(theSp.getId(), theSp.getUri(), theSp.getName(), theSp.getDescription(), theSp.getPath(), theSp.getParamType(), theSp.getProvidesMembershipInCompartments(), theSp.getTargets(), theSp.getStatus(), theSp.isUnique(), theSp.getComponents(), theSp.getBase());
this(theSp.getId(), theSp.getUri(), theSp.getName(), theSp.getDescription(), theSp.getPath(), theSp.getParamType(), theSp.getProvidesMembershipInCompartments(), theSp.getTargets(), theSp.getStatus(), theSp.getComboSearchParamType(), theSp.getComponents(), theSp.getBase());
}
/**
* Constructor
*/
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, boolean theUnique, List<Component> theComponents, Collection<String> theBase) {
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, ComboSearchParamType theComboSearchParamType, List<Component> theComponents, Collection<String> theBase) {
super();
myId = theId;
@ -110,7 +111,7 @@ public class RuntimeSearchParam {
} else {
myBase = Collections.unmodifiableSet(new HashSet<>(theBase));
}
myUnique = theUnique;
myComboSearchParamType = theComboSearchParamType;
if (theComponents != null) {
myComponents = Collections.unmodifiableList(theComponents);
} else {
@ -122,8 +123,12 @@ public class RuntimeSearchParam {
return myComponents;
}
public boolean isUnique() {
return myUnique;
/**
* Returns <code>null</code> if this is not a combo search param type
*/
@Nullable
public ComboSearchParamType getComboSearchParamType() {
return myComboSearchParamType;
}
/**

View File

@ -94,7 +94,8 @@ import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboNonUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
@ -609,8 +610,14 @@ public abstract class BaseConfig {
@Bean
@Scope("prototype")
public CompositeUniqueSearchParameterPredicateBuilder newCompositeUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return new CompositeUniqueSearchParameterPredicateBuilder(theSearchSqlBuilder);
public ComboUniqueSearchParameterPredicateBuilder newComboUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return new ComboUniqueSearchParameterPredicateBuilder(theSearchSqlBuilder);
}
@Bean
@Scope("prototype")
public ComboNonUniqueSearchParameterPredicateBuilder newComboNonUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return new ComboNonUniqueSearchParameterPredicateBuilder(theSearchSqlBuilder);
}
@Bean

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hibernate.HibernateException;
import org.hibernate.PessimisticLockException;
@ -81,7 +81,7 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
if (constraintName.contains(ResourceHistoryTable.IDX_RESVER_ID_VER)) {
throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure"));
}
if (constraintName.contains(ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) {
if (constraintName.contains(ResourceIndexedComboStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) {
throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceIndexedCompositeStringUniqueConstraintFailure"));
}
if (constraintName.contains(ForcedId.IDX_FORCEDID_TYPE_FID)) {

View File

@ -129,7 +129,6 @@ import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.validation.constraints.Null;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import java.util.ArrayList;
@ -1404,7 +1403,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
// Synchronize composite params
mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, entity, existingParams);
mySearchParamWithInlineReferencesExtractor.storeUniqueComboParameters(newParams, entity, existingParams);
}
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -42,7 +43,7 @@ import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
@ -868,9 +869,11 @@ public class LegacySearchBuilder implements ISearchBuilder {
// Since we're going to remove elements below
theParams.values().forEach(nextAndList -> ensureSubListsAreWritable(nextAndList));
List<RuntimeSearchParam> activeUniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, theParams.keySet());
List<RuntimeSearchParam> activeUniqueSearchParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet());
if (activeUniqueSearchParams.size() > 0) {
Validate.isTrue(activeUniqueSearchParams.get(0).getComboSearchParamType()== ComboSearchParamType.UNIQUE, "Non unique combo parameters are not supported with the legacy search builder");
StringBuilder sb = new StringBuilder();
sb.append(myResourceName);
sb.append("?");
@ -943,7 +946,7 @@ public class LegacySearchBuilder implements ISearchBuilder {
}
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, RequestPartitionId theRequestPartitionId) {
From<?, ResourceIndexedCompositeStringUnique> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COMPOSITE_UNIQUE, null);
From<?, ResourceIndexedComboStringUnique> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COMPOSITE_UNIQUE, null);
if (!theRequestPartitionId.isAllPartitions()) {
Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull();

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.dao.data;
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
@ -28,15 +28,15 @@ import org.springframework.data.repository.query.Param;
import java.util.List;
public interface IResourceIndexedCompositeStringUniqueDao extends JpaRepository<ResourceIndexedCompositeStringUnique, Long> {
public interface IResourceIndexedComboStringUniqueDao extends JpaRepository<ResourceIndexedComboStringUnique, Long> {
@Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString = :str")
ResourceIndexedCompositeStringUnique findByQueryString(@Param("str") String theQueryString);
@Query("SELECT r FROM ResourceIndexedComboStringUnique r WHERE r.myIndexString = :str")
ResourceIndexedComboStringUnique findByQueryString(@Param("str") String theQueryString);
@Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myResourceId = :resId")
List<ResourceIndexedCompositeStringUnique> findAllForResourceIdForUnitTest(@Param("resId") Long theResourceId);
@Query("SELECT r FROM ResourceIndexedComboStringUnique r WHERE r.myResourceId = :resId")
List<ResourceIndexedComboStringUnique> findAllForResourceIdForUnitTest(@Param("resId") Long theResourceId);
@Modifying
@Query("delete from ResourceIndexedCompositeStringUnique t WHERE t.myResourceId = :resid")
@Query("delete from ResourceIndexedComboStringUnique t WHERE t.myResourceId = :resid")
void deleteByResourceId(@Param("resid") Long theResourcePid);
}

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.dao.data;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 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.model.entity.ResourceIndexedComboTokenNonUnique;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface IResourceIndexedComboTokensNonUniqueDao extends JpaRepository<ResourceIndexedComboTokenNonUnique, Long> {
@Modifying
@Query("DELETE FROM ResourceIndexedComboTokenNonUnique t WHERE t.myResourceId = :res_id")
void deleteByResourceId(@Param("res_id") Long theResourcePid);
}

View File

@ -50,7 +50,8 @@ import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@ -136,7 +137,8 @@ public class ExpungeEverythingService {
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamToken.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamUri.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamCoords.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedCompositeStringUnique.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedComboStringUnique.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedComboTokenNonUnique.class));
counter.addAndGet(expungeEverythingByType(ResourceLink.class));
counter.addAndGet(expungeEverythingByType(SearchResult.class));
counter.addAndGet(expungeEverythingByType(SearchInclude.class));

View File

@ -28,7 +28,8 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamNumberDao;
@ -95,7 +96,9 @@ public class ResourceExpungeService implements IResourceExpungeService {
@Autowired
private IResourceIndexedSearchParamNumberDao myResourceIndexedSearchParamNumberDao;
@Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
private IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao;
@Autowired
private IResourceLinkDao myResourceLinkDao;
@Autowired
@ -289,9 +292,12 @@ public class ResourceExpungeService implements IResourceExpungeService {
if (resource == null || resource.isParamsTokenPopulated()) {
myResourceIndexedSearchParamTokenDao.deleteByResourceId(theResourceId);
}
if (resource == null || resource.isParamsCompositeStringUniquePresent()) {
if (resource == null || resource.isParamsComboStringUniquePresent()) {
myResourceIndexedCompositeStringUniqueDao.deleteByResourceId(theResourceId);
}
if (resource == null || resource.isParamsComboTokensNonUniquePresent()) {
myResourceIndexedComboTokensNonUniqueDao.deleteByResourceId(theResourceId);
}
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
mySearchParamPresentDao.deleteByResourceId(theResourceId);
}

View File

@ -40,6 +40,7 @@ public class ResourceTableFKProvider {
// SELECT FKTABLE_NAME, FKCOLUMN_NAME FROM CROSS_REFERENCES WHERE PKTABLE_NAME = 'HFJ_RESOURCE'
retval.add(new ResourceForeignKey("HFJ_FORCED_ID", "RESOURCE_PID"));
retval.add(new ResourceForeignKey("HFJ_IDX_CMP_STRING_UNIQ", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_IDX_CMB_TOK_NU", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_RES_LINK", "SRC_RESOURCE_ID"));
retval.add(new ResourceForeignKey("HFJ_RES_LINK", "TARGET_RESOURCE_ID"));
retval.add(new ResourceForeignKey("HFJ_RES_PARAM_PRESENT", "RES_ID"));

View File

@ -20,17 +20,11 @@ package ca.uhn.fhir.jpa.dao.index;
* #L%
*/
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
@ -43,15 +37,8 @@ import java.util.List;
@Service
public class DaoSearchParamSynchronizer {
private static final Logger ourLog = LoggerFactory.getLogger(DaoSearchParamSynchronizer.class);
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired
private ModelConfig myModelConfig;
public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
AddRemoveCount retVal = new AddRemoveCount();
@ -65,6 +52,7 @@ public class DaoSearchParamSynchronizer {
synchronize(theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
synchronize(theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
synchronize(theEntity, retVal, theParams.myLinks, existingParams.myLinks);
synchronize(theEntity, retVal, theParams.myComboTokenNonUnique, existingParams.myComboTokenNonUnique);
// make sure links are indexed
theEntity.setResourceLinks(theParams.myLinks);

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.index;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
@ -27,17 +28,19 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
@ -46,15 +49,18 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.StringUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
@ -64,8 +70,10 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -92,7 +100,7 @@ public class SearchParamWithInlineReferencesExtractor {
@Autowired
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
private PartitionSettings myPartitionSettings;
@ -140,7 +148,7 @@ public class SearchParamWithInlineReferencesExtractor {
}
/*
* Handle composites
* Handle combo parameters
*/
extractCompositeStringUniques(theEntity, theParams);
}
@ -148,40 +156,73 @@ public class SearchParamWithInlineReferencesExtractor {
private void extractCompositeStringUniques(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
final String resourceType = theEntity.getResourceType();
List<RuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(resourceType);
List<RuntimeSearchParam> comboSearchParams = mySearchParamRegistry.getActiveComboSearchParams(resourceType);
for (RuntimeSearchParam next : uniqueSearchParams) {
for (RuntimeSearchParam next : comboSearchParams) {
switch (Objects.requireNonNull(next.getComboSearchParamType())) {
case UNIQUE:
extractComboUniqueParam(theEntity, theParams, resourceType, next);
break;
case NON_UNIQUE:
extractComboNonUniqueParam(theEntity, theParams, resourceType, next);
}
}
}
private void extractComboNonUniqueParam(ResourceTable theEntity, ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
Set<String> queryStringsToPopulate = extractParameterCombinationsForComboParam(theParams, theResourceType, theParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
theParams.myComboTokenNonUnique.add(new ResourceIndexedComboTokenNonUnique(myPartitionSettings, theEntity, nextQueryString));
}
}
private void extractComboUniqueParam(ResourceTable theEntity, ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
Set<String> queryStringsToPopulate = extractParameterCombinationsForComboParam(theParams, theResourceType, theParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
theParams.myComboStringUniques.add(new ResourceIndexedComboStringUnique(theEntity, nextQueryString, theParam.getId()));
}
}
@Nonnull
private Set<String> extractParameterCombinationsForComboParam(ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
List<List<String>> partsChoices = new ArrayList<>();
List<RuntimeSearchParam> compositeComponents = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, next);
List<RuntimeSearchParam> compositeComponents = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParam);
for (RuntimeSearchParam nextCompositeOf : compositeComponents) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = findParameterIndexes(theParams, nextCompositeOf);
Collection<ResourceLink> linksForCompositePart = null;
Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) {
case NUMBER:
paramsListForCompositePart = theParams.myNumberParams;
break;
case DATE:
paramsListForCompositePart = theParams.myDateParams;
break;
case STRING:
paramsListForCompositePart = theParams.myStringParams;
break;
case TOKEN:
paramsListForCompositePart = theParams.myTokenParams;
break;
case REFERENCE:
linksForCompositePart = theParams.myLinks;
break;
case NUMBER:
case DATE:
case STRING:
case TOKEN:
case QUANTITY:
case URI:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) {
case REFERENCE:
linksForCompositePartWantPaths = new HashSet<>(nextCompositeOf.getPathsSplit());
break;
case NUMBER:
case DATE:
case STRING:
case TOKEN:
case QUANTITY:
paramsListForCompositePart = theParams.myQuantityParams;
break;
case URI:
paramsListForCompositePart = theParams.myUriParams;
break;
case SPECIAL:
case COMPOSITE:
case HAS:
@ -194,16 +235,20 @@ public class SearchParamWithInlineReferencesExtractor {
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
if (paramsListForCompositePart != null) {
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
String value = nextParamAsClientParam.getValueAsQueryToken(myContext);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceType, key);
if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE && param != null && param.getParamType() == RestSearchParameterTypeEnum.STRING) {
value = StringUtil.normalizeStringForSearchIndexing(value);
}
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
if (linksForCompositePart != null) {
for (ResourceLink nextLink : linksForCompositePart) {
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
@ -219,15 +264,44 @@ public class SearchParamWithInlineReferencesExtractor {
}
}
Set<String> queryStringsToPopulate = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices);
return ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(theResourceType, partsChoices);
}
for (String nextQueryString : queryStringsToPopulate) {
if (isNotBlank(nextQueryString)) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
theParams.myCompositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString, next.getId()));
}
@Nullable
private Collection<? extends BaseResourceIndexedSearchParam> findParameterIndexes(ResourceIndexedSearchParams theParams, RuntimeSearchParam nextCompositeOf) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
switch (nextCompositeOf.getParamType()) {
case NUMBER:
paramsListForCompositePart = theParams.myNumberParams;
break;
case DATE:
paramsListForCompositePart = theParams.myDateParams;
break;
case STRING:
paramsListForCompositePart = theParams.myStringParams;
break;
case TOKEN:
paramsListForCompositePart = theParams.myTokenParams;
break;
case QUANTITY:
paramsListForCompositePart = theParams.myQuantityParams;
break;
case URI:
paramsListForCompositePart = theParams.myUriParams;
break;
case REFERENCE:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
if (paramsListForCompositePart != null) {
paramsListForCompositePart = paramsListForCompositePart
.stream()
.filter(t->t.getParamName().equals(nextCompositeOf.getName()))
.collect(Collectors.toList());
}
return paramsListForCompositePart;
}
@ -309,19 +383,21 @@ public class SearchParamWithInlineReferencesExtractor {
myDaoSearchParamSynchronizer = theDaoSearchParamSynchronizer;
}
public void storeCompositeStringUniques(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
public void storeUniqueComboParameters(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams theExistingParams) {
// Store composite string uniques
/*
* String Uniques
*/
if (myDaoConfig.isUniqueIndexesEnabled()) {
for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(existingParams.myCompositeStringUniques, theParams.myCompositeStringUniques)) {
for (ResourceIndexedComboStringUnique next : myDaoSearchParamSynchronizer.subtract(theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) {
ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().remove(next);
theEntity.getParamsComboStringUnique().remove(next);
}
boolean haveNewParams = false;
for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myCompositeStringUniques, existingParams.myCompositeStringUniques)) {
boolean haveNewStringUniqueParams = false;
for (ResourceIndexedComboStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) {
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
ResourceIndexedComboStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) {
String searchParameterId = "(unknown)";
@ -335,12 +411,12 @@ public class SearchParamWithInlineReferencesExtractor {
}
ourLog.debug("Persisting unique index: {}", next);
myEntityManager.persist(next);
haveNewParams = true;
haveNewStringUniqueParams = true;
}
if (theParams.myCompositeStringUniques.size() > 0 || haveNewParams) {
theEntity.setParamsCompositeStringUniquePresent(true);
if (theParams.myComboStringUniques.size() > 0 || haveNewStringUniqueParams) {
theEntity.setParamsComboStringUniquePresent(true);
} else {
theEntity.setParamsCompositeStringUniquePresent(false);
theEntity.setParamsComboStringUniquePresent(false);
}
}
}

View File

@ -170,7 +170,7 @@ class QueryRootEntryResourceTable extends QueryRootEntry {
join = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
break;
case COMPOSITE_UNIQUE:
join = myResourceTableRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
join = myResourceTableRoot.join("myParamsComboStringUnique", JoinType.LEFT);
break;
case RESOURCE_TAGS:
join = myResourceTableRoot.join("myTags", JoinType.LEFT);

View File

@ -34,7 +34,8 @@ import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboNonUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
@ -1214,11 +1215,18 @@ public class QueryStack {
}
public void addPredicateCompositeUnique(String theIndexString, RequestPartitionId theRequestPartitionId) {
CompositeUniqueSearchParameterPredicateBuilder predicateBuilder = mySqlBuilder.addCompositeUniquePredicateBuilder();
ComboUniqueSearchParameterPredicateBuilder predicateBuilder = mySqlBuilder.addComboUniquePredicateBuilder();
Condition predicate = predicateBuilder.createPredicateIndexString(theRequestPartitionId, theIndexString);
mySqlBuilder.addPredicate(predicate);
}
public void addPredicateCompositeNonUnique(String theIndexString, RequestPartitionId theRequestPartitionId) {
ComboNonUniqueSearchParameterPredicateBuilder predicateBuilder = mySqlBuilder.addComboNonUniquePredicateBuilder();
Condition predicate = predicateBuilder.createPredicateHashComplete(theRequestPartitionId, theIndexString);
mySqlBuilder.addPredicate(predicate);
}
public void addPredicateEverythingOperation(String theResourceName, Long theTargetPid) {
ResourceLinkPredicateBuilder table = mySqlBuilder.addReferencePredicateBuilder(this, null);
Condition predicate = table.createEverythingPredicate(theResourceName, theTargetPid);

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.search.builder;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -85,6 +86,7 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.StringUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
@ -119,6 +121,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -213,7 +216,7 @@ public class SearchBuilder implements ISearchBuilder {
// Attempt to lookup via composite unique key.
if (isCompositeUniqueSpCandidate()) {
attemptCompositeUniqueSpProcessing(theQueryStack, theParams, theRequest);
attemptComboUniqueSpProcessing(theQueryStack, theParams, theRequest);
}
SearchContainedModeEnum searchContainedMode = theParams.getSearchContainedMode();
@ -366,8 +369,11 @@ public class SearchBuilder implements ISearchBuilder {
QueryStack queryStack3 = new QueryStack(theParams, myDaoConfig, myDaoConfig.getModelConfig(), myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
if (theParams.keySet().size() > 1 || theParams.getSort() != null || theParams.keySet().contains(Constants.PARAM_HAS)) {
List<RuntimeSearchParam> activeComboParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet());
if (activeComboParams.isEmpty()) {
sqlBuilder.setNeedResourceTableRoot(true);
}
}
JdbcTemplate jdbcTemplate = new JdbcTemplate(myEntityManagerFactory.getDataSource());
jdbcTemplate.setFetchSize(myFetchSize);
@ -1009,22 +1015,43 @@ public class SearchBuilder implements ISearchBuilder {
}
}
private void attemptCompositeUniqueSpProcessing(QueryStack theQueryStack3, @Nonnull SearchParameterMap theParams, RequestDetails theRequest) {
private void attemptComboUniqueSpProcessing(QueryStack theQueryStack3, @Nonnull SearchParameterMap theParams, RequestDetails theRequest) {
RuntimeSearchParam comboParam = null;
List<String> comboParamNames = null;
List<RuntimeSearchParam> exactMatchParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet());
if (exactMatchParams.size() > 0) {
comboParam = exactMatchParams.get(0);
comboParamNames = new ArrayList<>(theParams.keySet());
}
if (comboParam == null) {
List<RuntimeSearchParam> candidateComboParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName);
for (RuntimeSearchParam nextCandidate : candidateComboParams) {
List<String> nextCandidateParamNames = JpaParamUtil
.resolveComponentParameters(mySearchParamRegistry, nextCandidate)
.stream()
.map(t -> t.getName())
.collect(Collectors.toList());
if (theParams.keySet().containsAll(nextCandidateParamNames)) {
comboParam = nextCandidate;
comboParamNames = nextCandidateParamNames;
break;
}
}
}
if (comboParam != null) {
// Since we're going to remove elements below
theParams.values().forEach(nextAndList -> ensureSubListsAreWritable(nextAndList));
List<RuntimeSearchParam> activeUniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, theParams.keySet());
if (activeUniqueSearchParams.size() > 0) {
StringBuilder sb = new StringBuilder();
sb.append(myResourceName);
sb.append("?");
boolean first = true;
ArrayList<String> keys = new ArrayList<>(theParams.keySet());
Collections.sort(keys);
for (String nextParamName : keys) {
Collections.sort(comboParamNames);
for (String nextParamName : comboParamNames) {
List<List<IQueryParameterType>> nextValues = theParams.get(nextParamName);
nextParamName = UrlUtil.escapeUrlParam(nextParamName);
@ -1047,6 +1074,13 @@ public class SearchBuilder implements ISearchBuilder {
List<? extends IQueryParameterType> nextAnd = nextValues.remove(0);
IQueryParameterType nextOr = nextAnd.remove(0);
String nextOrValue = nextOr.getValueAsQueryToken(myContext);
if (comboParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE) {
if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.STRING) {
nextOrValue = StringUtil.normalizeStringForSearchIndexing(nextOrValue);
}
}
nextOrValue = UrlUtil.escapeUrlParam(nextOrValue);
if (first) {
@ -1061,18 +1095,25 @@ public class SearchBuilder implements ISearchBuilder {
if (sb != null) {
String indexString = sb.toString();
ourLog.debug("Checking for unique index for query: {}", indexString);
ourLog.debug("Checking for {} combo index for query: {}", comboParam.getComboSearchParamType(), indexString);
// Interceptor broadcast: JPA_PERFTRACE_INFO
StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage("Using unique index for query for search: " + indexString);
.setMessage("Using " + comboParam.getComboSearchParamType() + " index for query for search: " + indexString);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params);
switch (comboParam.getComboSearchParamType()) {
case UNIQUE:
theQueryStack3.addPredicateCompositeUnique(indexString, myRequestPartitionId);
break;
case NON_UNIQUE:
theQueryStack3.addPredicateCompositeNonUnique(indexString, myRequestPartitionId);
break;
}
// Remove any empty parameters remaining after this
theParams.clean();

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 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.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
public class ComboNonUniqueSearchParameterPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnIndexString;
/**
* Constructor
*/
public ComboNonUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_IDX_CMB_TOK_NU"));
myColumnIndexString = getTable().addColumn("IDX_STRING");
}
public Condition createPredicateHashComplete(RequestPartitionId theRequestPartitionId, String theIndexString) {
BinaryCondition predicate = BinaryCondition.equalTo(myColumnIndexString, generatePlaceholder(theIndexString));
return combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
}
}

View File

@ -26,14 +26,14 @@ import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
public class CompositeUniqueSearchParameterPredicateBuilder extends BaseSearchParamPredicateBuilder {
public class ComboUniqueSearchParameterPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnString;
/**
* Constructor
*/
public CompositeUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
public ComboUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_IDX_CMP_STRING_UNIQ"));
myColumnString = getTable().addColumn("IDX_STRING");

View File

@ -27,7 +27,8 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboNonUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
@ -145,12 +146,20 @@ public class SearchQueryBuilder {
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a Composite Unique search parameter
*/
public CompositeUniqueSearchParameterPredicateBuilder addCompositeUniquePredicateBuilder() {
CompositeUniqueSearchParameterPredicateBuilder retVal = mySqlBuilderFactory.newCompositeUniqueSearchParameterPredicateBuilder(this);
public ComboUniqueSearchParameterPredicateBuilder addComboUniquePredicateBuilder() {
ComboUniqueSearchParameterPredicateBuilder retVal = mySqlBuilderFactory.newComboUniqueSearchParameterPredicateBuilder(this);
addTable(retVal, null);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a Composite Unique search parameter
*/
public ComboNonUniqueSearchParameterPredicateBuilder addComboNonUniquePredicateBuilder() {
ComboNonUniqueSearchParameterPredicateBuilder retVal = mySqlBuilderFactory.newComboNonUniqueSearchParameterPredicateBuilder(this);
addTable(retVal, null);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a COORDS search parameter

View File

@ -21,7 +21,8 @@ package ca.uhn.fhir.jpa.search.builder.sql;
*/
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboNonUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ComboUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
@ -45,10 +46,15 @@ public class SqlObjectFactory {
@Autowired
private ApplicationContext myApplicationContext;
public CompositeUniqueSearchParameterPredicateBuilder newCompositeUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(CompositeUniqueSearchParameterPredicateBuilder.class, theSearchSqlBuilder);
public ComboUniqueSearchParameterPredicateBuilder newComboUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(ComboUniqueSearchParameterPredicateBuilder.class, theSearchSqlBuilder);
}
public ComboNonUniqueSearchParameterPredicateBuilder newComboNonUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(ComboNonUniqueSearchParameterPredicateBuilder.class, theSearchSqlBuilder);
}
public CoordsPredicateBuilder coordsPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(CoordsPredicateBuilder.class, theSearchSqlBuilder);
}
@ -112,4 +118,5 @@ public class SqlObjectFactory {
public SearchQueryExecutor newSearchQueryExecutor(GeneratedSql theGeneratedSql, Integer theMaxResultsToFetch) {
return myApplicationContext.getBean(SearchQueryExecutor.class, theGeneratedSql, theMaxResultsToFetch);
}
}

View File

@ -13,6 +13,7 @@ import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
@ -161,6 +162,8 @@ public abstract class BaseJpaTest extends BaseTest {
@Autowired
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@Autowired
protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao;
@Autowired
private IdHelperService myIdHelperService;
@Autowired
private MemoryCacheService myMemoryCacheService;
@ -306,6 +309,12 @@ public abstract class BaseJpaTest extends BaseTest {
});
}
protected void logAllNonUniqueIndexes() {
runInTransaction(() -> {
ourLog.info("Non unique indexes:\n * {}", myResourceIndexedComboTokensNonUniqueDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
}
protected void logAllTokenIndexes() {
runInTransaction(() -> {
ourLog.info("Token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));

View File

@ -17,7 +17,7 @@ import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.config.TestDstu3Config;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
@ -150,7 +150,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Qualifier("myCoverageDaoDstu3")
protected IFhirResourceDao<Coverage> myCoverageDao;
@Autowired
protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
protected IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
@Qualifier("myAllergyIntoleranceDaoDstu3")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;

View File

@ -0,0 +1,80 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.util.SpringObjectCaster;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.ArgumentMatchers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public abstract class BaseComboParamsR4Test extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(BaseComboParamsR4Test.class);
@Autowired
protected ISearchParamRegistry mySearchParamRegistry;
protected List<String> myMessages = new ArrayList<>();
private IInterceptorBroadcaster myInterceptorBroadcaster;
@BeforeEach
public void before() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myDaoConfig.setSchedulingDisabled(true);
myDaoConfig.setUniqueIndexesEnabled(true);
myInterceptorBroadcaster = mock(IInterceptorBroadcaster.class);
when(mySrd.getInterceptorBroadcaster()).thenReturn(myInterceptorBroadcaster);
when(mySrd.getServer().getPagingProvider()).thenReturn(new DatabaseBackedPagingProvider());
when(myInterceptorBroadcaster.hasHooks(eq(Pointcut.JPA_PERFTRACE_WARNING))).thenReturn(true);
when(myInterceptorBroadcaster.hasHooks(eq(Pointcut.JPA_PERFTRACE_INFO))).thenReturn(true);
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_INFO), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
HookParams params = t.getArgument(1, HookParams.class);
myMessages.add("INFO " + params.get(StorageProcessingMessage.class).getMessage());
return null;
});
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
HookParams params = t.getArgument(1, HookParams.class);
myMessages.add("WARN " + params.get(StorageProcessingMessage.class).getMessage());
return null;
});
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
HookParams params = t.getArgument(1, HookParams.class);
myMessages.add("REUSING CACHED SEARCH");
return null;
});
}
@AfterEach
public void after() throws Exception {
myModelConfig.setDefaultSearchParamsCanBeOverridden(new ModelConfig().isDefaultSearchParamsCanBeOverridden());
myDaoConfig.setUniqueIndexesCheckedBeforeSave(new DaoConfig().isUniqueIndexesCheckedBeforeSave());
myDaoConfig.setSchedulingDisabled(new DaoConfig().isSchedulingDisabled());
myDaoConfig.setUniqueIndexesEnabled(new DaoConfig().isUniqueIndexesEnabled());
myDaoConfig.setReindexThreadCount(new DaoConfig().getReindexThreadCount());
ResourceReindexingSvcImpl svc = SpringObjectCaster.getTargetObject(myResourceReindexingSvc, ResourceReindexingSvcImpl.class);
svc.initExecutor();
}
protected void logCapturedMessages() {
ourLog.info("Messages:\n {}", String.join("\n ", myMessages));
}
}

View File

@ -26,7 +26,8 @@ import ca.uhn.fhir.jpa.dao.data.IMdmLinkDao;
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
@ -228,7 +229,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@Autowired
protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
protected IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao;
@Autowired
@Qualifier("myAllergyIntoleranceDaoR4")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;

View File

@ -0,0 +1,194 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.Test;
import java.util.Comparator;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4Test {
private void createNamesAndGenderSp() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-family");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("family");
sp.setExpression("Patient.name.family + '|'");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-given");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("given");
sp.setExpression("Patient.name.given");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("gender");
sp.setExpression("Patient.gender");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-names-and-gender");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-family");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-given");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-gender");
sp.addExtension()
.setUrl(HapiExtensions.EXT_SP_UNIQUE)
.setValue(new BooleanType(false));
mySearchParameterDao.update(sp);
mySearchParamRegistry.forceRefresh();
myMessages.clear();
}
@Test
public void testCreateAndUse() {
createNamesAndGenderSp();
IIdType id1 = createPatient1();
assertNotNull(id1);
IIdType id2 = createPatient2();
assertNotNull(id2);
logAllNonUniqueIndexes();
runInTransaction(() -> {
List<ResourceIndexedComboTokenNonUnique> indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll();
indexedTokens.sort(Comparator.comparing(t -> t.getId()));
assertEquals(2, indexedTokens.size());
assertEquals(-7504889232313729794L, indexedTokens.get(0).getHashComplete().longValue());
});
myMessages.clear();
SearchParameterMap params = SearchParameterMap.newSynchronous();
params.add("family", new StringParam("fAmIlY1|")); // weird casing to test normalization
params.add("given", new StringParam("gIVEn1"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.search(params, mySrd);
List<String> actual = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueries();
assertThat(actual, containsInAnyOrder(id1.toUnqualifiedVersionless().getValue()));
String sql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false);
assertEquals("SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 WHERE (t0.IDX_STRING = 'Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1')", sql);
logCapturedMessages();
assertThat(myMessages.toString(), containsString("[INFO Using NON_UNIQUE index for query for search: Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1]"));
myMessages.clear();
// Remove 1, add another
myPatientDao.delete(id1);
IIdType id3 = createPatient1();
assertNotNull(id3);
params = SearchParameterMap.newSynchronous();
params.add("family", new StringParam("fAmIlY1|")); // weird casing to test normalization
params.add("given", new StringParam("gIVEn1"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
results = myPatientDao.search(params, mySrd);
actual = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueries();
assertThat(actual, containsInAnyOrder(id3.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchWithExtraParameters() {
createNamesAndGenderSp();
IIdType id1 = createPatient1();
assertNotNull(id1);
IIdType id2 = createPatient2();
assertNotNull(id2);
logAllNonUniqueIndexes();
runInTransaction(() -> {
List<ResourceIndexedComboTokenNonUnique> indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll();
indexedTokens.sort(Comparator.comparing(t -> t.getId()));
assertEquals(2, indexedTokens.size());
assertEquals(-7504889232313729794L, indexedTokens.get(0).getHashComplete().longValue());
});
myMessages.clear();
SearchParameterMap params = SearchParameterMap.newSynchronous();
params.add("family", new StringParam("fAmIlY1|")); // weird casing to test normalization
params.add("given", new StringParam("gIVEn1"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2021-02-02"));
myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.search(params, mySrd);
List<String> actual = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueries();
assertThat(actual, containsInAnyOrder(id1.toUnqualifiedVersionless().getValue()));
String sql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false);
assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 LEFT OUTER JOIN HFJ_IDX_CMB_TOK_NU t0 ON (t1.RES_ID = t0.RES_ID) LEFT OUTER JOIN HFJ_SPIDX_DATE t2 ON (t1.RES_ID = t2.RES_ID) WHERE ((t0.IDX_STRING = 'Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1') AND ((t2.HASH_IDENTITY = '5247847184787287691') AND ((t2.SP_VALUE_LOW_DATE_ORDINAL >= '20210202') AND (t2.SP_VALUE_HIGH_DATE_ORDINAL <= '20210202'))))", sql);
logCapturedMessages();
assertThat(myMessages.toString(), containsString("[INFO Using NON_UNIQUE index for query for search: Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1]"));
myMessages.clear();
}
private IIdType createPatient2() {
Patient pt2 = new Patient();
pt2.getNameFirstRep().setFamily("Family2").addGiven("Given2");
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2021-02-02"));
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualified();
return id2;
}
private IIdType createPatient1() {
Patient pt1 = new Patient();
pt1.getNameFirstRep().setFamily("Family1").addGiven("Given1");
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2021-02-02"));
return myPatientDao.create(pt1).getId().toUnqualified();
}
}

View File

@ -1,19 +1,12 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.jpa.util.SpringObjectCaster;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
@ -27,17 +20,12 @@ import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -57,56 +45,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class);
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
private IInterceptorBroadcaster myInterceptorBroadcaster;
private List<String> myMessages = new ArrayList<>();
@AfterEach
public void after() throws Exception {
myModelConfig.setDefaultSearchParamsCanBeOverridden(new ModelConfig().isDefaultSearchParamsCanBeOverridden());
myDaoConfig.setUniqueIndexesCheckedBeforeSave(new DaoConfig().isUniqueIndexesCheckedBeforeSave());
myDaoConfig.setSchedulingDisabled(new DaoConfig().isSchedulingDisabled());
myDaoConfig.setUniqueIndexesEnabled(new DaoConfig().isUniqueIndexesEnabled());
myDaoConfig.setReindexThreadCount(new DaoConfig().getReindexThreadCount());
ResourceReindexingSvcImpl svc = SpringObjectCaster.getTargetObject(myResourceReindexingSvc, ResourceReindexingSvcImpl.class);
svc.initExecutor();
}
@BeforeEach
public void before() {
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myDaoConfig.setSchedulingDisabled(true);
myDaoConfig.setUniqueIndexesEnabled(true);
myInterceptorBroadcaster = mock(IInterceptorBroadcaster.class);
when(mySrd.getInterceptorBroadcaster()).thenReturn(myInterceptorBroadcaster);
when(mySrd.getServer().getPagingProvider()).thenReturn(new DatabaseBackedPagingProvider());
when(myInterceptorBroadcaster.hasHooks(eq(Pointcut.JPA_PERFTRACE_WARNING))).thenReturn(true);
when(myInterceptorBroadcaster.hasHooks(eq(Pointcut.JPA_PERFTRACE_INFO))).thenReturn(true);
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_INFO), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
HookParams params = t.getArgument(1, HookParams.class);
myMessages.add("INFO " + params.get(StorageProcessingMessage.class).getMessage());
return null;
});
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
HookParams params = t.getArgument(1, HookParams.class);
myMessages.add("WARN " + params.get(StorageProcessingMessage.class).getMessage());
return null;
});
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
HookParams params = t.getArgument(1, HookParams.class);
myMessages.add("REUSING CACHED SEARCH");
return null;
});
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ComboUniqueParamTest.class);
private void createUniqueBirthdateAndGenderSps() {
@ -647,7 +589,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
// Make sure entries are saved
runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(2, all.size());
});
@ -691,7 +633,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) {
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(2, all.size());
}
});
@ -706,7 +648,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
ResourceReindexingSvcImpl svc = SpringObjectCaster.getTargetObject(myResourceReindexingSvc, ResourceReindexingSvcImpl.class);
svc.initExecutor();
List<RuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams("Observation");
List<RuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveComboSearchParams("Observation");
assertEquals(0, uniqueSearchParams.size());
Patient pt1 = new Patient();
@ -735,7 +677,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
createUniqueObservationSubjectDateCode();
uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams("Observation");
uniqueSearchParams = mySearchParamRegistry.getActiveComboSearchParams("Observation");
assertEquals(1, uniqueSearchParams.size());
assertEquals(3, uniqueSearchParams.get(0).getComponents().size());
@ -745,7 +687,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertEquals(0, myResourceReindexingSvc.forceReindexingPass());
runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size(), uniques.toString());
assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart())));
assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString());
@ -753,7 +695,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myResourceIndexedCompositeStringUniqueDao.deleteAll();
});
assertEquals(1, mySearchParamRegistry.getActiveUniqueSearchParams("Observation").size());
assertEquals(1, mySearchParamRegistry.getActiveComboSearchParams("Observation").size());
myResourceReindexingSvc.markAllResourcesForReindexing("Observation");
assertEquals(1, myResourceReindexingSvc.forceReindexingPass());
@ -762,7 +704,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertEquals(0, myResourceReindexingSvc.forceReindexingPass());
runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size(), uniques.toString());
assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart())));
assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString());
@ -833,7 +775,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) {
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(2, all.size());
}
});
@ -873,7 +815,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
});
runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
ourLog.info("** Uniques: {}", uniques);
assertEquals(1, uniques.size(), uniques.toString());
assertEquals("Coverage/" + id3.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
@ -1042,7 +984,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) {
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, all.size(), all.toString());
}
});
@ -1074,7 +1016,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
assertThat(myMessages.toString(), containsString("Using UNIQUE index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
myMessages.clear();
}
@ -1102,7 +1044,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
String searchId = results.getUuid();
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1));
logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
assertThat(myMessages.toString(), containsString("Using UNIQUE index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
myMessages.clear();
// Other order
@ -1126,7 +1068,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
results = myPatientDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results), empty());
logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-03&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
assertThat(myMessages.toString(), containsString("Using UNIQUE index for query for search: Patient?birthdate=2011-01-03&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
myMessages.clear();
myMessages.clear();
@ -1151,7 +1093,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size(), uniques.toString());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString());
@ -1161,7 +1103,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
public void testUniqueValuesAreIndexed_RefAndDateAndToken() {
createUniqueObservationSubjectDateCode();
List<ResourceIndexedCompositeStringUnique> uniques;
List<ResourceIndexedComboStringUnique> uniques;
uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(0, uniques.size(), uniques.toString());
@ -1190,7 +1132,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
@Test
public void testUniqueValuesAreIndexed_Reference_UsingModifierSyntax() {
createUniqueNameAndManagingOrganizationSps();
List<ResourceIndexedCompositeStringUnique> uniques;
List<ResourceIndexedComboStringUnique> uniques;
Organization org = new Organization();
org.setId("Organization/ORG");
@ -1204,7 +1146,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG", mySrd).getId().toUnqualifiedVersionless();
logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"));
assertThat(myMessages.toString(), containsString("Using UNIQUE index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"));
myMessages.clear();
uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
@ -1221,7 +1163,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG", mySrd).getId().toUnqualifiedVersionless();
logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"));
assertThat(myMessages.toString(), containsString("Using UNIQUE index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"));
myMessages.clear();
uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
@ -1231,10 +1173,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
}
private void logCapturedMessages() {
ourLog.info("Messages:\n {}", String.join("\n ", myMessages));
}
@Test
public void testUniqueValuesAreIndexed_StringAndReference() {
createUniqueNameAndManagingOrganizationSps();
@ -1253,7 +1191,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
pt1.setManagingOrganization(new Reference("Organization/ORG"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
Collections.sort(uniques);
assertEquals(3, uniques.size());
@ -1270,7 +1208,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
@Test
public void testUniqueValuesAreIndexed_StringAndReference_UsingConditional() {
createUniqueNameAndManagingOrganizationSps();
List<ResourceIndexedCompositeStringUnique> uniques;
List<ResourceIndexedComboStringUnique> uniques;
Organization org = new Organization();
org.setId("Organization/ORG");
@ -1303,7 +1241,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
@Test
public void testUniqueValuesAreIndexed_StringAndReference_UsingConditionalInTransaction() {
createUniqueNameAndManagingOrganizationSps();
List<ResourceIndexedCompositeStringUnique> uniques;
List<ResourceIndexedComboStringUnique> uniques;
Organization org = new Organization();
org.setId("Organization/ORG");
@ -1385,7 +1323,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
createUniqueBirthdateAndGenderSps();
Patient pt;
List<ResourceIndexedCompositeStringUnique> uniques;
List<ResourceIndexedComboStringUnique> uniques;
pt = new Patient();
pt.setGender(Enumerations.AdministrativeGender.MALE);
@ -1416,7 +1354,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
org.setName("ORG");
myOrganizationDao.update(org);
List<ResourceIndexedCompositeStringUnique> uniques;
List<ResourceIndexedComboStringUnique> uniques;
Patient pt;
pt = new Patient();
@ -1464,7 +1402,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myResourceReindexingSvc.forceReindexingPass();
myResourceReindexingSvc.forceReindexingPass();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size(), uniques.toString());
assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString());
@ -1486,10 +1424,10 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<RuntimeSearchParam> params = mySearchParamRegistry.getActiveUniqueSearchParams("Patient");
List<RuntimeSearchParam> params = mySearchParamRegistry.getActiveComboSearchParams("Patient");
assertEquals(1, params.size());
assertEquals(params.get(0).isUnique(), true);
assertEquals(ComboSearchParamType.UNIQUE, params.get(0).getComboSearchParamType());
assertEquals(2, params.get(0).getComponents().size());
// Should be alphabetical order
@ -1565,7 +1503,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id2.getValue()));
logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
assertThat(myMessages.toString(), containsString("Using UNIQUE index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
myMessages.clear();
}

View File

@ -12,7 +12,7 @@ import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
@ -435,7 +435,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
assertEquals(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate());
// HFJ_IDX_CMP_STRING_UNIQ
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId);
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId);
assertEquals(1, uniques.size());
assertEquals(myPartitionId, uniques.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate());
@ -519,7 +519,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
assertEquals(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate());
// HFJ_IDX_CMP_STRING_UNIQ
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId);
List<ResourceIndexedComboStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId);
assertEquals(1, uniques.size());
assertEquals(null, uniques.get(0).getPartitionId().getPartitionId());
assertEquals(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate());

View File

@ -327,7 +327,7 @@ public class SearchParamExtractorR4Test {
public void testExtensionContainingReference() {
String path = "Patient.extension('http://patext').value.as(Reference)";
RuntimeSearchParam sp = new RuntimeSearchParam(null, null, "extpat", "Patient SP", path, RestSearchParameterTypeEnum.REFERENCE, new HashSet<>(), Sets.newHashSet("Patient"), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null);
RuntimeSearchParam sp = new RuntimeSearchParam(null, null, "extpat", "Patient SP", path, RestSearchParameterTypeEnum.REFERENCE, new HashSet<>(), Sets.newHashSet("Patient"), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null);
mySearchParamRegistry.addSearchParam(sp);
Patient patient = new Patient();
@ -440,7 +440,7 @@ public class SearchParamExtractorR4Test {
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
throw new UnsupportedOperationException();
}
@ -451,7 +451,7 @@ public class SearchParamExtractorR4Test {
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
throw new UnsupportedOperationException();
}

View File

@ -22,7 +22,7 @@ import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
@ -185,7 +185,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@Autowired
protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
protected IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
@Qualifier("myAllergyIntoleranceDaoR5")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;

View File

@ -102,6 +102,21 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
version.onTable("HFJ_IDX_CMP_STRING_UNIQ")
.modifyColumn("20210713.1","IDX_STRING").nonNullable().withType(ColumnTypeEnum.STRING, 500);
version.onTable("HFJ_RESOURCE")
.addColumn("20210720.1", "SP_CMPTOKS_PRESENT").nullable().type(ColumnTypeEnum.BOOLEAN);
version.addIdGenerator("20210720.2", "SEQ_IDXCMBTOKNU_ID");
Builder.BuilderAddTableByColumns cmpToks = version
.addTableByColumns("20210720.3", "HFJ_IDX_CMB_TOK_NU", "PID");
cmpToks.addColumn("PID").nonNullable().type(ColumnTypeEnum.LONG);
cmpToks.addColumn("RES_ID").nonNullable().type(ColumnTypeEnum.LONG);
cmpToks.addColumn("HASH_COMPLETE").nonNullable().type(ColumnTypeEnum.LONG);
cmpToks.addColumn("IDX_STRING").nonNullable().type(ColumnTypeEnum.STRING, 500);
cmpToks.addForeignKey("20210720.4", "FK_IDXCMBTOKNU_RES_ID").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
cmpToks.addIndex("20210720.5", "IDX_IDXCMBTOKNU_STR").unique(false).withColumns("IDX_STRING");
cmpToks.addIndex("20210720.6", "IDX_IDXCMBTOKNU_RES").unique(false).withColumns("RES_ID");
}
private void init540() {

View File

@ -43,6 +43,7 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import java.util.Date;
import java.util.List;
@MappedSuperclass
public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
@ -182,6 +183,17 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
}
public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, List<String> theAdditionalValues) {
String[] values = new String[theAdditionalValues.size() + 2];
values[0] = theResourceType;
values[1] = theParamName;
for (int i = 0; i < theAdditionalValues.size(); i++) {
values[i + 2] = theAdditionalValues.get(i);
}
return hash(thePartitionSettings, theRequestPartitionId, values);
}
/**
* Applies a fast and consistent hashing algorithm to a set of strings
*/

View File

@ -32,10 +32,10 @@ import javax.persistence.*;
@Entity()
@Table(name = "HFJ_IDX_CMP_STRING_UNIQ", indexes = {
@Index(name = ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING, columnList = "IDX_STRING", unique = true),
@Index(name = ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_RESOURCE, columnList = "RES_ID", unique = false)
@Index(name = ResourceIndexedComboStringUnique.IDX_IDXCMPSTRUNIQ_STRING, columnList = "IDX_STRING", unique = true),
@Index(name = ResourceIndexedComboStringUnique.IDX_IDXCMPSTRUNIQ_RESOURCE, columnList = "RES_ID", unique = false)
})
public class ResourceIndexedCompositeStringUnique extends BasePartitionable implements Comparable<ResourceIndexedCompositeStringUnique> {
public class ResourceIndexedComboStringUnique extends BasePartitionable implements Comparable<ResourceIndexedComboStringUnique> {
public static final int MAX_STRING_LENGTH = 500;
public static final String IDX_IDXCMPSTRUNIQ_STRING = "IDX_IDXCMPSTRUNIQ_STRING";
@ -66,14 +66,14 @@ public class ResourceIndexedCompositeStringUnique extends BasePartitionable impl
/**
* Constructor
*/
public ResourceIndexedCompositeStringUnique() {
public ResourceIndexedComboStringUnique() {
super();
}
/**
* Constructor
*/
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString, IIdType theSearchParameterId) {
public ResourceIndexedComboStringUnique(ResourceTable theResource, String theIndexString, IIdType theSearchParameterId) {
setResource(theResource);
setIndexString(theIndexString);
setPartitionId(theResource.getPartitionId());
@ -81,7 +81,7 @@ public class ResourceIndexedCompositeStringUnique extends BasePartitionable impl
}
@Override
public int compareTo(ResourceIndexedCompositeStringUnique theO) {
public int compareTo(ResourceIndexedComboStringUnique theO) {
CompareToBuilder b = new CompareToBuilder();
b.append(myIndexString, theO.getIndexString());
return b.toComparison();
@ -91,11 +91,11 @@ public class ResourceIndexedCompositeStringUnique extends BasePartitionable impl
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof ResourceIndexedCompositeStringUnique)) {
if (!(theO instanceof ResourceIndexedComboStringUnique)) {
return false;
}
ResourceIndexedCompositeStringUnique that = (ResourceIndexedCompositeStringUnique) theO;
ResourceIndexedComboStringUnique that = (ResourceIndexedComboStringUnique) theO;
return new EqualsBuilder()
.append(myIndexString, that.myIndexString)

View File

@ -0,0 +1,190 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR JPA Model
* %%
* Copyright (C) 2014 - 2021 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.model.config.PartitionSettings;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import static ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam.hash;
@Entity
@Table(name = "HFJ_IDX_CMB_TOK_NU", indexes = {
@Index(name = "IDX_IDXCMBTOKNU_STR", columnList = "IDX_STRING", unique = false),
@Index(name = "IDX_IDXCMBTOKNU_RES", columnList = "RES_ID", unique = false)
})
public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implements Comparable<ResourceIndexedComboTokenNonUnique> {
@SequenceGenerator(name = "SEQ_IDXCMBTOKNU_ID", sequenceName = "SEQ_IDXCMBTOKNU_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_IDXCMBTOKNU_ID")
@Id
@Column(name = "PID")
private Long myId;
@ManyToOne
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_IDXCMBTOKNU_RES_ID"))
private ResourceTable myResource;
@Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourceId;
@Column(name = "HASH_COMPLETE", nullable = false)
private Long myHashComplete;
@Column(name = "IDX_STRING", nullable = false, length = ResourceIndexedComboStringUnique.MAX_STRING_LENGTH)
private String myIndexString;
@Transient
private transient PartitionSettings myPartitionSettings;
/**
* Constructor
*/
public ResourceIndexedComboTokenNonUnique() {
super();
}
public ResourceIndexedComboTokenNonUnique(PartitionSettings thePartitionSettings, ResourceTable theEntity, String theQueryString) {
myPartitionSettings = thePartitionSettings;
myResource = theEntity;
myIndexString = theQueryString;
}
public String getIndexString() {
return myIndexString;
}
public void setIndexString(String theIndexString) {
myIndexString = theIndexString;
}
@Override
public boolean equals(Object theO) {
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
return false;
}
ResourceIndexedComboTokenNonUnique that = (ResourceIndexedComboTokenNonUnique) theO;
return new EqualsBuilder()
.append(myResource, that.myResource)
.append(myHashComplete, that.myHashComplete)
.isEquals();
}
@Override
public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
throw new IllegalStateException();
}
@Override
public Long getId() {
return myId;
}
@Override
public void setId(Long theId) {
myId = theId;
}
@Override
public void calculateHashes() {
PartitionSettings partitionSettings = getPartitionSettings();
PartitionablePartitionId partitionId = getPartitionId();
String queryString = myIndexString;
setHashComplete(calculateHashComplete(partitionSettings, partitionId, queryString));
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myResource)
.append(myHashComplete)
.toHashCode();
}
public PartitionSettings getPartitionSettings() {
return myPartitionSettings;
}
public ResourceTable getResource() {
return myResource;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
public Long getHashComplete() {
return myHashComplete;
}
public void setHashComplete(Long theHashComplete) {
myHashComplete = theHashComplete;
}
@Override
public int compareTo(ResourceIndexedComboTokenNonUnique theO) {
CompareToBuilder b = new CompareToBuilder();
b.append(myHashComplete, theO.getHashComplete());
return b.toComparison();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", myId)
.append("resourceId", myResourceId)
.append("hashComplete", myHashComplete)
.append("indexString", myIndexString)
.toString();
}
public static long calculateHashComplete(PartitionSettings partitionSettings, PartitionablePartitionId thePartitionId, String queryString) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(thePartitionId);
return hash(partitionSettings, requestPartitionId, queryString);
}
public static long calculateHashComplete(PartitionSettings partitionSettings, RequestPartitionId partitionId, String queryString) {
return hash(partitionSettings, partitionId, queryString);
}
}

View File

@ -192,11 +192,20 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
// Added in 3.0.0 - Should make this a primitive Boolean at some point
@OptimisticLock(excluded = true)
@Column(name = "SP_CMPSTR_UNIQ_PRESENT")
private Boolean myParamsCompositeStringUniquePresent = false;
private Boolean myParamsComboStringUniquePresent = false;
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
private Collection<ResourceIndexedCompositeStringUnique> myParamsCompositeStringUnique;
private Collection<ResourceIndexedComboStringUnique> myParamsComboStringUnique;
// Added in 5.5.0 - Should make this a primitive Boolean at some point
@OptimisticLock(excluded = true)
@Column(name = "SP_CMPTOKS_PRESENT")
private Boolean myParamsComboTokensNonUniquePresent = false;
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
private Collection<ResourceIndexedComboTokenNonUnique> myParamsComboTokensNonUnique;
@OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
@ -312,11 +321,18 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
myLanguage = theLanguage;
}
public Collection<ResourceIndexedCompositeStringUnique> getParamsCompositeStringUnique() {
if (myParamsCompositeStringUnique == null) {
myParamsCompositeStringUnique = new ArrayList<>();
public Collection<ResourceIndexedComboStringUnique> getParamsComboStringUnique() {
if (myParamsComboStringUnique == null) {
myParamsComboStringUnique = new ArrayList<>();
}
return myParamsCompositeStringUnique;
return myParamsComboStringUnique;
}
public Collection<ResourceIndexedComboTokenNonUnique> getmyParamsComboTokensNonUnique() {
if (myParamsComboTokensNonUnique == null) {
myParamsComboTokensNonUnique = new ArrayList<>();
}
return myParamsComboTokensNonUnique;
}
public Collection<ResourceIndexedSearchParamCoords> getParamsCoords() {
@ -494,15 +510,26 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
myHasLinks = theHasLinks;
}
public boolean isParamsCompositeStringUniquePresent() {
if (myParamsCompositeStringUniquePresent == null) {
public boolean isParamsComboStringUniquePresent() {
if (myParamsComboStringUniquePresent == null) {
return false;
}
return myParamsCompositeStringUniquePresent;
return myParamsComboStringUniquePresent;
}
public void setParamsCompositeStringUniquePresent(boolean theParamsCompositeStringUniquePresent) {
myParamsCompositeStringUniquePresent = theParamsCompositeStringUniquePresent;
public void setParamsComboStringUniquePresent(boolean theParamsComboStringUniquePresent) {
myParamsComboStringUniquePresent = theParamsComboStringUniquePresent;
}
public boolean isParamsComboTokensNonUniquePresent() {
if (myParamsComboTokensNonUniquePresent == null) {
return false;
}
return myParamsComboTokensNonUniquePresent;
}
public void setParamsComboTokensNonUniquePresent(boolean theParamsComboTokensNonUniquePresent) {
myParamsComboStringUniquePresent = theParamsComboTokensNonUniquePresent;
}
public boolean isParamsCoordsPopulated() {

View File

@ -93,7 +93,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
public static final Set<String> COORDS_INDEX_PATHS;
private static final Pattern SPLIT = Pattern.compile("\\||( or )");
private static final Pattern SPLIT_R4 = Pattern.compile("\\|");
private static final Pattern SPLIT_R4 = Pattern.compile("\\s+\\|");
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
static {

View File

@ -26,7 +26,8 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@ -70,7 +71,8 @@ public final class ResourceIndexedSearchParams {
final public Collection<ResourceIndexedSearchParamUri> myUriParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamCoords> myCoordsParams = new ArrayList<>();
final public Collection<ResourceIndexedCompositeStringUnique> myCompositeStringUniques = new HashSet<>();
final public Collection<ResourceIndexedComboStringUnique> myComboStringUniques = new HashSet<>();
final public Collection<ResourceIndexedComboTokenNonUnique> myComboTokenNonUnique = new HashSet<>();
final public Collection<ResourceLink> myLinks = new HashSet<>();
final public Set<String> myPopulatedResourceLinkParameters = new HashSet<>();
@ -106,8 +108,11 @@ public final class ResourceIndexedSearchParams {
myLinks.addAll(theEntity.getResourceLinks());
}
if (theEntity.isParamsCompositeStringUniquePresent()) {
myCompositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
if (theEntity.isParamsComboStringUniquePresent()) {
myComboStringUniques.addAll(theEntity.getParamsComboStringUnique());
}
if (theEntity.isParamsComboTokensNonUniquePresent()) {
myComboTokenNonUnique.addAll(theEntity.getmyParamsComboTokensNonUnique());
}
}
@ -125,7 +130,7 @@ public final class ResourceIndexedSearchParams {
theEntity.setParamsDatePopulated(myDateParams.isEmpty() == false);
theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false);
theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false);
theEntity.setParamsCompositeStringUniquePresent(myCompositeStringUniques.isEmpty() == false);
theEntity.setParamsComboStringUniquePresent(myComboStringUniques.isEmpty() == false);
theEntity.setHasLinks(myLinks.isEmpty() == false);
}
@ -305,7 +310,8 @@ public final class ResourceIndexedSearchParams {
", dateParams=" + myDateParams +
", uriParams=" + myUriParams +
", coordsParams=" + myCoordsParams +
", compositeStringUniques=" + myCompositeStringUniques +
", comboStringUniques=" + myComboStringUniques +
", comboTokenNonUniques=" + myComboTokenNonUnique +
", links=" + myLinks +
'}';
}
@ -431,6 +437,9 @@ public final class ResourceIndexedSearchParams {
List<String> values = new ArrayList<>();
Set<String> queryStringsToPopulate = new HashSet<>();
extractCompositeStringUniquesValueChains(theResourceType, thePartsChoices, values, queryStringsToPopulate);
values.removeIf(StringUtils::isBlank);
return queryStringsToPopulate;
}

View File

@ -40,26 +40,25 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaSearchParamCache {
private static final Logger ourLog = LoggerFactory.getLogger(JpaSearchParamCache.class);
private volatile Map<String, List<RuntimeSearchParam>> myActiveUniqueSearchParams = Collections.emptyMap();
private volatile Map<String, Map<Set<String>, List<RuntimeSearchParam>>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap();
private volatile Map<String, List<RuntimeSearchParam>> myActiveComboSearchParams = Collections.emptyMap();
private volatile Map<String, Map<Set<String>, List<RuntimeSearchParam>>> myActiveParamNamesToComboSearchParams = Collections.emptyMap();
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
List<RuntimeSearchParam> retval = myActiveUniqueSearchParams.get(theResourceName);
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
List<RuntimeSearchParam> retval = myActiveComboSearchParams.get(theResourceName);
if (retval == null) {
retval = Collections.emptyList();
}
return retval;
}
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
Map<Set<String>, List<RuntimeSearchParam>> paramNamesToParams = myActiveParamNamesToUniqueSearchParams.get(theResourceName);
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
Map<Set<String>, List<RuntimeSearchParam>> paramNamesToParams = myActiveParamNamesToComboSearchParams.get(theResourceName);
if (paramNamesToParams == null) {
return Collections.emptyList();
}
@ -72,8 +71,8 @@ public class JpaSearchParamCache {
}
void populateActiveSearchParams(IInterceptorService theInterceptorBroadcaster, IPhoneticEncoder theDefaultPhoneticEncoder, RuntimeSearchParamCache theActiveSearchParams) {
Map<String, List<RuntimeSearchParam>> activeUniqueSearchParams = new HashMap<>();
Map<String, Map<Set<String>, List<RuntimeSearchParam>>> activeParamNamesToUniqueSearchParams = new HashMap<>();
Map<String, List<RuntimeSearchParam>> resourceNameToComboSearchParams = new HashMap<>();
Map<String, Map<Set<String>, List<RuntimeSearchParam>>> activeParamNamesToComboSearchParams = new HashMap<>();
Map<String, RuntimeSearchParam> idToRuntimeSearchParam = new HashMap<>();
List<RuntimeSearchParam> jpaSearchParams = new ArrayList<>();
@ -83,7 +82,7 @@ public class JpaSearchParamCache {
*/
for (String theResourceName : theActiveSearchParams.getResourceNameKeys()) {
Map<String, RuntimeSearchParam> searchParamMap = theActiveSearchParams.getSearchParamMap(theResourceName);
List<RuntimeSearchParam> uniqueSearchParams = activeUniqueSearchParams.computeIfAbsent(theResourceName, k -> new ArrayList<>());
List<RuntimeSearchParam> comboSearchParams = resourceNameToComboSearchParams.computeIfAbsent(theResourceName, k -> new ArrayList<>());
Collection<RuntimeSearchParam> nextSearchParamsForResourceName = searchParamMap.values();
ourLog.trace("Resource {} has {} params", theResourceName, searchParamMap.size());
@ -99,10 +98,9 @@ public class JpaSearchParamCache {
idToRuntimeSearchParam.put(nextCandidate.getUri(), nextCandidate);
}
RuntimeSearchParam nextCandidateCasted = nextCandidate;
jpaSearchParams.add(nextCandidateCasted);
if (nextCandidateCasted.isUnique()) {
uniqueSearchParams.add(nextCandidateCasted);
jpaSearchParams.add(nextCandidate);
if (nextCandidate.getComboSearchParamType() != null) {
comboSearchParams.add(nextCandidate);
}
setPhoneticEncoder(theDefaultPhoneticEncoder, nextCandidate);
@ -137,19 +135,19 @@ public class JpaSearchParamCache {
}
}
if (next.isUnique()) {
if (next.getComboSearchParamType() != null) {
for (String nextBase : next.getBase()) {
activeParamNamesToUniqueSearchParams.computeIfAbsent(nextBase, v -> new HashMap<>());
activeParamNamesToUniqueSearchParams.get(nextBase).computeIfAbsent(paramNames, t -> new ArrayList<>());
activeParamNamesToUniqueSearchParams.get(nextBase).get(paramNames).add(next);
activeParamNamesToComboSearchParams.computeIfAbsent(nextBase, v -> new HashMap<>());
activeParamNamesToComboSearchParams.get(nextBase).computeIfAbsent(paramNames, t -> new ArrayList<>());
activeParamNamesToComboSearchParams.get(nextBase).get(paramNames).add(next);
}
}
}
ourLog.info("Have {} unique search params", activeParamNamesToUniqueSearchParams.size());
ourLog.info("Have {} unique search params", activeParamNamesToComboSearchParams.size());
myActiveUniqueSearchParams = activeUniqueSearchParams;
myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams;
myActiveComboSearchParams = resourceNameToComboSearchParams;
myActiveParamNamesToComboSearchParams = activeParamNamesToComboSearchParams;
}
void setPhoneticEncoder(IPhoneticEncoder theDefaultPhoneticEncoder, RuntimeSearchParam searchParam) {

View File

@ -109,13 +109,13 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry, IResourceC
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
return myJpaSearchParamCache.getActiveUniqueSearchParams(theResourceName);
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
return myJpaSearchParamCache.getActiveComboSearchParams(theResourceName);
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
return myJpaSearchParamCache.getActiveUniqueSearchParams(theResourceName, theParamNames);
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
return myJpaSearchParamCache.getActiveComboSearchParams(theResourceName, theParamNames);
}
@Nullable

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
@ -148,14 +149,16 @@ public class SearchParameterCanonicalizer {
IIdType id = theNextSp.getIdElement();
String uri = "";
boolean unique = false;
ComboSearchParamType unique = null;
List<ExtensionDt> uniqueExts = theNextSp.getUndeclaredExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE);
if (uniqueExts.size() > 0) {
IPrimitiveType<?> uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive();
if (uniqueExtsValuePrimitive != null) {
if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = true;
unique = ComboSearchParamType.UNIQUE;
} else if ("false".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = ComboSearchParamType.NON_UNIQUE;
}
}
}
@ -228,14 +231,16 @@ public class SearchParameterCanonicalizer {
IIdType id = theNextSp.getIdElement();
String uri = "";
boolean unique = false;
ComboSearchParamType unique = null;
List<Extension> uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE);
if (uniqueExts.size() > 0) {
IPrimitiveType<?> uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive();
if (uniqueExtsValuePrimitive != null) {
if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = true;
unique = ComboSearchParamType.UNIQUE;
} else if ("false".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = ComboSearchParamType.NON_UNIQUE;
}
}
}
@ -311,7 +316,7 @@ public class SearchParameterCanonicalizer {
IIdType id = theNextSp.getIdElement();
String uri = terser.getSinglePrimitiveValueOrNull(theNextSp, "url");
boolean unique = false;
ComboSearchParamType unique = null;
String value = ((IBaseHasExtensions) theNextSp).getExtension()
.stream()
@ -322,7 +327,9 @@ public class SearchParameterCanonicalizer {
.findFirst()
.orElse("");
if ("true".equalsIgnoreCase(value)) {
unique = true;
unique = ComboSearchParamType.UNIQUE;
} else if ("false".equalsIgnoreCase(value)) {
unique = ComboSearchParamType.NON_UNIQUE;
}
List<RuntimeSearchParam.Component> components = new ArrayList<>();

View File

@ -132,11 +132,11 @@ public class SearchParamExtractorDstu3Test {
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
extractor.extractSearchParamStrings(resource);
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", null, RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", null, RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
extractor.extractSearchParamStrings(resource);
}
@ -148,7 +148,7 @@ public class SearchParamExtractorDstu3Test {
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "communication.language.coding.system | communication.language.coding.code", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "communication.language.coding.system | communication.language.coding.code", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
resource.getCommunicationFirstRep().getLanguage().getCodingFirstRep().setCode("blah");
Set<ResourceIndexedSearchParamString> strings = extractor.extractSearchParamStrings(resource);
@ -166,37 +166,37 @@ public class SearchParamExtractorDstu3Test {
extractor.start();
{
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> outcome = extractor.extractSearchParamStrings(resource);
assertThat(outcome.getWarnings(), Matchers.contains("Search param foo is of unexpected datatype: class org.hl7.fhir.dstu3.model.Patient"));
}
{
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.TOKEN, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.TOKEN, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> outcome = extractor.extractSearchParamTokens(resource);
assertThat(outcome.getWarnings(), Matchers.contains("Search param foo is of unexpected datatype: class org.hl7.fhir.dstu3.model.Patient"));
}
{
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.QUANTITY, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.QUANTITY, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> outcome = extractor.extractSearchParamQuantity(resource);
assertThat(outcome.getWarnings(), Matchers.contains("Search param foo is of unexpected datatype: class org.hl7.fhir.dstu3.model.Patient"));
}
{
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.DATE, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.DATE, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> outcome = extractor.extractSearchParamDates(resource);
assertThat(outcome.getWarnings(), Matchers.contains("Search param foo is of unexpected datatype: class org.hl7.fhir.dstu3.model.Patient"));
}
{
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.NUMBER, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.NUMBER, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> outcome = extractor.extractSearchParamNumber(resource);
assertThat(outcome.getWarnings(), Matchers.contains("Search param foo is of unexpected datatype: class org.hl7.fhir.dstu3.model.Patient"));
}
{
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.URI, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
searchParamRegistry.addSearchParam(new RuntimeSearchParam(null, null, "foo", "foo", "Patient", RestSearchParameterTypeEnum.URI, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
Patient resource = new Patient();
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> outcome = extractor.extractSearchParamUri(resource);
assertThat(outcome.getWarnings(), Matchers.contains("Search param foo is of unexpected datatype: class org.hl7.fhir.dstu3.model.Patient"));
@ -269,7 +269,7 @@ public class SearchParamExtractorDstu3Test {
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
throw new UnsupportedOperationException();
}
@ -280,7 +280,7 @@ public class SearchParamExtractorDstu3Test {
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
throw new UnsupportedOperationException();
}

View File

@ -282,7 +282,7 @@ public class SearchParamExtractorMegaTest {
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
throw new UnsupportedOperationException();
}
@ -293,7 +293,7 @@ public class SearchParamExtractorMegaTest {
}
@Override
public List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
throw new UnsupportedOperationException();
}

View File

@ -59,13 +59,13 @@ public class InMemoryResourceMatcherR5Test {
@BeforeEach
public void before() {
RuntimeSearchParam dateSearchParam = new RuntimeSearchParam(null, null, null, null, "Observation.effective", RestSearchParameterTypeEnum.DATE, null, null, RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null);
RuntimeSearchParam dateSearchParam = new RuntimeSearchParam(null, null, null, null, "Observation.effective", RestSearchParameterTypeEnum.DATE, null, null, RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null);
when(mySearchParamRegistry.getActiveSearchParam("Observation", "date")).thenReturn(dateSearchParam);
RuntimeSearchParam codeSearchParam = new RuntimeSearchParam(null, null, null, null, "Observation.code", RestSearchParameterTypeEnum.TOKEN, null, null, RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null);
RuntimeSearchParam codeSearchParam = new RuntimeSearchParam(null, null, null, null, "Observation.code", RestSearchParameterTypeEnum.TOKEN, null, null, RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null);
when(mySearchParamRegistry.getActiveSearchParam("Observation", "code")).thenReturn(codeSearchParam);
RuntimeSearchParam encSearchParam = new RuntimeSearchParam(null, null, null, null, "Observation.encounter", RestSearchParameterTypeEnum.REFERENCE, null, null, RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null);
RuntimeSearchParam encSearchParam = new RuntimeSearchParam(null, null, null, null, "Observation.encounter", RestSearchParameterTypeEnum.REFERENCE, null, null, RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null);
when(mySearchParamRegistry.getActiveSearchParam("Observation", "encounter")).thenReturn(encSearchParam);
myObservation = new Observation();

View File

@ -293,7 +293,7 @@ public class SearchParamRegistryImplTest {
@Test
public void testGetActiveUniqueSearchParams_Empty() {
assertThat(mySearchParamRegistry.getActiveUniqueSearchParams("Patient"), is(empty()));
assertThat(mySearchParamRegistry.getActiveComboSearchParams("Patient"), is(empty()));
}
@Test

View File

@ -45,7 +45,7 @@ public class EIDHelperR4Test extends BaseR4Test {
@BeforeEach
public void before() {
when(mySearchParamRetriever.getActiveSearchParam("Patient", "identifier"))
.thenReturn(new RuntimeSearchParam(null, null, "identifier", "Description", "identifier", RestSearchParameterTypeEnum.STRING, new HashSet<>(), new HashSet<>(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, false, null, null));
.thenReturn(new RuntimeSearchParam(null, null, "identifier", "Description", "identifier", RestSearchParameterTypeEnum.STRING, new HashSet<>(), new HashSet<>(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, null, null, null));
myMdmSettings = new MdmSettings(new MdmRuleValidator(ourFhirContext, mySearchParamRetriever)) {
{

View File

@ -496,7 +496,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
Set<String> targets = Collections.emptySet();
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE;
Collection<String> base = Collections.singletonList(theSearchMethodBinding.getResourceName());
RuntimeSearchParam param = new RuntimeSearchParam(id, uri, nextParamName, description, path, type, providesMembershipInCompartments, targets, status, false, null, base);
RuntimeSearchParam param = new RuntimeSearchParam(id, uri, nextParamName, description, path, type, providesMembershipInCompartments, targets, status, null, null, base);
theMapToPopulate.put(nextParamName, param);
}

View File

@ -70,11 +70,11 @@ public interface ISearchParamRegistry {
default void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {
}
default List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
default List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
return Collections.emptyList();
}
default List<RuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
default List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
return Collections.emptyList();
}