diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index e5463754381..aab598b635c 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 665f6a02661..f987d8a961b 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 2ceb0fd2d00..b389f2365b8 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 125aba306fa..0a31642d5c9 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 03667f2a69d..89642243afc 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index 3e0f78b837a..ec787045d82 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index b38bbff564c..8d7e6bf5a6a 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index 3dc23dd132d..9597144c7c4 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../hapi-deployable-pom diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index dda93b942ed..56876d76233 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 194a35b3187..e9c950eba23 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 55a35016dda..85bc2d27664 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 8704b295417..0c28401b81e 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index cdf22a9fb5c..374148b8be4 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index 1ea2df3e7d1..312ad06fb4f 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-add-reindex-instance-op.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-add-reindex-instance-op.yaml new file mode 100644 index 00000000000..315a313fa5c --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-add-reindex-instance-op.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 4699 +title: "Two new operations have been added to the JPA server: + * Instance level $reindex performs a synchronous reindex of a single resource and returns a Parameters object containing all previous and new indexes + * Instance level $reindex-dryrun simulates a reindex and shows the previous and new indexes generated" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-bundlebuilder-on-dstu2.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-bundlebuilder-on-dstu2.yaml new file mode 100644 index 00000000000..33f969c49eb --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-bundlebuilder-on-dstu2.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 4699 +title: "The BundleBuilder utility class did not work with DSTU2 bundles. This has been + corrected." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-combo-nonunique-searchparams-updates.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-combo-nonunique-searchparams-updates.yaml new file mode 100644 index 00000000000..bebfe117feb --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4699-combo-nonunique-searchparams-updates.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 4699 +title: "When updating resource fields targeted by a Combo Non-Unique SearchParameter, previous + indexes were not deleted meaning that old search values could still find the resource. This + has been corrected." diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index a2ba9fd6ca3..146143f6809 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index d39e5b47a0b..d6c780e9fc9 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index e8dfba3ed7f..de40f3e0e93 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 3729f9c7ee7..7a1605aebf9 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java index 9fd5db160b3..2678c339d5b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java @@ -96,6 +96,7 @@ import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc; import ca.uhn.fhir.jpa.provider.DiffProvider; import ca.uhn.fhir.jpa.provider.ProcessMessageProvider; +import ca.uhn.fhir.jpa.provider.InstanceReindexProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; @@ -138,7 +139,9 @@ import ca.uhn.fhir.jpa.search.cache.DatabaseSearchResultCacheSvcImpl; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.elastic.IndexNamePrefixLayoutStrategy; +import ca.uhn.fhir.jpa.search.reindex.IInstanceReindexService; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; +import ca.uhn.fhir.jpa.search.reindex.InstanceReindexServiceImpl; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexer; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl; import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; @@ -376,6 +379,18 @@ public class JpaConfig { return new ResourceReindexingSvcImpl(); } + @Bean + @Lazy + public IInstanceReindexService instanceReindexService() { + return new InstanceReindexServiceImpl(); + } + + @Bean + @Lazy + public InstanceReindexProvider instanceReindexProvider(IInstanceReindexService theInstanceReindexService) { + return new InstanceReindexProvider(theInstanceReindexService); + } + @Bean public ResourceReindexer resourceReindexer(FhirContext theFhirContext) { return new ResourceReindexer(theFhirContext); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 8495fc8e9c5..462c31f1368 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1013,8 +1013,10 @@ public abstract class BaseHapiFhirDao extends BaseStora failIfPartitionMismatch(theRequest, entity); + // Extract search params for resource mySearchParamWithInlineReferencesExtractor.populateFromResource(requestPartitionId, newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing); + // Actually persist the ResourceTable and ResourceHistoryTable entities changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true); if (theForceUpdate) { @@ -1044,11 +1046,11 @@ public abstract class BaseHapiFhirDao extends BaseStora } else { - changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, false); - entity.setUpdated(theTransactionDetails.getTransactionDate()); entity.setIndexStatus(null); + changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, false); + } } @@ -1105,9 +1107,7 @@ public abstract class BaseHapiFhirDao extends BaseStora * those by path and not by parameter name. */ if (thePerformIndexing && newParams != null) { - Map searchParamPresenceMap = getSearchParamPresenceMap(entity, newParams); - - AddRemoveCount presenceCount = mySearchParamPresenceSvc.updatePresence(entity, searchParamPresenceMap); + AddRemoveCount presenceCount = mySearchParamPresenceSvc.updatePresence(entity, newParams.mySearchParamPresentEntities); // Interceptor broadcast: JPA_PERFTRACE_INFO if (!presenceCount.isEmpty()) { @@ -1248,22 +1248,6 @@ public abstract class BaseHapiFhirDao extends BaseStora encodedResource.setEncoding(theEncoding); } - @Nonnull - private Map getSearchParamPresenceMap(ResourceTable entity, ResourceIndexedSearchParams newParams) { - Map retval = new HashMap<>(); - - for (String nextKey : newParams.getPopulatedResourceLinkParameters()) { - retval.put(nextKey, Boolean.TRUE); - } - - ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(entity.getResourceType()); - activeSearchParams.getReferenceSearchParamNames().forEach(key -> { - if (!retval.containsKey(key)) { - retval.put(key, Boolean.FALSE); - } - }); - return retval; - } /** * TODO eventually consider refactoring this to be part of an interceptor. diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index d6ec8442e99..76ca1c8a392 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -28,6 +28,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.i18n.Msg; 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.interceptor.model.ReadPartitionIdRequestDetails; import ca.uhn.fhir.interceptor.model.RequestPartitionId; @@ -1232,7 +1233,7 @@ public abstract class BaseHapiFhirResourceDao extends B } //If the resolved fhir model is null, we don't need to run pre-access over or pre-show over it. if (retVal != null) { - invokeStoragePreaccessResources(theId, theRequest, retVal); + invokeStoragePreAccessResources(theId, theRequest, retVal); retVal = invokeStoragePreShowResources(theRequest, retVal); } @@ -1241,29 +1242,12 @@ public abstract class BaseHapiFhirResourceDao extends B } private T invokeStoragePreShowResources(RequestDetails theRequest, T retVal) { - // Interceptor broadcast: STORAGE_PRESHOW_RESOURCES - SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(retVal); - HookParams params = new HookParams() - .add(IPreResourceShowDetails.class, showDetails) - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest); - CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params); - //noinspection unchecked - retVal = (T) showDetails.getResource(0);//TODO GGG/JA : getting resource 0 is interesting. We apparently allow null values in the list. Should we? + retVal = invokeStoragePreShowResources(myInterceptorBroadcaster, theRequest, retVal); return retVal; } - private void invokeStoragePreaccessResources(IIdType theId, RequestDetails theRequest, T theResource) { - // Interceptor broadcast: STORAGE_PREACCESS_RESOURCES - SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(theResource); - HookParams params = new HookParams() - .add(IPreResourceAccessDetails.class, accessDetails) - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest); - CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); - if (accessDetails.isDontReturnResourceAtIndex(0)) { - throw new ResourceNotFoundException(Msg.code(1995) + "Resource " + theId + " is not known"); - } + private void invokeStoragePreAccessResources(IIdType theId, RequestDetails theRequest, T theResource) { + invokeStoragePreAccessResources(myInterceptorBroadcaster, theRequest, theId, theResource); } @Override @@ -1954,6 +1938,37 @@ public abstract class BaseHapiFhirResourceDao extends B myIdHelperService = theIdHelperService; } + public static T invokeStoragePreShowResources(IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, T retVal) { + if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRESHOW_RESOURCES, theInterceptorBroadcaster, theRequest)) { + SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(retVal); + HookParams params = new HookParams() + .add(IPreResourceShowDetails.class, showDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + CompositeInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params); + //noinspection unchecked + retVal = (T) showDetails.getResource(0);//TODO GGG/JA : getting resource 0 is interesting. We apparently allow null values in the list. Should we? + return retVal; + } else { + return retVal; + } + } + + public static void invokeStoragePreAccessResources(IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, IIdType theId, IBaseResource theResource) { + if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, theInterceptorBroadcaster, theRequest)) { + SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(theResource); + HookParams params = new HookParams() + .add(IPreResourceAccessDetails.class, accessDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + CompositeInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); + if (accessDetails.isDontReturnResourceAtIndex(0)) { + throw new ResourceNotFoundException(Msg.code(1995) + "Resource " + theId + " is not known"); + } + } + } + + private static class IdChecker implements IValidatorModule { private final ValidationModeEnum myMode; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java index 5656fef963c..fd6eab9da66 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java @@ -126,8 +126,8 @@ public class DaoSearchParamSynchronizer { } - List subtract(Collection theSubtractFrom, Collection theToSubtract) { - assert theSubtractFrom != theToSubtract; + public static List subtract(Collection theSubtractFrom, Collection theToSubtract) { + assert theSubtractFrom != theToSubtract || (theSubtractFrom.isEmpty()); if (theSubtractFrom.isEmpty()) { return new ArrayList<>(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index e13ff5972e6..76a8769fa8b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -32,6 +32,7 @@ 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.searchparam.extractor.BaseSearchParamWithInlineReferencesExtractor; +import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamWithInlineReferencesExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; @@ -95,12 +96,7 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit extractInlineReferences(theRequest, theResource, theTransactionDetails); } - mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequest, theParams, theExistingParams, theEntity, theResource, theTransactionDetails, thePerformIndexing); - - ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()); - if (myStorageSettings.getIndexMissingFields() == JpaStorageSettings.IndexEnabledEnum.ENABLED) { - theParams.findMissingSearchParams(myPartitionSettings, myStorageSettings, theEntity, activeSearchParams); - } + mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequest, theParams, theExistingParams, theEntity, theResource, theTransactionDetails, thePerformIndexing, ISearchParamExtractor.ALL_PARAMS); /* * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them @@ -113,12 +109,6 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit } } - extractComboParameters(theEntity, theParams); - } - - private void extractComboParameters(ResourceTable theEntity, ResourceIndexedSearchParams theParams) { - mySearchParamExtractorService.extractSearchParamComboUnique(theEntity, theParams); - mySearchParamExtractorService.extractSearchParamComboNonUnique(theEntity, theParams); } @Nullable @@ -169,13 +159,13 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit * String Uniques */ if (myStorageSettings.isUniqueIndexesEnabled()) { - for (ResourceIndexedComboStringUnique next : myDaoSearchParamSynchronizer.subtract(theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) { + for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract(theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) { ourLog.debug("Removing unique index: {}", next); myEntityManager.remove(next); theEntity.getParamsComboStringUnique().remove(next); } boolean haveNewStringUniqueParams = false; - for (ResourceIndexedComboStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) { + for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract(theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) { if (myStorageSettings.isUniqueIndexesCheckedBeforeSave()) { ResourceIndexedComboStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); if (existing != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/InstanceReindexProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/InstanceReindexProvider.java new file mode 100644 index 00000000000..d68ea0e5e44 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/InstanceReindexProvider.java @@ -0,0 +1,75 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2023 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% + */ +package ca.uhn.fhir.jpa.provider; + +import ca.uhn.fhir.jpa.search.reindex.IInstanceReindexService; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class InstanceReindexProvider { + + private final IInstanceReindexService myInstanceReindexService; + + /** + * Constructor + */ + public InstanceReindexProvider(@Nonnull IInstanceReindexService theInstanceReindexService) { + Validate.notNull(theInstanceReindexService); + myInstanceReindexService = theInstanceReindexService; + } + + @Operation(name = ProviderConstants.OPERATION_REINDEX_DRYRUN, idempotent = true, global = true) + public IBaseParameters reindexInstanceDryRun( + @IdParam IIdType theId, + @OperationParam(name="code", typeName = "code", min = 0, max = OperationParam.MAX_UNLIMITED) List> theCodes, + RequestDetails theRequestDetails + ) { + Set codes = null; + if (theCodes != null && theCodes.size() > 0) { + codes = theCodes + .stream() + .map(IPrimitiveType::getValueAsString) + .collect(Collectors.toSet()); + } + + return myInstanceReindexService.reindexDryRun(theRequestDetails, theId, codes); + } + + @Operation(name = ProviderConstants.OPERATION_REINDEX, idempotent = false, global = true) + public IBaseParameters reindexInstance( + @IdParam IIdType theId, + RequestDetails theRequestDetails + ) { + return myInstanceReindexService.reindex(theRequestDetails, theId); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IInstanceReindexService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IInstanceReindexService.java new file mode 100644 index 00000000000..2015ceea533 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IInstanceReindexService.java @@ -0,0 +1,40 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2023 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% + */ +package ca.uhn.fhir.jpa.search.reindex; + +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IIdType; + +import javax.annotation.Nullable; +import java.util.Set; + +public interface IInstanceReindexService { + + /** + * Simulate a reindex and return the details about what would change + */ + IBaseParameters reindexDryRun(RequestDetails theRequestDetails, IIdType theResourceId, @Nullable Set theParameters); + + /** + * Perform a reindex on a single resource and return details about what changed + */ + IBaseParameters reindex(RequestDetails theRequestDetails, IIdType theResourceId); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImpl.java new file mode 100644 index 00000000000..c37aabd25b1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImpl.java @@ -0,0 +1,616 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2023 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% + */ +package ca.uhn.fhir.jpa.search.reindex; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; +import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.partition.BaseRequestPartitionHelperSvc; +import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; +import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; +import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; +import ca.uhn.fhir.rest.server.util.ResourceSearchParams; +import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.r4.model.UrlType; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer.subtract; +import static java.util.Comparator.comparing; +import static org.apache.commons.collections4.CollectionUtils.intersection; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class InstanceReindexServiceImpl implements IInstanceReindexService { + + private final FhirContext myContextR4 = FhirContext.forR4Cached(); + @Autowired + protected IJpaStorageResourceParser myJpaStorageResourceParser; + @Autowired + private SearchParamExtractorService mySearchParamExtractorService; + @Autowired + private BaseRequestPartitionHelperSvc myPartitionHelperSvc; + @Autowired + private IHapiTransactionService myTransactionService; + @Autowired + private IInterceptorService myInterceptorService; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private VersionCanonicalizer myVersionCanonicalizer; + @Autowired + private PartitionSettings myPartitionSettings; + private final CustomThymeleafNarrativeGenerator myNarrativeGenerator; + @Autowired + private ISearchParamRegistry mySearchParamRegistry; + + /** + * Constructor + */ + public InstanceReindexServiceImpl() { + myNarrativeGenerator = new CustomThymeleafNarrativeGenerator("classpath:ca/uhn/fhir/jpa/search/reindex/reindex-outcome-narrative.properties"); + } + + @Override + public IBaseParameters reindexDryRun(RequestDetails theRequestDetails, IIdType theResourceId, @Nullable Set theParameters) { + RequestPartitionId partitionId = determinePartition(theRequestDetails, theResourceId); + TransactionDetails transactionDetails = new TransactionDetails(); + + Parameters retValCanonical = myTransactionService + .withRequest(theRequestDetails) + .withTransactionDetails(transactionDetails) + .withRequestPartitionId(partitionId) + .execute(() -> reindexDryRunInTransaction(theRequestDetails, theResourceId, partitionId, transactionDetails, theParameters)); + + return myVersionCanonicalizer.parametersFromCanonical(retValCanonical); + } + + @Override + public IBaseParameters reindex(RequestDetails theRequestDetails, IIdType theResourceId) { + RequestPartitionId partitionId = determinePartition(theRequestDetails, theResourceId); + TransactionDetails transactionDetails = new TransactionDetails(); + + Parameters retValCanonical = myTransactionService + .withRequest(theRequestDetails) + .withTransactionDetails(transactionDetails) + .withRequestPartitionId(partitionId) + .execute(() -> reindexInTransaction(theRequestDetails, theResourceId)); + + return myVersionCanonicalizer.parametersFromCanonical(retValCanonical); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Nonnull + private Parameters reindexInTransaction(RequestDetails theRequestDetails, IIdType theResourceId) { + IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType()); + ResourceTable entity = (ResourceTable) dao.readEntity(theResourceId, theRequestDetails); + IBaseResource resource = myJpaStorageResourceParser.toResource(entity, false); + + // Invoke the pre-access and pre-show interceptors in case there are any security + // restrictions or audit requirements around the user accessing this resource + BaseHapiFhirResourceDao.invokeStoragePreAccessResources(myInterceptorService, theRequestDetails, theResourceId, resource); + BaseHapiFhirResourceDao.invokeStoragePreShowResources(myInterceptorService, theRequestDetails, resource); + + ResourceIndexedSearchParams existingParamsToPopulate = new ResourceIndexedSearchParams(entity); + existingParamsToPopulate.mySearchParamPresentEntities.addAll(entity.getSearchParamPresents()); + + dao.reindex(resource, entity); + + ResourceIndexedSearchParams newParamsToPopulate = new ResourceIndexedSearchParams(entity); + newParamsToPopulate.mySearchParamPresentEntities.addAll(entity.getSearchParamPresents()); + + return buildIndexResponse(existingParamsToPopulate, newParamsToPopulate, true); + } + + @Nonnull + private Parameters reindexDryRunInTransaction(RequestDetails theRequestDetails, IIdType theResourceId, RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails, Set theParameters) { + IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType()); + ResourceTable entity = (ResourceTable) dao.readEntity(theResourceId, theRequestDetails); + IBaseResource resource = myJpaStorageResourceParser.toResource(entity, false); + + // Invoke the pre-access and pre-show interceptors in case there are any security + // restrictions or audit requirements around the user accessing this resource + BaseHapiFhirResourceDao.invokeStoragePreAccessResources(myInterceptorService, theRequestDetails, theResourceId, resource); + BaseHapiFhirResourceDao.invokeStoragePreShowResources(myInterceptorService, theRequestDetails, resource); + + ISearchParamExtractor.ISearchParamFilter searchParamFilter = ISearchParamExtractor.ALL_PARAMS; + if (theParameters != null) { + searchParamFilter = params -> params + .stream() + .filter(t -> theParameters.contains(t.getName())) + .collect(Collectors.toSet()); + } + + ResourceIndexedSearchParams newParamsToPopulate = new ResourceIndexedSearchParams(); + mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequestDetails, newParamsToPopulate, new ResourceIndexedSearchParams(), entity, resource, theTransactionDetails, false, searchParamFilter); + + ResourceIndexedSearchParams existingParamsToPopulate; + boolean showAction; + if (theParameters == null) { + existingParamsToPopulate = new ResourceIndexedSearchParams(entity); + existingParamsToPopulate.mySearchParamPresentEntities.addAll(entity.getSearchParamPresents()); + fillInParamNames(entity, existingParamsToPopulate.mySearchParamPresentEntities, theResourceId.getResourceType()); + showAction = true; + } else { + existingParamsToPopulate = new ResourceIndexedSearchParams(); + showAction = false; + } + + return buildIndexResponse(existingParamsToPopulate, newParamsToPopulate, showAction); + } + + @Nonnull + private RequestPartitionId determinePartition(RequestDetails theRequestDetails, IIdType theResourceId) { + ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forRead(theResourceId); + return myPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details); + } + + @Nonnull + @VisibleForTesting + Parameters buildIndexResponse(ResourceIndexedSearchParams theExistingParams, ResourceIndexedSearchParams theNewParams, boolean theShowAction) { + Parameters parameters = new Parameters(); + + Parameters.ParametersParameterComponent narrativeParameter = parameters.addParameter(); + narrativeParameter.setName("Narrative"); + + // Normal indexes + addParamsNonMissing(parameters, "CoordinateIndexes", "Coords", theExistingParams.myCoordsParams, theNewParams.myCoordsParams, new CoordsParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "DateIndexes", "Date", theExistingParams.myDateParams, theNewParams.myDateParams, new DateParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "NumberIndexes", "Number", theExistingParams.myNumberParams, theNewParams.myNumberParams, new NumberParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "QuantityIndexes", "Quantity", theExistingParams.myQuantityParams, theNewParams.myQuantityParams, new QuantityParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "QuantityIndexes", "QuantityNormalized", theExistingParams.myQuantityNormalizedParams, theNewParams.myQuantityNormalizedParams, new QuantityNormalizedParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "UriIndexes", "Uri", theExistingParams.myUriParams, theNewParams.myUriParams, new UriParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "StringIndexes", "String", theExistingParams.myStringParams, theNewParams.myStringParams, new StringParamPopulator(), theShowAction); + addParamsNonMissing(parameters, "TokenIndexes", "Token", theExistingParams.myTokenParams, theNewParams.myTokenParams, new TokenParamPopulator(), theShowAction); + + // Resource links + addParams(parameters, "ResourceLinks", "Reference", normalizeLinks(theExistingParams.myLinks), normalizeLinks(theNewParams.myLinks), new ResourceLinkPopulator(), theShowAction); + + // Combo search params + addParams(parameters, "UniqueIndexes", "ComboStringUnique", theExistingParams.myComboStringUniques, theNewParams.myComboStringUniques, new ComboStringUniquePopulator(), theShowAction); + addParams(parameters, "NonUniqueIndexes", "ComboTokenNonUnique", theExistingParams.myComboTokenNonUnique, theNewParams.myComboTokenNonUnique, new ComboTokenNonUniquePopulator(), theShowAction); + + // Missing (:missing) indexes + addParamsMissing(parameters, "Coords", theExistingParams.myCoordsParams, theNewParams.myCoordsParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "Date", theExistingParams.myDateParams, theNewParams.myDateParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "Number", theExistingParams.myNumberParams, theNewParams.myNumberParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "Quantity", theExistingParams.myQuantityParams, theNewParams.myQuantityParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "QuantityNormalized", theExistingParams.myQuantityNormalizedParams, theNewParams.myQuantityNormalizedParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "Uri", theExistingParams.myUriParams, theNewParams.myUriParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "String", theExistingParams.myStringParams, theNewParams.myStringParams, new MissingIndexParamPopulator<>(), theShowAction); + addParamsMissing(parameters, "Token", theExistingParams.myTokenParams, theNewParams.myTokenParams, new MissingIndexParamPopulator<>(), theShowAction); + addParams(parameters, "MissingIndexes", "Reference", theExistingParams.mySearchParamPresentEntities, theNewParams.mySearchParamPresentEntities, new SearchParamPresentParamPopulator(), theShowAction); + + String narrativeText = myNarrativeGenerator.generateResourceNarrative(myContextR4, parameters); + narrativeParameter.setValue(new StringType(narrativeText)); + + return parameters; + } + + /** + * The {@link SearchParamPresentEntity} entity doesn't actually store the parameter names + * in the database entity, it only stores a hash. So we brute force possible hashes here + * to figure out the associated param names. + */ + private void fillInParamNames(ResourceTable theEntity, Collection theTarget, String theResourceName) { + Map hashes = new HashMap<>(); + ResourceSearchParams searchParams = mySearchParamRegistry.getActiveSearchParams(theResourceName); + for (RuntimeSearchParam next : searchParams.values()) { + hashes.put(SearchParamPresentEntity.calculateHashPresence(myPartitionSettings, theEntity.getPartitionId(), theResourceName, next.getName(), true), next.getName()); + hashes.put(SearchParamPresentEntity.calculateHashPresence(myPartitionSettings, theEntity.getPartitionId(), theResourceName, next.getName(), false), next.getName()); + } + + for (SearchParamPresentEntity next : theTarget) { + if (next.getParamName() == null) { + String name = hashes.get(next.getHashPresence()); + name = defaultIfBlank(name, "(unknown)"); + next.setParamName(name); + } + } + } + + private enum ActionEnum { + + ADD, + REMOVE, + UNKNOWN, + NO_CHANGE + + } + + private static abstract class BaseParamPopulator { + + + @Nonnull + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, T theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = theParent + .addPart() + .setName(toPartName(theParam)); + retVal + .addPart() + .setName("Action") + .setValue(new CodeType(theAction.name())); + if (theParamTypeName != null) { + retVal + .addPart() + .setName("Type") + .setValue(new CodeType(theParamTypeName)); + } + return retVal; + } + + protected abstract String toPartName(T theParam); + + public void sort(List theParams) { + theParams.sort(comparing(this::toPartName)); + } + } + + public static abstract class BaseIndexParamPopulator extends BaseParamPopulator { + @Override + protected String toPartName(T theParam) { + return theParam.getParamName(); + } + + } + + private static class ComboStringUniquePopulator extends BaseParamPopulator { + @Override + protected String toPartName(ResourceIndexedComboStringUnique theParam) { + return theParam.getIndexString(); + } + + } + + private static class ComboTokenNonUniquePopulator extends BaseParamPopulator { + @Override + protected String toPartName(ResourceIndexedComboTokenNonUnique theParam) { + return theParam.getIndexString(); + } + } + + private static class CoordsParamPopulator extends BaseIndexParamPopulator { + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamCoords theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Latitude") + .setValue(new DecimalType(theParam.getLatitude())); + retVal + .addPart() + .setName("Longitude") + .setValue(new DecimalType(theParam.getLongitude())); + return retVal; + } + + + } + + private static class DateParamPopulator extends BaseIndexParamPopulator { + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamDate theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("High") + .setValue(new InstantType(theParam.getValueHigh())); + retVal + .addPart() + .setName("Low") + .setValue(new InstantType(theParam.getValueLow())); + return retVal; + } + } + + private static class MissingIndexParamPopulator extends BaseIndexParamPopulator { + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, T theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Missing") + .setValue(new BooleanType(theParam.isMissing())); + return retVal; + } + + + } + + private static class NumberParamPopulator extends BaseIndexParamPopulator { + + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamNumber theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Value") + .setValue(new DecimalType(theParam.getValue())); + return retVal; + } + + } + + private static class QuantityParamPopulator extends BaseIndexParamPopulator { + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamQuantity theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Value") + .setValue(new DecimalType(theParam.getValue())); + retVal + .addPart() + .setName("System") + .setValue(new UriType(theParam.getSystem())); + retVal + .addPart() + .setName("Units") + .setValue(new CodeType(theParam.getUnits())); + return retVal; + } + + } + + private static class QuantityNormalizedParamPopulator extends BaseIndexParamPopulator { + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamQuantityNormalized theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Value") + .setValue(new DecimalType(theParam.getValue())); + retVal + .addPart() + .setName("System") + .setValue(new UriType(theParam.getSystem())); + retVal + .addPart() + .setName("Units") + .setValue(new CodeType(theParam.getUnits())); + return retVal; + } + + } + + private static class ResourceLinkPopulator extends BaseParamPopulator { + + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceLink theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + if (theParam.getTargetResourceId() != null) { + retVal + .addPart() + .setName("TargetId") + .setValue(new StringType(theParam.getTargetResourceType() + "/" + theParam.getTargetResourceId())); + } else if (theParam.getTargetResourceUrl() != null) { + retVal + .addPart() + .setName("TargetUrl") + .setValue(new UrlType(theParam.getTargetResourceUrl())); + } + + if (theParam.getTargetResourceVersion() != null) { + retVal + .addPart() + .setName("TargetVersion") + .setValue(new StringType(theParam.getTargetResourceVersion().toString())); + } + + return retVal; + } + + @Override + protected String toPartName(ResourceLink theParam) { + return theParam.getSourcePath(); + } + + } + + private static class SearchParamPresentParamPopulator extends BaseParamPopulator { + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, SearchParamPresentEntity theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Missing") + .setValue(new BooleanType(!theParam.isPresent())); + return retVal; + } + + @Override + protected String toPartName(SearchParamPresentEntity theParam) { + return theParam.getParamName(); + } + + } + + private static class StringParamPopulator extends BaseIndexParamPopulator { + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamString theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("ValueNormalized") + .setValue(new StringType(theParam.getValueNormalized())); + retVal + .addPart() + .setName("ValueExact") + .setValue(new StringType(theParam.getValueExact())); + return retVal; + } + } + + private static class TokenParamPopulator extends BaseIndexParamPopulator { + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamToken theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + if (isNotBlank(theParam.getSystem())) { + retVal + .addPart() + .setName("System") + .setValue(new StringType(theParam.getSystem())); + } + if (isNotBlank(theParam.getValue())) { + retVal + .addPart() + .setName("Value") + .setValue(new StringType(theParam.getValue())); + } + return retVal; + } + } + + private static class UriParamPopulator extends BaseIndexParamPopulator { + + @Nonnull + @Override + public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamUri theParam, String theParamTypeName) { + Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName); + retVal + .addPart() + .setName("Value") + .setValue(new UriType(theParam.getUri())); + return retVal; + } + } + + /** + * Links loaded from the database have a PID link to their target, but the ones + * extracted from the resource in memory won't have the PID. So this method + * strips the PIDs so that the generated hashCodes and equals comparisons + * will actually be equal. + */ + private static List normalizeLinks(Collection theLinks) { + return theLinks + .stream() + .map(ResourceLink::cloneWithoutTargetPid) + .collect(Collectors.toList()); + } + + private static void addParams(Parameters theParameters, String theSectionName, String theTypeName, Collection theExistingParams, Collection theNewParams, BaseParamPopulator thePopulator, boolean theShowAction) { + List addedParams = subtract(theNewParams, theExistingParams); + thePopulator.sort(addedParams); + for (T next : addedParams) { + Parameters.ParametersParameterComponent parent = getOrCreateSection(theParameters, theSectionName); + if (theShowAction) { + thePopulator.addIndexValue(ActionEnum.ADD, parent, next, theTypeName); + } else { + thePopulator.addIndexValue(ActionEnum.UNKNOWN, parent, next, theTypeName); + } + } + + List removedParams = subtract(theExistingParams, theNewParams); + addedParams.sort(comparing(thePopulator::toPartName)); + for (T next : removedParams) { + Parameters.ParametersParameterComponent parent = getOrCreateSection(theParameters, theSectionName); + thePopulator.addIndexValue(ActionEnum.REMOVE, parent, next, theTypeName); + } + + List unchangedParams = new ArrayList<>(intersection(theNewParams, theExistingParams)); + addedParams.sort(comparing(thePopulator::toPartName)); + for (T next : unchangedParams) { + Parameters.ParametersParameterComponent parent = getOrCreateSection(theParameters, theSectionName); + thePopulator.addIndexValue(ActionEnum.NO_CHANGE, parent, next, theTypeName); + } + + } + + private static void addParamsNonMissing(Parameters theParameters, String theSectionName, String theTypeName, Collection theExistingParams, Collection theNewParams, BaseParamPopulator thePopulator, boolean theShowAction) { + Collection existingParams = filterWantMissing(theExistingParams, false); + Collection newParams = filterWantMissing(theNewParams, false); + addParams(theParameters, theSectionName, theTypeName, existingParams, newParams, thePopulator, theShowAction); + } + + private static void addParamsMissing(Parameters theParameters, String theTypeName, Collection theExistingParams, Collection theNewParams, BaseParamPopulator thePopulator, boolean theShowAction) { + Collection existingParams = filterWantMissing(theExistingParams, true); + Collection newParams = filterWantMissing(theNewParams, true); + addParams(theParameters, "MissingIndexes", theTypeName, existingParams, newParams, thePopulator, theShowAction); + } + + private static Collection filterWantMissing(Collection theNewParams, boolean theWantMissing) { + return theNewParams + .stream() + .filter(t -> t.isMissing() == theWantMissing) + .collect(Collectors.toList()); + } + + @Nonnull + private static Parameters.ParametersParameterComponent getOrCreateSection(Parameters theParameters, String theSectionName) { + Parameters.ParametersParameterComponent parent = theParameters.getParameter(theSectionName); + if (parent == null) { + parent = theParameters.addParameter(); + parent.setName(theSectionName); + } + return parent; + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java index ffe49d172cd..d455ffe8304 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java @@ -20,12 +20,13 @@ package ca.uhn.fhir.jpa.sp; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; import ca.uhn.fhir.jpa.util.AddRemoveCount; -import java.util.Map; +import java.util.Collection; public interface ISearchParamPresenceSvc { - AddRemoveCount updatePresence(ResourceTable theResource, Map theParamNameToPresence); + AddRemoveCount updatePresence(ResourceTable theResource, Collection thePresenceEntities); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java index 1ae0ae17e62..09326ceadc9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java @@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.sp; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; -import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; import ca.uhn.fhir.jpa.util.AddRemoveCount; @@ -42,8 +41,6 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { @Autowired private ISearchParamPresentDao mySearchParamPresentDao; - @Autowired - private PartitionSettings myPartitionSettings; @Autowired private JpaStorageSettings myStorageSettings; @@ -53,14 +50,12 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { } @Override - public AddRemoveCount updatePresence(ResourceTable theResource, Map theParamNameToPresence) { + public AddRemoveCount updatePresence(ResourceTable theResource, Collection thePresenceEntities) { AddRemoveCount retVal = new AddRemoveCount(); if (myStorageSettings.getIndexMissingFields() == JpaStorageSettings.IndexEnabledEnum.DISABLED) { return retVal; } - Map presenceMap = new HashMap<>(theParamNameToPresence); - // Find existing entries Collection existing = theResource.getSearchParamPresents(); Map existingHashToPresence = new HashMap<>(); @@ -70,18 +65,8 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { // Find newly wanted set of entries Map newHashToPresence = new HashMap<>(); - for (Entry next : presenceMap.entrySet()) { - String paramName = next.getKey(); - - SearchParamPresentEntity present = new SearchParamPresentEntity(); - present.setPartitionSettings(myPartitionSettings); - present.setResource(theResource); - present.setParamName(paramName); - present.setPresent(next.getValue()); - present.setPartitionId(theResource.getPartitionId()); - present.calculateHashes(); - - newHashToPresence.put(present.getHashPresence(), present); + for (SearchParamPresentEntity next : thePresenceEntities) { + newHashToPresence.put(next.getHashPresence(), next); } // Delete any that should be deleted diff --git a/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/fragments.html b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/fragments.html new file mode 100644 index 00000000000..fbeea22c237 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/fragments.html @@ -0,0 +1,31 @@ +
+ + + + + + + + +
diff --git a/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/reindex-outcome-narrative.properties b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/reindex-outcome-narrative.properties new file mode 100644 index 00000000000..911b9d041f0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/reindex-outcome-narrative.properties @@ -0,0 +1,7 @@ + +parameters.resourceType = Parameters +parameters.style = thymeleaf +parameters.narrative = classpath:ca/uhn/fhir/jpa/search/reindex/reindex-outcome.html + +parameters-fragments.fragmentName=ParametersFragments +parameters-fragments.narrative=classpath:ca/uhn/fhir/jpa/search/reindex/fragments.html diff --git a/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/reindex-outcome.html b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/reindex-outcome.html new file mode 100644 index 00000000000..f57cb949dfe --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/search/reindex/reindex-outcome.html @@ -0,0 +1,219 @@ +
+ + +
+

Number Indexes

+ + + + + + + + + + + + + + + + + +
NameActionTypeValue
+
+ + +
+

Quantity Indexes

+ + + + + + + + + + + + + + + + + + + + + +
NameActionTypeValueSystemUnits
+
+ + +
+

String Indexes

+ + + + + + + + + + + + + + + + + + + +
NameActionTypeValueNormalizedValueExact
+
+ + +
+

Token Indexes

+ + + + + + + + + + + + + + + + + + + +
NameActionTypeSystemValue
+
+ + +
+

URI Indexes

+ + + + + + + + + + + + + + + + + +
NameActionTypeValue
+
+ + + + + +
+

Missing Indexes (:missing)

+ + + + + + + + + + + + + + + + + +
NameActionTypeMissing
+
+ + +
+

Combo Indexes (Non-Unique)

+ + + + + + + + + + + + + + + +
ActionTypeValue
+
+ + +
+

Combo Indexes (Unique)

+ + + + + + + + + + + + + + + +
ActionTypeValue
+
+ +
+ diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/InstanceReindexProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/InstanceReindexProviderTest.java new file mode 100644 index 00000000000..5d107dfb8df --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/InstanceReindexProviderTest.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.jpa.provider; + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.search.reindex.IInstanceReindexService; +import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderExtension; +import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Set; + +import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_REINDEX_DRYRUN; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class InstanceReindexProviderTest { + + @Mock + private IInstanceReindexService myDryRunService; + @RegisterExtension + @Order(0) + private RestfulServerExtension myServer = new RestfulServerExtension(FhirVersionEnum.R4) + .withServer(server -> server.registerProvider(new InstanceReindexProvider(myDryRunService))); + @RegisterExtension + @Order(1) + private HashMapResourceProviderExtension myPatientProvider = new HashMapResourceProviderExtension<>(myServer, Patient.class); + @Captor + private ArgumentCaptor> myCodeCaptor; + + @Test + public void testDryRun() { + Parameters parameters = new Parameters(); + parameters.addParameter("foo", "bar"); + when(myDryRunService.reindexDryRun(any(), any(), any())).thenReturn(parameters); + + Parameters outcome = myServer + .getFhirClient() + .operation() + .onInstance(new IdType("Patient/123")) + .named(OPERATION_REINDEX_DRYRUN) + .withNoParameters(Parameters.class) + .useHttpGet() + .execute(); + assertEquals("foo", outcome.getParameter().get(0).getName()); + } + + @Test + public void testDryRun_WithCodes() { + Parameters parameters = new Parameters(); + parameters.addParameter("foo", "bar"); + when(myDryRunService.reindexDryRun(any(), any(), any())).thenReturn(parameters); + + Parameters outcome = myServer + .getFhirClient() + .operation() + .onInstance(new IdType("Patient/123")) + .named(OPERATION_REINDEX_DRYRUN) + .withParameter(Parameters.class, "code", new CodeType("blah")) + .useHttpGet() + .execute(); + assertEquals("foo", outcome.getParameter().get(0).getName()); + + verify(myDryRunService, times(1)).reindexDryRun(any(), any(), myCodeCaptor.capture()); + + assertThat(myCodeCaptor.getValue(), contains("blah")); + } + +} diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 8ccce2ad972..2c45c948fff 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index 20a62036ad1..c41dd850fcc 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index e799c3abdab..b01100e0011 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 0b04919d349..4276b25ced0 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java index e4937e6ac18..8d2c7c1fd30 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java @@ -110,15 +110,17 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem ResourceIndexedComboTokenNonUnique that = (ResourceIndexedComboTokenNonUnique) theO; - return new EqualsBuilder() - .append(myResource, that.myResource) - .append(myHashComplete, that.myHashComplete) - .isEquals(); + EqualsBuilder b = new EqualsBuilder(); + b.append(myIndexString, that.myIndexString); + return b.isEquals(); } @Override public void copyMutableValuesFrom(T theSource) { - throw new IllegalStateException(Msg.code(1528)); + ResourceIndexedComboTokenNonUnique source = (ResourceIndexedComboTokenNonUnique) theSource; + myPartitionSettings = source.myPartitionSettings; + myHashComplete = source.myHashComplete; + myIndexString = source.myIndexString; } @Override @@ -151,8 +153,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem @Override public int hashCode() { return new HashCodeBuilder(17, 37) - .append(myResource) - .append(myHashComplete) + .append(myIndexString) .toHashCode(); } @@ -164,6 +165,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem myPartitionSettings = thePartitionSettings; } + @Override public ResourceTable getResource() { return myResource; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java index 29cda130d91..253b8d7fe7c 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java @@ -128,11 +128,18 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP b.append(getResourceType(), obj.getResourceType()); b.append(getParamName(), obj.getParamName()); b.append(getHashIdentity(), obj.getHashIdentity()); - b.append(getValue(), obj.getValue()); + b.append(normalizeForEqualityComparison(getValue()), normalizeForEqualityComparison(obj.getValue())); b.append(isMissing(), obj.isMissing()); return b.isEquals(); } + private Double normalizeForEqualityComparison(BigDecimal theValue) { + if (theValue == null) { + return null; + } + return theValue.doubleValue(); + } + public void setHashIdentity(Long theHashIdentity) { myHashIdentity = theHashIdentity; } @@ -161,7 +168,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP b.append(getResourceType()); b.append(getParamName()); b.append(getHashIdentity()); - b.append(getValue()); + b.append(normalizeForEqualityComparison(getValue())); b.append(isMissing()); return b.toHashCode(); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java index 7c1fa0152f2..f029ec3fc20 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java @@ -131,6 +131,18 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa calculateHashes(); } + /** + * Constructor + */ + public ResourceIndexedSearchParamToken(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, boolean theMissing) { + super(); + setPartitionSettings(thePartitionSettings); + setResourceType(theResourceType); + setParamName(theParamName); + setMissing(theMissing); + calculateHashes(); + } + @Override public void copyMutableValuesFrom(T theSource) { super.copyMutableValuesFrom(theSource); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java index 5358c67bd23..da26ba9f9ca 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java @@ -294,6 +294,28 @@ public class ResourceLink extends BaseResourceIndex { return myTargetResource; } + /** + * Creates a clone of this resourcelink which doesn't contain the internal PID + * of the target resource. + */ + public ResourceLink cloneWithoutTargetPid() { + ResourceLink retVal = new ResourceLink(); + retVal.mySourceResource = mySourceResource; + retVal.mySourceResourcePid = mySourceResource.getId(); + retVal.mySourceResourceType = mySourceResource.getResourceType(); + retVal.mySourcePath = mySourcePath; + retVal.myUpdated = myUpdated; + retVal.myTargetResourceType = myTargetResourceType; + if (myTargetResourceId != null) { + retVal.myTargetResourceId = myTargetResourceId; + } else if (myTargetResource != null) { + retVal.myTargetResourceId = myTargetResource.getIdDt().getIdPart(); + } + retVal.myTargetResourceUrl = myTargetResourceUrl; + retVal.myTargetResourceVersion = myTargetResourceVersion; + return retVal; + } + public static ResourceLink forAbsoluteReference(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) { ResourceLink retVal = new ResourceLink(); retVal.setSourcePath(theSourcePath); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java index 7cfbdf6604a..bc576605565 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java @@ -598,7 +598,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas } public void setParamsComboTokensNonUniquePresent(boolean theParamsComboTokensNonUniquePresent) { - myParamsComboStringUniquePresent = theParamsComboTokensNonUniquePresent; + myParamsComboTokensNonUniquePresent = theParamsComboTokensNonUniquePresent; } public boolean isParamsCoordsPopulated() { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java index 0a49117683c..b23be685bc1 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresentEntity.java @@ -21,6 +21,9 @@ package ca.uhn.fhir.jpa.model.entity; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -63,6 +66,14 @@ public class SearchParamPresentEntity extends BasePartitionable implements Seria super(); } + /** + * Constructor + */ + public SearchParamPresentEntity(String theParamName, boolean thePresent) { + myParamName = theParamName; + myPresent = thePresent; + } + @SuppressWarnings("unused") @PrePersist public void calculateHashes() { @@ -75,6 +86,7 @@ public class SearchParamPresentEntity extends BasePartitionable implements Seria } public Long getHashPresence() { + Validate.notNull(myHashPresence); return myHashPresence; } @@ -106,6 +118,26 @@ public class SearchParamPresentEntity extends BasePartitionable implements Seria myPresent = thePresent; } + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (theO == null || getClass() != theO.getClass()) return false; + + SearchParamPresentEntity that = (SearchParamPresentEntity) theO; + + EqualsBuilder b = new EqualsBuilder(); + b.append(getHashPresence(), that.getHashPresence()); + return b.isEquals(); + } + + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(17, 37); + b.append(getHashPresence()); + return b.toHashCode(); + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java index ac21403bdd6..b03133db91a 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/StorageSettings.java @@ -116,6 +116,8 @@ public class StorageSettings { private boolean myAllowMdmExpansion = false; private boolean myAutoSupportDefaultSearchParams = true; private boolean myIndexIdentifierOfType = false; + private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED; + /** * Since 6.4.0 */ @@ -259,6 +261,53 @@ public class StorageSettings { myEnableInMemorySubscriptionMatching = theEnableInMemorySubscriptionMatching; } + /** + * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED}) + * the server will not create search indexes for search parameters with no values in resources. + *

+ * Disabling this feature means that the :missing search modifier will not be + * supported on the server, but also means that storage and indexing (i.e. writes to the + * database) may be much faster on servers which have lots of search parameters and need + * to write quickly. + *

+ *

+ * This feature may be enabled on servers where supporting the use of the :missing parameter is + * of higher importance than raw write performance + *

+ */ + public IndexEnabledEnum getIndexMissingFields() { + return myIndexMissingFieldsEnabled; + } + + /** + * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED}) + * the server will not create search indexes for search parameters with no values in resources. + *

+ * Disabling this feature means that the :missing search modifier will not be + * supported on the server, but also means that storage and indexing (i.e. writes to the + * database) may be much faster on servers which have lots of search parameters and need + * to write quickly. + *

+ *

+ * This feature may be enabled on servers where supporting the use of the :missing parameter is + * of higher importance than raw write performance + *

+ *

+ * Note that this setting also has an impact on sorting (i.e. using the + * _sort parameter on searches): If the server is configured + * to not index missing field. + *

+ *

+ * The following index may need to be added into the indexed tables such as HFJ_SPIDX_TOKEN + * to improve the search performance while :missing is enabled. + * RES_TYPE, SP_NAME, SP_MISSING + *

+ */ + public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) { + Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null"); + myIndexMissingFieldsEnabled = theIndexMissingFields; + } + /** * If this is enabled (disabled by default), Mass Ingestion Mode is enabled. In this mode, a number of * runtime checks are disabled. This mode is designed for rapid backloading of data while the system is not @@ -1220,4 +1269,9 @@ public class StorageSettings { } + public enum IndexEnabledEnum { + ENABLED, + DISABLED + } + } diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 1b1d1974fe8..c3337e2297d 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index d1f879ffe16..26d9ddb28d5 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -269,9 +269,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamComposites(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamComposites(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createCompositeExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.COMPOSITE, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.COMPOSITE, false, theSearchParamFilter); } private IExtractor createCompositeExtractor(IBaseResource theResource) { @@ -557,9 +557,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamTokens(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamTokens(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createTokenExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false, theSearchParamFilter); } @Override @@ -592,10 +592,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamSpecial(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamSpecial(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { String resourceTypeName = toRootTypeName(theResource); IExtractor extractor = createSpecialExtractor(resourceTypeName); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false, theSearchParamFilter); } private IExtractor createSpecialExtractor(String theResourceTypeName) { @@ -612,9 +612,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamUri(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamUri(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createUriExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false, theSearchParamFilter); } private IExtractor createUriExtractor(IBaseResource theResource) { @@ -637,9 +637,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamDates(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamDates(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createDateExtractor(theResource); - return extractSearchParams(theResource, extractor, DATE, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, DATE, false, theSearchParamFilter); } private IExtractor createDateExtractor(IBaseResource theResource) { @@ -653,9 +653,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamNumber(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamNumber(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createNumberExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false, theSearchParamFilter); } private IExtractor createNumberExtractor(IBaseResource theResource) { @@ -688,16 +688,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamQuantity(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamQuantity(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createQuantityUnnormalizedExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter); } @Override - public SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createQuantityNormalizedExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter); } @Nonnull @@ -767,10 +767,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public SearchParamSet extractSearchParamStrings(IBaseResource theResource, Set theParamsToIndex) { + public SearchParamSet extractSearchParamStrings(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { IExtractor extractor = createStringExtractor(theResource); - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false, theParamsToIndex); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false, theSearchParamFilter); } private IExtractor createStringExtractor(IBaseResource theResource) { @@ -1321,22 +1321,22 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor * This is the only way you could actually specify a FhirPath expression for those * prior to 6.2.0 so this isn't a breaking change. */ - SearchParamSet extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences, Set theParamsToIndex) { + SearchParamSet extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences, ISearchParamFilter theSearchParamFilter) { SearchParamSet retVal = new SearchParamSet<>(); Collection searchParams = getSearchParams(theResource); - cleanUpContainedResourceReferences(theResource, theSearchParamType, searchParams); + int preFilterSize = searchParams.size(); + Collection filteredSearchParams = theSearchParamFilter.filterSearchParams(searchParams); + assert filteredSearchParams.size() == preFilterSize || searchParams != filteredSearchParams; - for (RuntimeSearchParam nextSpDef : searchParams) { + cleanUpContainedResourceReferences(theResource, theSearchParamType, filteredSearchParams); + + for (RuntimeSearchParam nextSpDef : filteredSearchParams) { if (nextSpDef.getParamType() != theSearchParamType) { continue; } - if (!theParamsToIndex.equals(ISearchParamExtractor.ALL_PARAMS) && !theParamsToIndex.contains(nextSpDef.getName())) { - continue; - } - // See the method javadoc for an explanation of this if (startsWith(nextSpDef.getPath(), "Resource.")) { continue; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java index b0b95d5ef81..8571def6a6d 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java @@ -21,78 +21,77 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.model.entity.*; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Set; public interface ISearchParamExtractor { /** - * Constant for the {@literal theParamsToIndex} parameters on this interface + * Constant for the {@literal theSearchParamFilter} parameters on this interface * indicating that all search parameters should be indexed. */ - Set ALL_PARAMS = Set.of("*"); + ISearchParamFilter ALL_PARAMS = t -> t; + + /** + * Constant for the {@literal theSearchParamFilter} parameters on this interface + * indicating that no search parameters should be indexed. + */ + ISearchParamFilter NO_PARAMS = t -> Collections.emptyList(); default SearchParamSet extractSearchParamDates(IBaseResource theResource) { return extractSearchParamDates(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamDates(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamDates(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); default SearchParamSet extractSearchParamNumber(IBaseResource theResource) { return extractSearchParamNumber(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamNumber(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamNumber(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); default SearchParamSet extractSearchParamQuantity(IBaseResource theResource) { return extractSearchParamQuantity(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamQuantity(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamQuantity(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); default SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource) { return extractSearchParamQuantityNormalized(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); default SearchParamSet extractSearchParamStrings(IBaseResource theResource) { return extractSearchParamStrings(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamStrings(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamStrings(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); default SearchParamSet extractSearchParamComposites(IBaseResource theResource) { return extractSearchParamComposites(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamComposites(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamComposites(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); + default SearchParamSet extractSearchParamTokens(IBaseResource theResource) { return extractSearchParamTokens(theResource, ALL_PARAMS); } - SearchParamSet extractSearchParamTokens(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamTokens(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); SearchParamSet extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam); - SearchParamSet extractSearchParamSpecial(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamSpecial(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); + SearchParamSet extractSearchParamComboUnique(String theResourceType, ResourceIndexedSearchParams theParams); SearchParamSet extractSearchParamComboNonUnique(String theResourceType, ResourceIndexedSearchParams theParams); @@ -102,7 +101,7 @@ public interface ISearchParamExtractor { } - SearchParamSet extractSearchParamUri(IBaseResource theResource, Set theParamsToIndex); + SearchParamSet extractSearchParamUri(IBaseResource theResource, ISearchParamFilter theSearchParamFilter); SearchParamSet extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences); @@ -130,6 +129,20 @@ public interface ISearchParamExtractor { String getDisplayTextFromCodeableConcept(IBase theValue); + @FunctionalInterface + interface ISearchParamFilter { + + /** + * Given the list of search parameters for indexing, an implementation of this + * interface may selectively remove any that it wants to remove (or can add if desired). + *

+ * Implementations must not modify the list that is passed in. If changes are + * desired, a new list must be created and returned. + */ + Collection filterSearchParams(Collection theSearchParams); + + } + class SearchParamSet extends HashSet { private List myWarnings; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java index 5610f1f63dd..e847757dbd1 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java @@ -59,6 +59,7 @@ public final class ResourceIndexedSearchParams { final public Collection myComboTokenNonUnique = new HashSet<>(); final public Collection myLinks = new HashSet<>(); final public Set myPopulatedResourceLinkParameters = new HashSet<>(); + final public Collection mySearchParamPresentEntities = new HashSet<>(); final public Collection myCompositeParams = new HashSet<>(); public ResourceIndexedSearchParams() { @@ -116,6 +117,7 @@ public final class ResourceIndexedSearchParams { theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false); theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false); theEntity.setParamsComboStringUniquePresent(myComboStringUniques.isEmpty() == false); + theEntity.setParamsComboTokensNonUniquePresent(myComboTokenNonUnique.isEmpty() == false); theEntity.setHasLinks(myLinks.isEmpty() == false); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index bf19c243652..80d726737fa 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -47,9 +47,9 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; import ca.uhn.fhir.jpa.model.entity.StorageSettings; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; -import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; @@ -57,6 +57,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; +import ca.uhn.fhir.rest.server.util.ResourceSearchParams; import ca.uhn.fhir.util.FhirTerser; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; @@ -70,10 +71,13 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; 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.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -94,8 +98,6 @@ public class SearchParamExtractorService { private PartitionSettings myPartitionSettings; @Autowired(required = false) private IResourceLinkResolver myResourceLinkResolver; - @Autowired - private IRequestPartitionHelperSvc myPartitionHelperSvc; @VisibleForTesting public void setSearchParamExtractor(ISearchParamExtractor theSearchParamExtractor) { @@ -104,7 +106,7 @@ public class SearchParamExtractorService { public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference) { - extractFromResource(theRequestPartitionId, theRequestDetails, theParams, new ResourceIndexedSearchParams(), theEntity, theResource, theTransactionDetails, theFailOnInvalidReference); + extractFromResource(theRequestPartitionId, theRequestDetails, theParams, new ResourceIndexedSearchParams(), theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, ISearchParamExtractor.ALL_PARAMS); } /** @@ -113,11 +115,11 @@ public class SearchParamExtractorService { * a given resource type, it extracts the associated indexes and populates * {@literal theParams}. */ - public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theNewParams, ResourceIndexedSearchParams theExistingParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference) { + public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theNewParams, ResourceIndexedSearchParams theExistingParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, @Nonnull ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { // All search parameter types except Reference ResourceIndexedSearchParams normalParams = new ResourceIndexedSearchParams(); - extractSearchIndexParameters(theRequestDetails, normalParams, theResource, ISearchParamExtractor.ALL_PARAMS); + extractSearchIndexParameters(theRequestDetails, normalParams, theResource, theSearchParamFilter); mergeParams(normalParams, theNewParams); boolean indexOnContainedResources = myStorageSettings.isIndexOnContainedResources(); @@ -146,9 +148,51 @@ public class SearchParamExtractorService { extractResourceLinksForContainedResources(theRequestPartitionId, theNewParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails); } + // Missing (:missing) Indexes - These are indexes to satisfy the :missing + // modifier + if (myStorageSettings.getIndexMissingFields() == StorageSettings.IndexEnabledEnum.ENABLED) { + + // References + Map presenceMap = getReferenceSearchParamPresenceMap(theEntity, theNewParams); + presenceMap.forEach((key, value) -> { + SearchParamPresentEntity present = new SearchParamPresentEntity(); + present.setPartitionSettings(myPartitionSettings); + present.setResource(theEntity); + present.setParamName(key); + present.setPresent(value); + present.setPartitionId(theEntity.getPartitionId()); + present.calculateHashes(); + theNewParams.mySearchParamPresentEntities.add(present); + }); + + // Everything else + ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()); + theNewParams.findMissingSearchParams(myPartitionSettings, myStorageSettings, theEntity, activeSearchParams); + } + + extractSearchParamComboUnique(theEntity, theNewParams); + + extractSearchParamComboNonUnique(theEntity, theNewParams); + theNewParams.setUpdatedTime(theTransactionDetails.getTransactionDate()); } + @Nonnull + private Map getReferenceSearchParamPresenceMap(ResourceTable entity, ResourceIndexedSearchParams newParams) { + Map retval = new HashMap<>(); + + for (String nextKey : newParams.getPopulatedResourceLinkParameters()) { + retval.put(nextKey, Boolean.TRUE); + } + + ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(entity.getResourceType()); + activeSearchParams + .getReferenceSearchParamNames() + .forEach(key -> retval.putIfAbsent(key, Boolean.FALSE)); + return retval; + } + + @VisibleForTesting public void setStorageSettings(StorageSettings theStorageSettings) { myStorageSettings = theStorageSettings; @@ -169,8 +213,9 @@ public class SearchParamExtractorService { // Extract search parameters IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() { + @Nonnull @Override - public Set getChainedSearchParametersToIndexForPath(PathAndRef thePathAndRef) { + public ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef) { // Currently for contained resources we always index all search parameters // on all contained resources. A potential nice future optimization would // be to make this configurable, perhaps with an optional extension you could @@ -179,7 +224,7 @@ public class SearchParamExtractorService { } @Override - public IBaseResource fetchResourceAtPath(PathAndRef thePathAndRef) { + public IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef) { return findContainedResource(containedResources, thePathAndRef.getRef()); } }; @@ -197,15 +242,23 @@ public class SearchParamExtractorService { private void extractSearchIndexParametersForUpliftedRefchains(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails, ISearchParamExtractor.SearchParamSet theIndexedReferences) { IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() { + @Nonnull @Override - public Set getChainedSearchParametersToIndexForPath(PathAndRef thePathAndRef) { + public ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef) { String searchParamName = thePathAndRef.getSearchParamName(); RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theEntity.getResourceType(), searchParamName); - return searchParam.getUpliftRefchainCodes(); + Set upliftRefchainCodes = searchParam.getUpliftRefchainCodes(); + if (upliftRefchainCodes.isEmpty()) { + return ISearchParamExtractor.NO_PARAMS; + } + return sp -> sp + .stream() + .filter(t -> upliftRefchainCodes.contains(t.getName())) + .collect(Collectors.toList()); } @Override - public IBaseResource fetchResourceAtPath(PathAndRef thePathAndRef) { + public IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef) { // The PathAndRef will contain a resource if the SP path was inside a Bundle // and pointed to a resource (e.g. Bundle.entry.resource) as opposed to // pointing to a reference (e.g. Observation.subject) @@ -264,8 +317,8 @@ public class SearchParamExtractorService { continue; // 3.1.2 check if this ref actually applies here - Set searchParamsToIndex = theTargetIndexingStrategy.getChainedSearchParametersToIndexForPath(nextPathAndRef); - if (searchParamsToIndex.isEmpty()) { + ISearchParamExtractor.ISearchParamFilter searchParamsToIndex = theTargetIndexingStrategy.getSearchParamFilter(nextPathAndRef); + if (searchParamsToIndex == ISearchParamExtractor.NO_PARAMS) { continue; } @@ -332,42 +385,42 @@ public class SearchParamExtractorService { theTargetParams.myCompositeParams.addAll(theSrcParams.myCompositeParams); } - void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, Set theParamsToIndex) { + void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, @Nonnull ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { // Strings - ISearchParamExtractor.SearchParamSet strings = extractSearchParamStrings(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet strings = extractSearchParamStrings(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings); theParams.myStringParams.addAll(strings); // Numbers - ISearchParamExtractor.SearchParamSet numbers = extractSearchParamNumber(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet numbers = extractSearchParamNumber(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers); theParams.myNumberParams.addAll(numbers); // Quantities - ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities); theParams.myQuantityParams.addAll(quantities); if (myStorageSettings.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED) || myStorageSettings.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) { - ISearchParamExtractor.SearchParamSet quantitiesNormalized = extractSearchParamQuantityNormalized(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet quantitiesNormalized = extractSearchParamQuantityNormalized(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized); theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized); } // Dates - ISearchParamExtractor.SearchParamSet dates = extractSearchParamDates(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet dates = extractSearchParamDates(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates); theParams.myDateParams.addAll(dates); // URIs - ISearchParamExtractor.SearchParamSet uris = extractSearchParamUri(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet uris = extractSearchParamUri(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris); theParams.myUriParams.addAll(uris); // Tokens (can result in both Token and String, as we index the display name for // the types: Coding, CodeableConcept) - ISearchParamExtractor.SearchParamSet tokens = extractSearchParamTokens(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet tokens = extractSearchParamTokens(theResource, theSearchParamFilter); for (BaseResourceIndexedSearchParam next : tokens) { if (next instanceof ResourceIndexedSearchParamToken) { theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next); @@ -381,13 +434,13 @@ public class SearchParamExtractorService { // Composites // dst2 composites use stuff like value[x] , and we don't support them. if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { - ISearchParamExtractor.SearchParamSet composites = extractSearchParamComposites(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet composites = extractSearchParamComposites(theResource, theSearchParamFilter); handleWarnings(theRequestDetails, myInterceptorBroadcaster, composites); theParams.myCompositeParams.addAll(composites); } // Specials - ISearchParamExtractor.SearchParamSet specials = extractSearchParamSpecial(theResource, theParamsToIndex); + ISearchParamExtractor.SearchParamSet specials = extractSearchParamSpecial(theResource, theSearchParamFilter); for (BaseResourceIndexedSearchParam next : specials) { if (next instanceof ResourceIndexedSearchParamCoords) { theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next); @@ -543,7 +596,7 @@ public class SearchParamExtractorService { */ myResourceLinkResolver.validateTypeOrThrowException(type); - /** + /* * We need to obtain a resourceLink out of the provided {@literal thePathAndRef}. In the case * where we are updating a resource that already has resourceLinks (stored in {@literal theExistingParams.getResourceLinks()}), * let's try to match thePathAndRef to an already existing resourceLink to avoid the @@ -664,9 +717,8 @@ public class SearchParamExtractorService { } } + @SuppressWarnings("unchecked") private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, String theSourceResourceName, PathAndRef thePathAndRef, ResourceTable theEntity, Date theUpdateTime, IIdType theNextId, RequestDetails theRequest, TransactionDetails theTransactionDetails) { - assert theRequestPartitionId != null; - JpaPid resolvedResourceId = (JpaPid) theTransactionDetails.getResolvedResourceId(theNextId); if (resolvedResourceId != null) { String targetResourceType = theNextId.getResourceType(); @@ -742,40 +794,40 @@ public class SearchParamExtractorService { } } - private ISearchParamExtractor.SearchParamSet extractSearchParamDates(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamDates(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamDates(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamDates(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamNumber(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamNumber(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamNumber(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamNumber(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamQuantity(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamQuantity(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamQuantity(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamQuantity(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamStrings(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamStrings(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamStrings(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamStrings(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamTokens(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamTokens(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamTokens(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamTokens(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamSpecial(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamSpecial(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamSpecial(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamSpecial(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamUri(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamUri(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamUri(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamUri(theResource, theSearchParamFilter); } - private ISearchParamExtractor.SearchParamSet extractSearchParamComposites(IBaseResource theResource, Set theParamsToIndex) { - return mySearchParamExtractor.extractSearchParamComposites(theResource, theParamsToIndex); + private ISearchParamExtractor.SearchParamSet extractSearchParamComposites(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { + return mySearchParamExtractor.extractSearchParamComposites(theResource, theSearchParamFilter); } @VisibleForTesting @@ -812,12 +864,13 @@ public class SearchParamExtractorService { /** * Which search parameters should be indexed for the resource target * at the given path. In other words if thePathAndRef contains - * "Patient/123", then we could return a Set containing "name" and "gender" - * if we only want those two parameters to be indexed for the - * resolved Patient resource with that ID. + * "Patient/123", then we could return a filter that only lets the + * "name" and "gender" search params through if we only want those + * two parameters to be indexed for the resolved Patient resource + * with that ID. */ @Nonnull - Set getChainedSearchParametersToIndexForPath(@Nonnull PathAndRef thePathAndRef); + ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef); /** * Actually fetch the resource at the given path, or return diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 769de666558..75251619e01 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index ed11b71f83d..7f30b4b2f17 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index af7dac0133b..0060faff615 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 13e6ff0daaa..a45893bb3b3 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java index 1d9bc6dde35..9e985d8fa04 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java @@ -28,8 +28,10 @@ import java.util.Comparator; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.in; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -167,6 +169,59 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T } + @Test + public void testCreateAndUpdateResource() { + createNamesAndGenderSp(); + + // Create a resource patching the unique SP + myCaptureQueriesListener.clear(); + IIdType id1 = createPatient1(); + assertNotNull(id1); + + assertEquals(0, myCaptureQueriesListener.countSelectQueries()); + assertEquals(12, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + assertEquals(1, myCaptureQueriesListener.countCommits()); + assertEquals(0, myCaptureQueriesListener.countRollbacks()); + + runInTransaction(()->{ + List indexes = myResourceIndexedComboTokensNonUniqueDao + .findAll() + .stream() + .map(ResourceIndexedComboTokenNonUnique::getIndexString) + .toList(); + assertThat(indexes.toString(), indexes, contains("Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1")); + }); + + /* + * Now update the resource + */ + + Patient patient = myPatientDao.read(id1, mySrd); + patient.getNameFirstRep().setFamily("Family2"); + + myCaptureQueriesListener.clear(); + myPatientDao.update(patient, mySrd); + + assertEquals(6, myCaptureQueriesListener.countSelectQueries()); + assertEquals(1, myCaptureQueriesListener.countInsertQueries()); + assertEquals(5, myCaptureQueriesListener.countUpdateQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + assertEquals(1, myCaptureQueriesListener.countCommits()); + assertEquals(0, myCaptureQueriesListener.countRollbacks()); + + runInTransaction(()->{ + List indexes = myResourceIndexedComboTokensNonUniqueDao + .findAll() + .stream() + .map(ResourceIndexedComboTokenNonUnique::getIndexString) + .toList(); + assertThat(indexes.toString(), indexes, contains("Patient?family=FAMILY2%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1")); + }); + + } + @Test public void testSearchWithExtraParameters() { createNamesAndGenderSp(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index 2f88b3db09e..0db25d40305 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum; +import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; import ca.uhn.fhir.jpa.entity.TermValueSet; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.model.entity.ForcedId; @@ -54,6 +55,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.util.comparator.ComparableComparator; @@ -79,9 +81,25 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +/** + * Note about this test class: + *

+ * This entire test class is a regression test - The aim here is to make sure that + * changes we make don't inadvertently add additional database operations. The + * various test perform different kinds of actions and then check the numbers of + * SQL selects, inserts, etc. The various numbers are arbitrary, but the point of + * this test is that if you make a change and suddenly one of these tests shows + * that a new SQL statement has been added, it is critical that you identify why + * that change has happened and work out if it is absolutely necessary. Every + * single individual SQL statement adds up when we're doing operations at scale, + * so don't ever blindly adjust numbers in this test without figuring out why. + */ +@SuppressWarnings("JavadocBlankLines") @TestMethodOrder(MethodOrderer.MethodName.class) public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class); + @Autowired + private ISearchParamPresentDao mySearchParamPresentDao; @AfterEach public void afterResetDao() { @@ -113,7 +131,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myValidationSupport.fetchAllStructureDefinitions(); } - + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdateWithNoChanges() { IIdType id = runInTransaction(() -> { @@ -137,7 +157,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } - + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdateWithChanges() { IIdType id = runInTransaction(() -> { @@ -163,6 +185,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdateGroup_withAddedReferences_willSucceed() { int initialPatientsCount = 30; @@ -186,6 +211,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdateGroup_NoChangesToReferences() { List patientList = createPatients(30); @@ -208,6 +236,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdateWithChangesAndTags() { myStorageSettings.setTagStorageMode(JpaStorageSettings.TagStorageModeEnum.NON_VERSIONED); @@ -241,7 +272,83 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + /** + * See the class javadoc before changing the counts in this test! + */ + @Test + public void testUpdateWithIndexMissingFieldsEnabled() { + myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.ENABLED); + IIdType id = runInTransaction(() -> { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("2"); + p.addName().setFamily("FAMILY"); + myCaptureQueriesListener.clear(); + return myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + }); + assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(6, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + assertEquals(1, myCaptureQueriesListener.countCommits()); + assertEquals(0, myCaptureQueriesListener.countRollbacks()); + + runInTransaction(() -> { + assertEquals(9, myResourceIndexedSearchParamStringDao.count()); + assertEquals(9, myResourceIndexedSearchParamTokenDao.count()); + assertEquals(3, mySearchParamPresentDao.count()); + }); + + // Now update with one additional string index + + runInTransaction(() -> { + Patient p = new Patient(); + p.setId(id); + p.addIdentifier().setSystem("urn:system").setValue("2"); + p.addName().setFamily("FAMILY").addGiven("GIVEN"); + myCaptureQueriesListener.clear(); + myPatientDao.update(p, mySrd); + }); + assertEquals(6, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + assertEquals(1, myCaptureQueriesListener.countCommits()); + assertEquals(0, myCaptureQueriesListener.countRollbacks()); + + runInTransaction(() -> { + assertEquals(11, myResourceIndexedSearchParamStringDao.count()); + assertEquals(9, myResourceIndexedSearchParamTokenDao.count()); + assertEquals(3, mySearchParamPresentDao.count()); + }); + + // Now update with no changes + + runInTransaction(() -> { + Patient p = new Patient(); + p.setId(id); + p.addIdentifier().setSystem("urn:system").setValue("2"); + p.addName().setFamily("FAMILY").addGiven("GIVEN"); + myCaptureQueriesListener.clear(); + myPatientDao.update(p, mySrd); + }); + assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + assertEquals(1, myCaptureQueriesListener.countCommits()); + assertEquals(0, myCaptureQueriesListener.countRollbacks()); + + runInTransaction(() -> { + assertEquals(11, myResourceIndexedSearchParamStringDao.count()); + assertEquals(9, myResourceIndexedSearchParamTokenDao.count()); + assertEquals(3, mySearchParamPresentDao.count()); + }); + } + + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdate_DeletesSearchUrlOnlyWhenPresent() { @@ -273,6 +380,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdate_DeletesSearchUrlOnlyWhenPresent_NonConditional() { @@ -304,6 +414,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testRead() { IIdType id = runInTransaction(() -> { @@ -327,6 +440,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testValidate() { @@ -379,6 +495,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testVRead() { IIdType id = runInTransaction(() -> { @@ -402,6 +521,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testCreateWithClientAssignedId() { myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED); @@ -439,6 +561,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testCreateWithServerAssignedId_AnyClientAssignedIdStrategy() { myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY); @@ -470,10 +595,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } List resources = myResourceTableDao.findAll(); - String versions = "Resource Versions:\n * " + resources - .stream() - .map(t -> "Resource " + t.getIdDt() + " has version: " + t.getVersion()) - .collect(Collectors.joining("\n * ")); + String versions = "Resource Versions:\n * " + resources.stream().map(t -> "Resource " + t.getIdDt() + " has version: " + t.getVersion()).collect(Collectors.joining("\n * ")); for (ResourceTable next : resources) { assertEquals(1, next.getVersion(), versions); @@ -490,6 +612,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testCreateWithClientAssignedId_AnyClientAssignedIdStrategy() { myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY); @@ -535,10 +660,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } List resources = myResourceTableDao.findAll(); - String versions = "Resource Versions:\n * " + resources - .stream() - .map(t -> "Resource " + t.getIdDt() + " has version: " + t.getVersion()) - .collect(Collectors.joining("\n * ")); + String versions = "Resource Versions:\n * " + resources.stream().map(t -> "Resource " + t.getIdDt() + " has version: " + t.getVersion()).collect(Collectors.joining("\n * ")); for (ResourceTable next : resources) { assertEquals(1, next.getVersion(), versions); @@ -554,6 +676,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testCreateWithClientAssignedId_CheckDisabledMode() { when(mySrd.getHeader(eq(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK))).thenReturn(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK_DISABLED); @@ -576,6 +701,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testUpdateWithClientAssignedId_DeletesDisabled() { myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED); @@ -629,6 +757,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testReferenceToForcedId() { myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED); @@ -675,6 +806,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testReferenceToForcedId_DeletesDisabled() { myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED); @@ -730,6 +864,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testHistory_Server() { myStorageSettings.setHistoryCountMode(HistoryCountModeEnum.COUNT_ACCURATE); @@ -788,6 +925,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test * This could definitely stand to be optimized some, since we load tags individually * for each resource */ + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testHistory_Server_WithTags() { myStorageSettings.setHistoryCountMode(HistoryCountModeEnum.COUNT_ACCURATE); @@ -847,6 +987,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchAndPageThroughResults_SmallChunksOnSameBundleProvider() { List ids = create150Patients(); @@ -871,6 +1014,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getRollbackCount()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchAndPageThroughResults_LargeChunksOnIndependentBundleProvider() { List ids = create150Patients(); @@ -893,6 +1039,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getRollbackCount()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchAndPageThroughResults_LargeChunksOnSameBundleProvider_Synchronous() { List ids = create150Patients(); @@ -930,6 +1079,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchUsingOffsetMode_Explicit() { for (int i = 0; i < 10; i++) { @@ -943,17 +1095,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test // First page myCaptureQueriesListener.clear(); - Bundle outcome = myClient - .search() - .forResource("Patient") - .where(Patient.ACTIVE.exactly().code("true")) - .offset(0) - .count(5) - .returnBundle(Bundle.class) - .execute(); - assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder( - "Patient/A0", "Patient/A1", "Patient/A2", "Patient/A3", "Patient/A4" - )); + Bundle outcome = myClient.search().forResource("Patient").where(Patient.ACTIVE.exactly().code("true")).offset(0).count(5).returnBundle(Bundle.class).execute(); + assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder("Patient/A0", "Patient/A1", "Patient/A2", "Patient/A3", "Patient/A4")); myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); @@ -968,17 +1111,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test // Second page myCaptureQueriesListener.clear(); - outcome = myClient - .search() - .forResource("Patient") - .where(Patient.ACTIVE.exactly().code("true")) - .offset(5) - .count(5) - .returnBundle(Bundle.class) - .execute(); - assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder( - "Patient/A5", "Patient/A6", "Patient/A7", "Patient/A8", "Patient/A9" - )); + outcome = myClient.search().forResource("Patient").where(Patient.ACTIVE.exactly().code("true")).offset(5).count(5).returnBundle(Bundle.class).execute(); + assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder("Patient/A5", "Patient/A6", "Patient/A7", "Patient/A8", "Patient/A9")); myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); @@ -995,14 +1129,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test // Third page (no results) myCaptureQueriesListener.clear(); - outcome = myClient - .search() - .forResource("Patient") - .where(Patient.ACTIVE.exactly().code("true")) - .offset(10) - .count(5) - .returnBundle(Bundle.class) - .execute(); + outcome = myClient.search().forResource("Patient").where(Patient.ACTIVE.exactly().code("true")).offset(10).count(5).returnBundle(Bundle.class).execute(); assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), empty()); myCaptureQueriesListener.logSelectQueries(); assertEquals(1, myCaptureQueriesListener.countSelectQueries()); @@ -1016,6 +1143,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchUsingForcedIdReference() { @@ -1056,6 +1186,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchUsingForcedIdReference_DeletedDisabled() { myStorageSettings.setDeleteEnabled(false); @@ -1097,6 +1230,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchOnChainedToken() { Patient patient = new Patient(); @@ -1125,6 +1261,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchOnReverseInclude() { Patient patient = new Patient(); @@ -1153,18 +1292,12 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myCareTeamDao.update(ct); } - SearchParameterMap map = SearchParameterMap - .newSynchronous() - .addRevInclude(CareTeam.INCLUDE_SUBJECT) - .setSort(new SortSpec(Patient.SP_NAME)); + SearchParameterMap map = SearchParameterMap.newSynchronous().addRevInclude(CareTeam.INCLUDE_SUBJECT).setSort(new SortSpec(Patient.SP_NAME)); myCaptureQueriesListener.clear(); IBundleProvider outcome = myPatientDao.search(map); assertEquals(SimpleBundleProvider.class, outcome.getClass()); - assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder( - "Patient/P1", "CareTeam/CT1-0", "CareTeam/CT1-1", "CareTeam/CT1-2", - "Patient/P2", "CareTeam/CT2-0", "CareTeam/CT2-1", "CareTeam/CT2-2" - )); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder("Patient/P1", "CareTeam/CT1-0", "CareTeam/CT1-1", "CareTeam/CT1-2", "Patient/P2", "CareTeam/CT2-0", "CareTeam/CT2-1", "CareTeam/CT2-2")); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); @@ -1174,6 +1307,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchWithMultipleIncludes_Async() { // Setup @@ -1205,6 +1341,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test }); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchWithMultipleIncludesRecurse_Async() { // Setup @@ -1230,6 +1369,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchWithMultipleIncludes_Sync() { // Setup @@ -1256,6 +1398,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testSearchWithMultipleIncludesRecurse_Sync() { // Setup @@ -1282,6 +1427,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleCreates() { myStorageSettings.setMassIngestionMode(true); @@ -1348,6 +1496,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test return (Bundle) bb.getBundle(); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleCreates_PreExistingMatchUrl() { myStorageSettings.setMassIngestionMode(true); @@ -1398,6 +1549,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithTwoCreates() { @@ -1431,6 +1585,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test runInTransaction(() -> assertEquals(2, myResourceTableDao.count())); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleUpdates() { @@ -1508,6 +1665,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleUpdates_ResourcesHaveTags() { @@ -1591,6 +1751,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleInlineMatchUrls() { myStorageSettings.setDeleteEnabled(false); @@ -1637,6 +1800,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleInlineMatchUrlsWithAuthentication() { myStorageSettings.setDeleteEnabled(false); @@ -1688,6 +1854,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleForcedIdReferences() { myStorageSettings.setDeleteEnabled(false); @@ -1742,6 +1911,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleNumericIdReferences() { myStorageSettings.setDeleteEnabled(false); @@ -1794,6 +1966,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleConditionalUpdates() { @@ -1904,6 +2079,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithConditionalCreate_MatchUrlCacheEnabled() { myStorageSettings.setMatchUrlCacheEnabled(true); @@ -1967,6 +2145,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithConditionalCreate_MatchUrlCacheNotEnabled() { @@ -2034,6 +2215,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithCreateClientAssignedIdAndReference() { myStorageSettings.setDeleteEnabled(false); @@ -2043,22 +2227,12 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test Patient patient = new Patient(); patient.setId("Patient/A"); patient.setActive(true); - input.addEntry() - .setFullUrl(patient.getId()) - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.PUT) - .setUrl("Patient/A"); + input.addEntry().setFullUrl(patient.getId()).setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.PUT).setUrl("Patient/A"); Observation observation = new Observation(); observation.setId(IdType.newRandomUuid()); observation.addReferenceRange().setText("A"); - input.addEntry() - .setFullUrl(observation.getId()) - .setResource(observation) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Observation"); + input.addEntry().setFullUrl(observation.getId()).setResource(observation).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Observation"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2079,22 +2253,12 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test patient = new Patient(); patient.setId("Patient/A"); patient.setActive(true); - input.addEntry() - .setFullUrl(patient.getId()) - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.PUT) - .setUrl("Patient/A"); + input.addEntry().setFullUrl(patient.getId()).setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.PUT).setUrl("Patient/A"); observation = new Observation(); observation.setId(IdType.newRandomUuid()); observation.addReferenceRange().setText("A"); - input.addEntry() - .setFullUrl(observation.getId()) - .setResource(observation) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Observation"); + input.addEntry().setFullUrl(observation.getId()).setResource(observation).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Observation"); myCaptureQueriesListener.clear(); output = mySystemDao.transaction(mySrd, input); @@ -2112,6 +2276,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleReferences() { Bundle input = new Bundle(); @@ -2119,22 +2286,12 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test Patient patient = new Patient(); patient.setId(IdType.newRandomUuid()); patient.setActive(true); - input.addEntry() - .setFullUrl(patient.getId()) - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Patient"); + input.addEntry().setFullUrl(patient.getId()).setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient"); Practitioner practitioner = new Practitioner(); practitioner.setId(IdType.newRandomUuid()); practitioner.setActive(true); - input.addEntry() - .setFullUrl(practitioner.getId()) - .setResource(practitioner) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Practitioner"); + input.addEntry().setFullUrl(practitioner.getId()).setResource(practitioner).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Practitioner"); ServiceRequest sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); @@ -2143,12 +2300,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); @@ -2157,12 +2309,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2179,6 +2326,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultiplePreExistingReferences_ForcedId() { myStorageSettings.setDeleteEnabled(true); @@ -2201,23 +2351,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2239,23 +2379,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); output = mySystemDao.transaction(mySrd, input); @@ -2270,6 +2400,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultiplePreExistingReferences_Numeric() { myStorageSettings.setDeleteEnabled(true); @@ -2289,23 +2422,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2328,23 +2451,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); output = mySystemDao.transaction(mySrd, input); @@ -2359,6 +2472,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultiplePreExistingReferences_ForcedId_DeletesDisabled() { myStorageSettings.setDeleteEnabled(false); @@ -2381,23 +2497,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2419,23 +2525,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); output = mySystemDao.transaction(mySrd, input); @@ -2450,6 +2546,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultiplePreExistingReferences_Numeric_DeletesDisabled() { myStorageSettings.setDeleteEnabled(false); @@ -2469,23 +2568,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2507,23 +2596,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReferenceElement(patientId); sr.addPerformer().setReferenceElement(practitionerId); sr.addPerformer().setReferenceElement(practitionerId); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); output = mySystemDao.transaction(mySrd, input); @@ -2538,6 +2617,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultiplePreExistingReferences_IfNoneExist() { myStorageSettings.setDeleteEnabled(true); @@ -2559,46 +2641,24 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test patient = new Patient(); patient.setId(IdType.newRandomUuid()); patient.setActive(true); - input.addEntry() - .setFullUrl(patient.getId()) - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Patient") - .setIfNoneExist("Patient?active=true"); + input.addEntry().setFullUrl(patient.getId()).setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient").setIfNoneExist("Patient?active=true"); practitioner = new Practitioner(); practitioner.setId(IdType.newRandomUuid()); practitioner.setActive(true); - input.addEntry() - .setFullUrl(practitioner.getId()) - .setResource(practitioner) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Practitioner") - .setIfNoneExist("Practitioner?active=true"); + input.addEntry().setFullUrl(practitioner.getId()).setResource(practitioner).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Practitioner").setIfNoneExist("Practitioner?active=true"); ServiceRequest sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); Bundle output = mySystemDao.transaction(mySrd, input); @@ -2618,46 +2678,24 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test patient = new Patient(); patient.setId(IdType.newRandomUuid()); patient.setActive(true); - input.addEntry() - .setFullUrl(patient.getId()) - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Patient") - .setIfNoneExist("Patient?active=true"); + input.addEntry().setFullUrl(patient.getId()).setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient").setIfNoneExist("Patient?active=true"); practitioner = new Practitioner(); practitioner.setId(IdType.newRandomUuid()); practitioner.setActive(true); - input.addEntry() - .setFullUrl(practitioner.getId()) - .setResource(practitioner) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Practitioner") - .setIfNoneExist("Practitioner?active=true"); + input.addEntry().setFullUrl(practitioner.getId()).setResource(practitioner).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Practitioner").setIfNoneExist("Practitioner?active=true"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); sr = new ServiceRequest(); sr.getSubject().setReference(patient.getId()); sr.addPerformer().setReference(practitioner.getId()); sr.addPerformer().setReference(practitioner.getId()); - input.addEntry() - .setFullUrl(sr.getId()) - .setResource(sr) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("ServiceRequest"); + input.addEntry().setFullUrl(sr.getId()).setResource(sr).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("ServiceRequest"); myCaptureQueriesListener.clear(); output = mySystemDao.transaction(mySrd, input); @@ -2672,6 +2710,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithMultipleProfiles() { myStorageSettings.setDeleteEnabled(true); @@ -2685,11 +2726,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test patient.getMeta().addProfile("http://example.com/profile"); patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-1"); patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-2"); - input.addEntry() - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Patient"); + input.addEntry().setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient"); } myCaptureQueriesListener.clear(); @@ -2709,11 +2746,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test patient.getMeta().addProfile("http://example.com/profile"); patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-1"); patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-2"); - input.addEntry() - .setResource(patient) - .getRequest() - .setMethod(Bundle.HTTPVerb.POST) - .setUrl("Patient"); + input.addEntry().setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient"); } myCaptureQueriesListener.clear(); @@ -2733,6 +2766,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test * need to be resolved (ie SQL SELECT) in order to proceed with the transaction. Prior * to the optimization that introduced this test, we had 140 SELECTs, now it's 17. */ + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithManyInlineMatchUrls() throws IOException { myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true); @@ -2752,13 +2788,16 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(input.getEntry().size(), output.getEntry().size()); - runInTransaction(()->{ + runInTransaction(() -> { assertEquals(437, myResourceTableDao.count()); assertEquals(437, myResourceHistoryTableDao.count()); }); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testTransactionWithClientAssignedId() { BundleBuilder bb = new BundleBuilder(myFhirContext); @@ -2781,6 +2820,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testValueSetExpand_NotPreExpanded_UseHibernateSearch() { createLocalCsAndVs(); @@ -2819,6 +2861,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.countRollbacks()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testValueSetExpand_NotPreExpanded_DontUseHibernateSearch() { TermReadSvcImpl.setForceDisableHibernateSearchForUnitTest(true); @@ -2859,6 +2904,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.countRollbacks()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testValueSetExpand_PreExpanded_UseHibernateSearch() { createLocalCsAndVs(); @@ -2903,6 +2951,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(0, myCaptureQueriesListener.countRollbacks()); } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testMassIngestionMode_TransactionWithChanges() { myStorageSettings.setDeleteEnabled(false); @@ -2910,10 +2961,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myStorageSettings.setMassIngestionMode(true); myFhirContext.getParserOptions().setStripVersionsFromReferences(false); myStorageSettings.setRespectVersionsForSearchIncludes(true); - myStorageSettings.setAutoVersionReferenceAtPaths( - "ExplanationOfBenefit.patient", - "ExplanationOfBenefit.insurance.coverage" - ); + myStorageSettings.setAutoVersionReferenceAtPaths("ExplanationOfBenefit.patient", "ExplanationOfBenefit.insurance.coverage"); Patient warmUpPt = new Patient(); warmUpPt.getMeta().addProfile("http://foo"); @@ -2967,6 +3015,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testMassIngestionMode_TransactionWithChanges_2() throws IOException { myStorageSettings.setDeleteEnabled(false); @@ -2975,10 +3026,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myFhirContext.getParserOptions().setStripVersionsFromReferences(false); myStorageSettings.setRespectVersionsForSearchIncludes(true); myStorageSettings.setTagStorageMode(JpaStorageSettings.TagStorageModeEnum.NON_VERSIONED); - myStorageSettings.setAutoVersionReferenceAtPaths( - "ExplanationOfBenefit.patient", - "ExplanationOfBenefit.insurance.coverage" - ); + myStorageSettings.setAutoVersionReferenceAtPaths("ExplanationOfBenefit.patient", "ExplanationOfBenefit.insurance.coverage"); // Pre-cache tag definitions Patient patient = new Patient(); @@ -3010,15 +3058,15 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + /** + * See the class javadoc before changing the counts in this test! + */ @Test public void testDeleteResource_WithMassIngestionMode_enabled() { myStorageSettings.setMassIngestionMode(true); // given - Observation observation = new Observation() - .setStatus(Observation.ObservationStatus.FINAL) - .addCategory(new CodeableConcept().addCoding(new Coding("http://category-type", "12345", null))) - .setCode(new CodeableConcept().addCoding(new Coding("http://coverage-type", "12345", null))); + Observation observation = new Observation().setStatus(Observation.ObservationStatus.FINAL).addCategory(new CodeableConcept().addCoding(new Coding("http://category-type", "12345", null))).setCode(new CodeableConcept().addCoding(new Coding("http://coverage-type", "12345", null))); IIdType idDt = myObservationDao.create(observation, mySrd).getEntity().getIdDt(); @@ -3030,15 +3078,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertQueryCount(3, 1, 1, 2); } - private void printQueryCount() { - - ourLog.info("\tselect: {}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); - ourLog.info("\tupdate: {}", myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); - ourLog.info("\tinsert: {}", myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); - ourLog.info("\tdelete: {}", myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); - - } - private void assertQueryCount(int theExpectedSelectCount, int theExpectedUpdateCount, int theExpectedInsertCount, int theExpectedDeleteCount) { assertEquals(theExpectedSelectCount, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); @@ -3064,9 +3103,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test theGroup.addMember(aGroupMemberComponent); } - Group retVal = runInTransaction(() -> (Group) myGroupDao.update(theGroup, mySrd).getResource()); - - return retVal; + return runInTransaction(() -> (Group) myGroupDao.update(theGroup, mySrd).getResource()); } @@ -3080,14 +3117,13 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } private IIdType createAPatient() { - IIdType retVal = runInTransaction(() -> { + + return runInTransaction(() -> { Patient p = new Patient(); p.getMeta().addTag("http://system", "foo", "display"); p.addIdentifier().setSystem("urn:system").setValue("2"); return myPatientDao.create(p).getId().toUnqualified(); }); - - return retVal; } } diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index acbeb94438a..15e8a848032 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 86fa3e3fa66..7db7243efee 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -28,6 +28,12 @@ ${project.version} test + + net.sourceforge.htmlunit + htmlunit + test + + diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index bbf7b0ece51..b14c4150aa9 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -453,7 +453,6 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil purgeHibernateSearch(myEntityManager); myStorageSettings.setSchedulingDisabled(true); - myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.ENABLED); } @BeforeEach diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImplNarrativeR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImplNarrativeR5Test.java new file mode 100644 index 00000000000..94c7d260602 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImplNarrativeR5Test.java @@ -0,0 +1,312 @@ +package ca.uhn.fhir.jpa.search.reindex; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; +import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; +import ca.uhn.fhir.test.utilities.HtmlUtil; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlTable; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests the narrative generation in {@link InstanceReindexServiceImpl}. This is a separate test + * from {@literal ReindexDryRunServiceImplTest} because this test doesn't need the JPA + * infrastructure. + */ +@SuppressWarnings({"unchecked", "SqlDialectInspection"}) +public class InstanceReindexServiceImplNarrativeR5Test { + private static final Logger ourLog = LoggerFactory.getLogger(InstanceReindexServiceImplNarrativeR5Test.class); + private final FhirContext myCtx = FhirContext.forR4Cached(); + private final InstanceReindexServiceImpl mySvc = new InstanceReindexServiceImpl(); + private final PartitionSettings myPartitionSettings = new PartitionSettings(); + private final JpaStorageSettings myStorageSettings = new JpaStorageSettings(); + private final ResourceTable myEntity = new ResourceTable(); + + @Test + public void testIndexComboNonUnique() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myComboTokenNonUnique.add(new ResourceIndexedComboTokenNonUnique(myPartitionSettings, myEntity, "Patient?identifier=123")); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("NonUniqueIndexesTable"); + assertEquals("ADD", getBodyCellValue(table, 0, 0)); + assertEquals("ComboTokenNonUnique", getBodyCellValue(table, 0, 1)); + assertEquals("Patient?identifier=123", getBodyCellValue(table, 0, 2)); + } + + @Test + public void testIndexComboUnique() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myComboStringUniques.add(new ResourceIndexedComboStringUnique(myEntity, "Patient?identifier=123", new IdType("Parameter/foo"))); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("UniqueIndexesTable"); + assertEquals("ADD", getBodyCellValue(table, 0, 0)); + assertEquals("ComboStringUnique", getBodyCellValue(table, 0, 1)); + assertEquals("Patient?identifier=123", getBodyCellValue(table, 0, 2)); + } + + @Test + public void testIndexMissing() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myTokenParams.add(new ResourceIndexedSearchParamToken(myPartitionSettings, "Observation", "identifier", true)); + SearchParamPresentEntity subject = new SearchParamPresentEntity("subject", false); + subject.setResource(new ResourceTable()); + subject.setPartitionSettings(myPartitionSettings); + subject.calculateHashes(); + newParams.mySearchParamPresentEntities.add(subject); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("MissingIndexesTable"); + assertEquals("identifier", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Token", getBodyCellValue(table, 0, 2)); + assertEquals("true", getBodyCellValue(table, 0, 3)); + assertEquals("subject", getBodyCellValue(table, 1, 0)); + assertEquals("ADD", getBodyCellValue(table, 1, 1)); + assertEquals("Reference", getBodyCellValue(table, 1, 2)); + assertEquals("true", getBodyCellValue(table, 1, 3)); + } + + @Test + public void testIndexNumber() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myNumberParams.add(new ResourceIndexedSearchParamNumber(myPartitionSettings, "Immunization", "dose", BigDecimal.ONE)); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("NumberIndexesTable"); + assertEquals("dose", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Number", getBodyCellValue(table, 0, 2)); + assertEquals("1", getBodyCellValue(table, 0, 3)); + } + + @Test + public void testIndexResourceLink() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myLinks.add(ResourceLink.forLocalReference("Observation.subject", myEntity, "Patient", 123L, "123", new Date(), 555L)); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("ResourceLinksTable"); + assertEquals("Observation.subject", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Reference", getBodyCellValue(table, 0, 2)); + assertEquals("Patient/123", getBodyCellValue(table, 0, 3)); + assertEquals("555", getBodyCellValue(table, 0, 4)); + } + + @Test + public void testIndexResourceLinkLogical() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myLinks.add(ResourceLink.forLogicalReference("Observation.subject", myEntity, "http://foo/base/Patient/456", new Date())); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("ResourceLinksTable"); + assertEquals("Observation.subject", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Reference", getBodyCellValue(table, 0, 2)); + assertEquals("", getBodyCellValue(table, 0, 3)); + assertEquals("", getBodyCellValue(table, 0, 4)); + assertEquals("http://foo/base/Patient/456", getBodyCellValue(table, 0, 5)); + } + + @Test + public void testIndexResourceLinkAbsolute() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myLinks.add(ResourceLink.forAbsoluteReference("Observation.subject", myEntity, new IdType("http://foo/base/Patient/123"), new Date())); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("ResourceLinksTable"); + assertEquals("Observation.subject", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Reference", getBodyCellValue(table, 0, 2)); + assertEquals("", getBodyCellValue(table, 0, 3)); + assertEquals("", getBodyCellValue(table, 0, 4)); + assertEquals("http://foo/base/Patient/123", getBodyCellValue(table, 0, 5)); + } + + @Test + public void testIndexQuantity() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myQuantityParams.add(new ResourceIndexedSearchParamQuantity(myPartitionSettings, "Observation", "value-quantity", BigDecimal.valueOf(123), "http://unitsofmeasure.org", "kg")); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("QuantityIndexesTable"); + assertEquals("value-quantity", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Quantity", getBodyCellValue(table, 0, 2)); + assertEquals("123", getBodyCellValue(table, 0, 3)); + assertEquals("http://unitsofmeasure.org", getBodyCellValue(table, 0, 4)); + assertEquals("kg", getBodyCellValue(table, 0, 5)); + } + + @Test + public void testIndexQuantityNormalized() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myQuantityNormalizedParams.add(new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, "Observation", "value-quantity", 123.0, "http://unitsofmeasure.org", "kg")); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("QuantityIndexesTable"); + assertEquals("value-quantity", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("QuantityNormalized", getBodyCellValue(table, 0, 2)); + assertEquals("123.0", getBodyCellValue(table, 0, 3)); + assertEquals("http://unitsofmeasure.org", getBodyCellValue(table, 0, 4)); + assertEquals("kg", getBodyCellValue(table, 0, 5)); + } + + @Test + public void testIndexString() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myStringParams.add(new ResourceIndexedSearchParamString(myPartitionSettings, myStorageSettings, "Patient", "family", "Simpson", "SIMPSON")); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("StringIndexesTable"); + assertEquals("family", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("String", getBodyCellValue(table, 0, 2)); + assertEquals("Simpson", getBodyCellValue(table, 0, 3)); + assertEquals("SIMPSON", getBodyCellValue(table, 0, 4)); + } + + @Test + public void testIndexToken() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myTokenParams.add(new ResourceIndexedSearchParamToken(myPartitionSettings, "Observation", "identifier", "http://id-system", "id-value")); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("TokenIndexesTable"); + assertEquals("identifier", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Token", getBodyCellValue(table, 0, 2)); + assertEquals("http://id-system", getBodyCellValue(table, 0, 3)); + assertEquals("id-value", getBodyCellValue(table, 0, 4)); + } + + @Test + public void testIndexUrl() throws IOException { + // Setup + ResourceIndexedSearchParams newParams = newParams(); + newParams.myUriParams.add(new ResourceIndexedSearchParamUri(myPartitionSettings, "CodeSystem", "uri", "http://some-codesystem")); + + // Test + Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true); + ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + // Verify + HtmlPage narrativeHtml = extractNarrative(outcome); + HtmlTable table = (HtmlTable) narrativeHtml.getElementById("UriIndexesTable"); + assertEquals("uri", getBodyCellValue(table, 0, 0)); + assertEquals("ADD", getBodyCellValue(table, 0, 1)); + assertEquals("Uri", getBodyCellValue(table, 0, 2)); + assertEquals("http://some-codesystem", getBodyCellValue(table, 0, 3)); + } + + @Nonnull + private static HtmlPage extractNarrative(Parameters outcome) throws IOException { + StringType narrative = (StringType) outcome.getParameter().get(0).getValue(); + HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(narrative.getValueAsString()); + ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); + return narrativeHtml; + } + + private static String getBodyCellValue(HtmlTable table, int theRow, int theCol) { + return table.getBodies().get(0).getRows().get(theRow).getCell(theCol).asNormalizedText(); + } + + @Nonnull + private static ResourceIndexedSearchParams newParams() { + return new ResourceIndexedSearchParams(); + } + +} diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImplR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImplR5Test.java new file mode 100644 index 00000000000..a2b0797e6db --- /dev/null +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/search/reindex/InstanceReindexServiceImplR5Test.java @@ -0,0 +1,398 @@ +package ca.uhn.fhir.jpa.search.reindex; + +import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; +import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test; +import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.util.HapiExtensions; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.Enumerations; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.Patient; +import org.hl7.fhir.r5.model.ResearchStudy; +import org.hl7.fhir.r5.model.SearchParameter; +import org.hl7.fhir.r5.model.StringType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SuppressWarnings({"unchecked", "SqlDialectInspection"}) +public class InstanceReindexServiceImplR5Test extends BaseJpaR5Test { + + @Autowired + private IInstanceReindexService mySvc; + + @Override + @BeforeEach + public void beforeResetConfig() { + super.beforeResetConfig(); + + myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED); + } + + @Override + public void afterCleanupDao() { + super.afterCleanupDao(); + + JpaStorageSettings defaults = new JpaStorageSettings(); + myStorageSettings.setIndexMissingFields(defaults.getIndexMissingFields()); + myStorageSettings.setNormalizedQuantitySearchLevel(defaults.getNormalizedQuantitySearchLevel()); + } + + + @Test + public void testDryRunMissing() { + myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.ENABLED); + + IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + List sections = outcome.getParameters("MissingIndexes"); + assertEquals(1, sections.size()); + + List indexInstances = sections + .get(0) + .getPart() + .stream() + .map(t -> t.getName() + " " + getPartValue("Action", t) + " " + getPartValue("Type", t) + " " + getPartValue("Missing", t)) + .sorted() + .toList(); + assertThat(indexInstances.toString(), indexInstances, contains( + "_profile NO_CHANGE Reference true", + "active NO_CHANGE Token true", + "address NO_CHANGE String true", + "address-city NO_CHANGE String true", + "address-country NO_CHANGE String true", + "address-postalcode NO_CHANGE String true", + "address-state NO_CHANGE String true", + "address-use NO_CHANGE Token true", + "age NO_CHANGE Number true", + "birthOrderBoolean NO_CHANGE Token true", + "birthdate NO_CHANGE Date true", + "death-date NO_CHANGE Date true", + "email NO_CHANGE Token true", + "gender NO_CHANGE Token true", + "general-practitioner NO_CHANGE Reference true", + "identifier NO_CHANGE Token true", + "language NO_CHANGE Token true", + "link NO_CHANGE Reference true", + "mothersMaidenName NO_CHANGE String true", + "organization NO_CHANGE Reference true", + "part-agree NO_CHANGE Reference true", + "phone NO_CHANGE Token true", + "telecom NO_CHANGE Token true" + )); + } + + + @Test + public void testDryRunTypes_ComboNonUniqueSearchParam() { + createNamesAndGenderSp(false); + + IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer")); + + runInTransaction(this::logAllNonUniqueIndexes); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "Patient?family=SIMPSON%5C%7C&given=HOMER", "NonUniqueIndexes"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + } + + @Test + public void testDryRunTypes_ComboUniqueSearchParam() { + createNamesAndGenderSp(true); + + IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findIndexes(outcome, "Patient?family=Simpson%5C%7C&given=Homer", 1, "UniqueIndexes").get(0); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + } + + @Test + public void testDryRunTypes_Number() { + IIdType id = createResource("ResearchStudy", withPrimitiveAttribute("recruitment.targetNumber", "3")); + + logAllNumberIndexes(); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, ResearchStudy.SP_RECRUITMENTTARGET, "NumberIndexes"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("Number", getPartValue("Type", index)); + assertEquals("3", getPartValue("Value", index)); + } + + @Test + public void testDryRunTypes_Quantity() { + myStorageSettings.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED); + + IIdType id = createObservation(withQuantityAtPath("valueQuantity", 1.2, "http://unitsofmeasure.org", "kg")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "value-quantity", "QuantityIndexes"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("Quantity", getPartValue("Type", index)); + assertEquals("http://unitsofmeasure.org", getPartValue("System", index)); + assertEquals("kg", getPartValue("Units", index)); + assertEquals(1.2d, getPartValueDecimal(index), 0.001d); + } + + @Test + public void testDryRunTypes_QuantityNormalized() { + myStorageSettings.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED); + + IIdType id = createObservation(withQuantityAtPath("valueQuantity", 1.2, "http://unitsofmeasure.org", "mg")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index; + + index = findIndexes(outcome, "value-quantity", 2, "QuantityIndexes").get(0); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("Quantity", getPartValue("Type", index)); + assertEquals("http://unitsofmeasure.org", getPartValue("System", index)); + assertEquals("mg", getPartValue("Units", index)); + assertEquals(1.2d, getPartValueDecimal(index), 0.001d); + + index = findIndexes(outcome, "value-quantity", 2, "QuantityIndexes").get(1); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("QuantityNormalized", getPartValue("Type", index)); + assertEquals("http://unitsofmeasure.org", getPartValue("System", index)); + assertEquals("g", getPartValue("Units", index)); + assertEquals(0.0012d, getPartValueDecimal(index), 0.001d); + } + + @Test + public void testDryRunTypes_ResourceLink() { + createPatient(withId("A"), withActiveTrue()); + IIdType id = createObservation(withSubject("Patient/A")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "Observation.subject", "ResourceLinks"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("Reference", getPartValue("Type", index)); + assertEquals("Patient/A", getPartValue("TargetId", index)); + } + + @Test + public void testDryRunTypes_ResourceLink_WithUrl() { + createPatient(withId("A"), withActiveTrue()); + IIdType id = createObservation(withSubject("Patient/A")); + + runInTransaction(() -> { + assertEquals(2, myEntityManager.createNativeQuery("update HFJ_RES_LINK set TARGET_RESOURCE_ID = null").executeUpdate()); + assertEquals(2, myEntityManager.createNativeQuery("update HFJ_RES_LINK set TARGET_RESOURCE_URL = 'http://foo'").executeUpdate()); + assertEquals(2, myEntityManager.createNativeQuery("update HFJ_RES_LINK set TARGET_RESOURCE_VERSION = 1").executeUpdate()); + }); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + List indexes = findIndexes(outcome, "Observation.subject", 2, "ResourceLinks"); + Parameters.ParametersParameterComponent index; + index = indexes.get(0); + assertEquals("ADD", getPartValue("Action", index)); + assertEquals("Reference", getPartValue("Type", index)); + assertEquals("Patient/A", getPartValue("TargetId", index)); + index = indexes.get(1); + assertEquals("REMOVE", getPartValue("Action", index)); + assertEquals("Reference", getPartValue("Type", index)); + assertEquals("http://foo", getPartValue("TargetUrl", index)); + assertEquals("1", getPartValue("TargetVersion", index)); + } + + @Test + public void testDryRunTypes_String() { + IIdType id = createPatient(withIdentifier("http://identifiers", "123"), withFamily("Smith")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "family", "StringIndexes"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("String", getPartValue("Type", index)); + assertEquals("SMITH", getPartValue("ValueNormalized", index)); + assertEquals("Smith", getPartValue("ValueExact", index)); + } + + @Test + public void testDryRunTypes_String_SpecificParameter() { + IIdType id = createPatient(withIdentifier("http://identifiers", "123"), withFamily("Simpson"), withGiven("Homer")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, Set.of("family")); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "family", "StringIndexes"); + assertEquals("UNKNOWN", getPartValue("Action", index)); + assertEquals("String", getPartValue("Type", index)); + assertEquals("SIMPSON", getPartValue("ValueNormalized", index)); + assertEquals("Simpson", getPartValue("ValueExact", index)); + + findIndexes(outcome, "family", 1, "StringIndexes"); + findIndexes(outcome, "given", 0, "StringIndexes"); + } + + @Test + public void testDryRunTypes_Token() { + IIdType id = createPatient(withIdentifier("http://identifiers", "123"), withFamily("Smith")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "identifier", "TokenIndexes"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("Token", getPartValue("Type", index)); + assertEquals("http://identifiers", getPartValue("System", index)); + assertEquals("123", getPartValue("Value", index)); + } + + @Test + public void testDryRunTypes_Uri() { + IIdType id = createResource("CodeSystem", withPrimitiveAttribute("url", "http://foo")); + + Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "system", "UriIndexes"); + assertEquals("NO_CHANGE", getPartValue("Action", index)); + assertEquals("Uri", getPartValue("Type", index)); + assertEquals("http://foo", getPartValue("Value", index)); + } + + @Test + public void testReindexInstance() { + Patient p1 = new Patient(); + p1.setActive(true); + p1.addExtension() + .setUrl("http://acme.org/eyecolour") + .setValue(new StringType("Gold")); + IIdType p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameter eyeColourSp = new SearchParameter(); + eyeColourSp.addBase("Patient"); + eyeColourSp.setCode("eyecolour"); + eyeColourSp.setType(Enumerations.SearchParamType.STRING); + eyeColourSp.setTitle("Eye Colour"); + eyeColourSp.setExpression("Patient.extension('http://acme.org/eyecolour')"); + eyeColourSp.setStatus(Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(eyeColourSp, mySrd); + mySearchParamRegistry.forceRefresh(); + + SearchParameterMap map = SearchParameterMap.newSynchronous("eyecolour", new StringParam("GOLD")); + assertEquals(0, myPatientDao.search(map, mySrd).size()); + + Parameters outcome = (Parameters) mySvc.reindex(mySrd, p1id); + ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "eyecolour", "StringIndexes"); + assertEquals("ADD", getPartValue("Action", index)); + assertEquals("String", getPartValue("Type", index)); + assertEquals("GOLD", getPartValue("ValueNormalized", index)); + assertEquals("Gold", getPartValue("ValueExact", index)); + + assertEquals(1, myPatientDao.search(map, mySrd).size()); + } + + + private void createNamesAndGenderSp(boolean theUnique) { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name.family + '|'"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-given"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("given"); + sp.setExpression("Patient.name.given"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-names-and-gender"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-given"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(theUnique)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + + } + + private double getPartValueDecimal(Parameters.ParametersParameterComponent theParent) { + return Double.parseDouble(getPartValue("Value", theParent)); + } + + private static Parameters.ParametersParameterComponent findSingleIndex(Parameters theResponse, String theParamName, String theSectionName) { + List indexInstances = findIndexes(theResponse, theParamName, 1, theSectionName); + return indexInstances.get(0); + } + + @Nonnull + private static List findIndexes(Parameters theResponse, String theParamName, int theExpectedSize, String theSectionName) { + List indexes = theResponse.getParameters(theSectionName); + assertEquals(1, indexes.size()); + + List indexInstances = indexes + .get(0) + .getPart() + .stream() + .filter(t -> t.getName().equals(theParamName)) + .toList(); + + assertEquals(theExpectedSize, indexInstances.size()); + return indexInstances; + } + + @Nonnull + private static String getPartValue(String thePartName, Parameters.ParametersParameterComponent theParent) { + return theParent + .getPart() + .stream() + .filter(t2 -> t2.getName().equals(thePartName)) + .findFirst() + .map(t -> (IPrimitiveType) t.getValue()) + .map(IPrimitiveType::getValueAsString) + .orElseThrow(() -> new IllegalArgumentException("Couldn't find part with name: " + thePartName)); + } + +} diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index 593166773e7..65d232f5191 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 8845e47a848..4dadb41083d 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index e86a90d65dd..5e1de37d6da 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -17,6 +17,7 @@ import ca.uhn.fhir.jpa.provider.DiffProvider; import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProvider; +import ca.uhn.fhir.jpa.provider.InstanceReindexProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; @@ -197,6 +198,7 @@ public class TestRestfulServer extends RestfulServer { } providers.add(myAppCtx.getBean(JpaSystemProvider.class)); + providers.add(myAppCtx.getBean(InstanceReindexProvider.class)); /* * On the DSTU2 endpoint, we want to enable ETag support diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 18c787a80cd..b82a166ff73 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index f657f31381d..23819e6d843 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index d33cf86ad70..2dc91e60774 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java index 17df6580667..6fd2193c640 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java @@ -163,6 +163,11 @@ public class ProviderConstants { */ public static final String OPERATION_REINDEX = "$reindex"; + /** + * Operation name for the $reindex operation + */ + public static final String OPERATION_REINDEX_DRYRUN = "$reindex-dryrun"; + /** * Operation name for the $invalidate-expansion operation */ diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index f6cf7ba5a9f..58f227ad6e9 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index 7912133ae12..0f077f0e57b 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index a8f7e02077f..433bbab340e 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index 013e1aa808b..0a29c28105d 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index fe20757fe21..a367e6a8341 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 8c41678de27..5be945f0595 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index dac11660199..8477ef9f049 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index d0a51f6a0c6..d9ec280b9fb 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index d3af5fe33bd..b0a4bc7ffe2 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index f9cc373ce10..69842f660fd 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 60424a5e97a..26393464f9d 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index c2b0e96867a..60faa43220b 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index fbf4a15ee28..f4127d5da27 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index e0e58c38231..665484ba293 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/ReindexProvider.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/ReindexProvider.java index 9a783a3fc8b..888d1e6fb3d 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/ReindexProvider.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/ReindexProvider.java @@ -56,7 +56,7 @@ public class ReindexProvider { } @Operation(name = ProviderConstants.OPERATION_REINDEX, idempotent = false) - public IBaseParameters Reindex( + public IBaseParameters reindex( @OperationParam(name = ProviderConstants.OPERATION_REINDEX_PARAM_URL, typeName = "string", min = 0, max = OperationParam.MAX_UNLIMITED) List> theUrlsToReindex, RequestDetails theRequestDetails ) { diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index 5dbb3798b88..99e5cc22ee2 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 17e040d6258..0b6ac7129a9 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index 8f4a9210f32..c960ea37786 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index 28d8666d9db..c9751559d32 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index 1ac4575a063..0767e858ac7 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index 471a415e848..62dfbae3d6c 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java index 11338f1933f..5516f17182a 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java @@ -119,7 +119,6 @@ public class JpaStorageSettings extends StorageSettings { */ @Nullable private Integer myMaximumIncludesToLoadPerPage = DEFAULT_MAXIMUM_INCLUDES_TO_LOAD_PER_PAGE; - private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED; /** * update setter javadoc if default changes */ @@ -847,53 +846,6 @@ public class JpaStorageSettings extends StorageSettings { myFetchSizeDefaultMaximum = theFetchSizeDefaultMaximum; } - /** - * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED}) - * the server will not create search indexes for search parameters with no values in resources. - *

- * Disabling this feature means that the :missing search modifier will not be - * supported on the server, but also means that storage and indexing (i.e. writes to the - * database) may be much faster on servers which have lots of search parameters and need - * to write quickly. - *

- *

- * This feature may be enabled on servers where supporting the use of the :missing parameter is - * of higher importance than raw write performance - *

- */ - public IndexEnabledEnum getIndexMissingFields() { - return myIndexMissingFieldsEnabled; - } - - /** - * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED}) - * the server will not create search indexes for search parameters with no values in resources. - *

- * Disabling this feature means that the :missing search modifier will not be - * supported on the server, but also means that storage and indexing (i.e. writes to the - * database) may be much faster on servers which have lots of search parameters and need - * to write quickly. - *

- *

- * This feature may be enabled on servers where supporting the use of the :missing parameter is - * of higher importance than raw write performance - *

- *

- * Note that this setting also has an impact on sorting (i.e. using the - * _sort parameter on searches): If the server is configured - * to not index missing field. - *

- *

- * The following index may need to be added into the indexed tables such as HFJ_SPIDX_TOKEN - * to improve the search performance while :missing is enabled. - * RES_TYPE, SP_NAME, SP_MISSING - *

- */ - public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) { - Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null"); - myIndexMissingFieldsEnabled = theIndexMissingFields; - } - /** * See {@link #setMaximumExpansionSize(int)} */ @@ -2379,10 +2331,6 @@ public class JpaStorageSettings extends StorageSettings { } - public enum IndexEnabledEnum { - ENABLED, - DISABLED - } /** * This enum provides allowable options for {@link #setResourceServerIdStrategy(IdStrategyEnum)} diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index face2dce956..fd162ce8ee9 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index a493e910ffb..4d3b92016e1 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 5f14eb0f224..bc19e586a74 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index 09f57e9dd79..2d2f63c1a57 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index e92ecc7b27f..7fa92c22b5f 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index b8ea37cf733..0cb64f4226b 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index fb29e8d37ee..c2a36cb5e60 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 7c4a05ca88b..48dea7d8a5d 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 6e2333c0e88..77237b41712 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 149dd003143..5923690f479 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index 8da5589007b..f789b421b97 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index 147443d6fac..fb97de406de 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 575ce4d6be2..cfa94aa7f5b 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index 6116f979884..d102d0f44ad 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index e502df52918..c46e0054951 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index a33a17da4c6..1177a61059f 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 78973cbd850..17828eb5a54 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index d1d19ae73a8..21376158f9f 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 647a95cbc7c..bb20a782587 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index aa7dd5c1c73..4c48719579a 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index 15824271bbe..1b2f98cad39 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index e03268c56c4..ad0e093e309 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.5.11-SNAPSHOT + 6.5.12-SNAPSHOT ../../pom.xml