From 8384325d78f55d43ee9259dc916ae8f15999d59d Mon Sep 17 00:00:00 2001 From: Etienne Poirier <33007955+epeartree@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:26:10 -0400 Subject: [PATCH] To provide the target resource partitionId and partitionDate in the resourceLinlk (#6149) * initial POC. * addressing comments from first code review * Adding tests * adding changelog and spotless * fixing tests * spotless --------- Co-authored-by: peartree --- ...source-partition-info-in-resourcelink.yaml | 5 + .../fhir/jpa/dao/data/IResourceTableDao.java | 9 +- .../data/custom/IResourceTableDaoImpl.java | 29 ++-- .../fhir/jpa/dao/index/IdHelperService.java | 32 ++-- .../tasks/HapiFhirJpaMigrationTasks.java | 24 ++- .../jpa/model/cross/JpaResourceLookup.java | 15 +- .../ca/uhn/fhir/jpa/model/dao/JpaPid.java | 11 ++ .../jpa/model/entity/BasePartitionable.java | 11 +- .../entity/PartitionablePartitionId.java | 6 + .../fhir/jpa/model/entity/ResourceLink.java | 138 ++++++++++++++++-- .../SearchParamExtractorService.java | 106 +++++++------- .../ResourceIndexedSearchParamsTest.java | 23 ++- .../jpa/dao/index/IdHelperServiceTest.java | 16 +- .../jpa/dao/r4/PartitioningSqlR4Test.java | 20 ++- ...anceReindexServiceImplNarrativeR5Test.java | 26 +++- 15 files changed, 345 insertions(+), 126 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6148-provide-target-resource-partition-info-in-resourcelink.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6148-provide-target-resource-partition-info-in-resourcelink.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6148-provide-target-resource-partition-info-in-resourcelink.yaml new file mode 100644 index 00000000000..9dca360dc4e --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6148-provide-target-resource-partition-info-in-resourcelink.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 6148 +jira: SMILE-8613 +title: "Added the target resource partitionId and partitionDate to the resourceLink table." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java index 21f233891e5..a081668cdae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java @@ -135,7 +135,8 @@ public interface IResourceTableDao * This method returns a Collection where each row is an element in the collection. Each element in the collection * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. */ - @Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid)") + @Query( + "SELECT t.myResourceType, t.myId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue FROM ResourceTable t WHERE t.myId IN (:pid)") Collection findLookupFieldsByResourcePid(@Param("pid") List thePids); /** @@ -143,7 +144,7 @@ public interface IResourceTableDao * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. */ @Query( - "SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IN :partition_id") + "SELECT t.myResourceType, t.myId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IN :partition_id") Collection findLookupFieldsByResourcePidInPartitionIds( @Param("pid") List thePids, @Param("partition_id") Collection thePartitionId); @@ -152,7 +153,7 @@ public interface IResourceTableDao * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. */ @Query( - "SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND (t.myPartitionIdValue IS NULL OR t.myPartitionIdValue IN :partition_id)") + "SELECT t.myResourceType, t.myId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue FROM ResourceTable t WHERE t.myId IN (:pid) AND (t.myPartitionIdValue IS NULL OR t.myPartitionIdValue IN :partition_id)") Collection findLookupFieldsByResourcePidInPartitionIdsOrNullPartition( @Param("pid") List thePids, @Param("partition_id") Collection thePartitionId); @@ -161,7 +162,7 @@ public interface IResourceTableDao * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. */ @Query( - "SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IS NULL") + "SELECT t.myResourceType, t.myId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IS NULL") Collection findLookupFieldsByResourcePidInPartitionNull(@Param("pid") List thePids); @Query("SELECT t.myVersion FROM ResourceTable t WHERE t.myId = :pid") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/custom/IResourceTableDaoImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/custom/IResourceTableDaoImpl.java index d05aac4b708..cd7e6d17cb7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/custom/IResourceTableDaoImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/custom/IResourceTableDaoImpl.java @@ -56,9 +56,10 @@ public class IResourceTableDaoImpl implements IForcedIdQueries { @Override public Collection findAndResolveByForcedIdWithNoType( String theResourceType, Collection theForcedIds, boolean theExcludeDeleted) { - String query = "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted " - + "FROM ResourceTable t " - + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id )"; + String query = + "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue " + + "FROM ResourceTable t " + + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id )"; if (theExcludeDeleted) { query += " AND t.myDeleted IS NULL"; @@ -82,9 +83,10 @@ public class IResourceTableDaoImpl implements IForcedIdQueries { Collection theForcedIds, Collection thePartitionId, boolean theExcludeDeleted) { - String query = "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted " - + "FROM ResourceTable t " - + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND t.myPartitionIdValue IN ( :partition_id )"; + String query = + "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue " + + "FROM ResourceTable t " + + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND t.myPartitionIdValue IN ( :partition_id )"; if (theExcludeDeleted) { query += " AND t.myDeleted IS NULL"; @@ -106,9 +108,11 @@ public class IResourceTableDaoImpl implements IForcedIdQueries { @Override public Collection findAndResolveByForcedIdWithNoTypeInPartitionNull( String theResourceType, Collection theForcedIds, boolean theExcludeDeleted) { - String query = "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted " - + "FROM ResourceTable t " - + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND t.myPartitionIdValue IS NULL"; + // we fetch myPartitionIdValue and myPartitionDateValue for resultSet processing consistency + String query = + "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue " + + "FROM ResourceTable t " + + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND t.myPartitionIdValue IS NULL"; if (theExcludeDeleted) { query += " AND t.myDeleted IS NULL"; @@ -132,9 +136,10 @@ public class IResourceTableDaoImpl implements IForcedIdQueries { Collection theForcedIds, List thePartitionIdsWithoutDefault, boolean theExcludeDeleted) { - String query = "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted " - + "FROM ResourceTable t " - + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND (t.myPartitionIdValue IS NULL OR t.myPartitionIdValue IN ( :partition_id ))"; + String query = + "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted, t.myPartitionIdValue, t.myPartitionDateValue " + + "FROM ResourceTable t " + + "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND (t.myPartitionIdValue IS NULL OR t.myPartitionIdValue IN ( :partition_id ))"; if (theExcludeDeleted) { query += " AND t.myDeleted IS NULL"; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index ba16be6115f..9e81e87f598 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -30,6 +30,7 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.cross.JpaResourceLookup; import ca.uhn.fhir.jpa.model.dao.JpaPid; +import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.search.builder.SearchBuilder; import ca.uhn.fhir.jpa.util.MemoryCacheService; @@ -59,12 +60,11 @@ import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.IdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionSynchronizationManager; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -100,7 +100,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; */ @Service public class IdHelperService implements IIdHelperService { - private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class); public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0]; public static final String RESOURCE_PID = "RESOURCE_PID"; @@ -523,7 +522,7 @@ public class IdHelperService implements IIdHelperService { if (myStorageSettings.getResourceClientIdStrategy() != JpaStorageSettings.ClientIdStrategyEnum.ANY) { List pids = theId.stream() .filter(t -> isValidPid(t)) - .map(t -> t.getIdPartAsLong()) + .map(IIdType::getIdPartAsLong) .collect(Collectors.toList()); if (!pids.isEmpty()) { resolvePids(requestPartitionId, pids, retVal); @@ -578,8 +577,14 @@ public class IdHelperService implements IIdHelperService { Long resourcePid = (Long) next[1]; String forcedId = (String) next[2]; Date deletedAt = (Date) next[3]; + Integer partitionId = (Integer) next[4]; + LocalDate partitionDate = (LocalDate) next[5]; - JpaResourceLookup lookup = new JpaResourceLookup(resourceType, resourcePid, deletedAt); + JpaResourceLookup lookup = new JpaResourceLookup( + resourceType, + resourcePid, + deletedAt, + PartitionablePartitionId.with(partitionId, partitionDate)); retVal.computeIfAbsent(forcedId, id -> new ArrayList<>()).add(lookup); if (!myStorageSettings.isDeleteEnabled()) { @@ -638,7 +643,11 @@ public class IdHelperService implements IIdHelperService { } } lookup.stream() - .map(t -> new JpaResourceLookup((String) t[0], (Long) t[1], (Date) t[2])) + .map(t -> new JpaResourceLookup( + (String) t[0], + (Long) t[1], + (Date) t[2], + PartitionablePartitionId.with((Integer) t[3], (LocalDate) t[4]))) .forEach(t -> { String id = t.getPersistentId().toString(); if (!theTargets.containsKey(id)) { @@ -683,9 +692,8 @@ public class IdHelperService implements IIdHelperService { MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, nextResourcePid, Optional.empty()); } Map> convertRetVal = new HashMap<>(); - retVal.forEach((k, v) -> { - convertRetVal.put(JpaPid.fromId(k), v); - }); + retVal.forEach((k, v) -> convertRetVal.put(JpaPid.fromId(k), v)); + return new PersistentIdToForcedIdMap<>(convertRetVal); } @@ -716,7 +724,8 @@ public class IdHelperService implements IIdHelperService { } if (!myStorageSettings.isDeleteEnabled()) { - JpaResourceLookup lookup = new JpaResourceLookup(theResourceType, theJpaPid.getId(), theDeletedAt); + JpaResourceLookup lookup = new JpaResourceLookup( + theResourceType, theJpaPid.getId(), theDeletedAt, theJpaPid.getPartitionablePartitionId()); String nextKey = theJpaPid.toString(); myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP, nextKey, lookup); } @@ -744,8 +753,7 @@ public class IdHelperService implements IIdHelperService { @Nonnull public List getPidsOrThrowException( @Nonnull RequestPartitionId theRequestPartitionId, List theIds) { - List resourcePersistentIds = resolveResourcePersistentIdsWithCache(theRequestPartitionId, theIds); - return resourcePersistentIds; + return resolveResourcePersistentIdsWithCache(theRequestPartitionId, theIds); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index e2d37341ee9..99dfc09021b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -471,10 +471,26 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { } } - version.onTable(Search.HFJ_SEARCH) - .modifyColumn("20240722.1", Search.SEARCH_UUID) - .nonNullable() - .withType(ColumnTypeEnum.STRING, 48); + { + // Add target resource partition id/date columns to resource link + Builder.BuilderWithTableName resourceLinkTable = version.onTable("HFJ_RES_LINK"); + + resourceLinkTable + .addColumn("20240718.10", "TARGET_RES_PARTITION_ID") + .nullable() + .type(ColumnTypeEnum.INT); + resourceLinkTable + .addColumn("20240718.20", "TARGET_RES_PARTITION_DATE") + .nullable() + .type(ColumnTypeEnum.DATE_ONLY); + } + + { + version.onTable(Search.HFJ_SEARCH) + .modifyColumn("20240722.1", Search.SEARCH_UUID) + .nonNullable() + .withType(ColumnTypeEnum.STRING, 48); + } { final Builder.BuilderWithTableName hfjResource = version.onTable("HFJ_RESOURCE"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/cross/JpaResourceLookup.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/cross/JpaResourceLookup.java index 65393d22be5..891617aa007 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/cross/JpaResourceLookup.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/cross/JpaResourceLookup.java @@ -20,18 +20,26 @@ package ca.uhn.fhir.jpa.model.cross; import ca.uhn.fhir.jpa.model.dao.JpaPid; +import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; import java.util.Date; public class JpaResourceLookup implements IResourceLookup { + private final String myResourceType; private final Long myResourcePid; private final Date myDeletedAt; + private final PartitionablePartitionId myPartitionablePartitionId; - public JpaResourceLookup(String theResourceType, Long theResourcePid, Date theDeletedAt) { + public JpaResourceLookup( + String theResourceType, + Long theResourcePid, + Date theDeletedAt, + PartitionablePartitionId thePartitionablePartitionId) { myResourceType = theResourceType; myResourcePid = theResourcePid; myDeletedAt = theDeletedAt; + myPartitionablePartitionId = thePartitionablePartitionId; } @Override @@ -46,6 +54,9 @@ public class JpaResourceLookup implements IResourceLookup { @Override public JpaPid getPersistentId() { - return JpaPid.fromId(myResourcePid); + JpaPid jpaPid = JpaPid.fromId(myResourcePid); + jpaPid.setPartitionablePartitionId(myPartitionablePartitionId); + + return jpaPid; } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/dao/JpaPid.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/dao/JpaPid.java index 2954dda4ab9..c2f21c82894 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/dao/JpaPid.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/dao/JpaPid.java @@ -19,6 +19,7 @@ */ package ca.uhn.fhir.jpa.model.dao; +import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; import ca.uhn.fhir.rest.api.server.storage.BaseResourcePersistentId; import java.util.ArrayList; @@ -34,6 +35,7 @@ import java.util.Set; */ public class JpaPid extends BaseResourcePersistentId { private final Long myId; + private PartitionablePartitionId myPartitionablePartitionId; private JpaPid(Long theId) { super(null); @@ -55,6 +57,15 @@ public class JpaPid extends BaseResourcePersistentId { myId = theId; } + public PartitionablePartitionId getPartitionablePartitionId() { + return myPartitionablePartitionId; + } + + public JpaPid setPartitionablePartitionId(PartitionablePartitionId thePartitionablePartitionId) { + myPartitionablePartitionId = thePartitionablePartitionId; + return this; + } + public static List toLongList(Collection thePids) { List retVal = new ArrayList<>(thePids.size()); for (JpaPid next : thePids) { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java index 533650e3e5b..8a2c2393b68 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java @@ -25,6 +25,7 @@ import jakarta.persistence.Embedded; import jakarta.persistence.MappedSuperclass; import java.io.Serializable; +import java.time.LocalDate; /** * This is the base class for entities with partitioning that does NOT include Hibernate Envers logging. @@ -44,6 +45,13 @@ public abstract class BasePartitionable implements Serializable { @Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true) private Integer myPartitionIdValue; + /** + * This is here to support queries only, do not set this field directly + */ + @SuppressWarnings("unused") + @Column(name = PartitionablePartitionId.PARTITION_DATE, insertable = false, updatable = false, nullable = true) + private LocalDate myPartitionDateValue; + @Nullable public PartitionablePartitionId getPartitionId() { return myPartitionId; @@ -57,6 +65,7 @@ public abstract class BasePartitionable implements Serializable { public String toString() { return "BasePartitionable{" + "myPartitionId=" + myPartitionId + ", myPartitionIdValue=" - + myPartitionIdValue + '}'; + + myPartitionIdValue + ", myPartitionDateValue=" + + myPartitionDateValue + '}'; } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java index 01001d7e0fe..90609d9acaf 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java @@ -34,6 +34,7 @@ import java.time.LocalDate; public class PartitionablePartitionId implements Cloneable { static final String PARTITION_ID = "PARTITION_ID"; + static final String PARTITION_DATE = "PARTITION_DATE"; @Column(name = PARTITION_ID, nullable = true, insertable = true, updatable = false) private Integer myPartitionId; @@ -132,4 +133,9 @@ public class PartitionablePartitionId implements Cloneable { } return new PartitionablePartitionId(partitionId, theRequestPartitionId.getPartitionDate()); } + + public static PartitionablePartitionId with( + @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { + return new PartitionablePartitionId(thePartitionId, thePartitionDate); + } } 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 7c47c07025d..545d5bd7603 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 @@ -19,8 +19,9 @@ */ package ca.uhn.fhir.jpa.model.entity; -import jakarta.annotation.Nullable; +import jakarta.persistence.AttributeOverride; import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; @@ -119,6 +120,11 @@ public class ResourceLink extends BaseResourceIndex { @Transient private transient String myTargetResourceId; + @Embedded + @AttributeOverride(name = "myPartitionId", column = @Column(name = "TARGET_RES_PARTITION_ID")) + @AttributeOverride(name = "myPartitionDate", column = @Column(name = "TARGET_RES_PARTITION_DATE")) + private PartitionablePartitionId myTargetResourcePartitionId; + /** * Constructor */ @@ -188,6 +194,7 @@ public class ResourceLink extends BaseResourceIndex { myTargetResourceType = source.getTargetResourceType(); myTargetResourceVersion = source.getTargetResourceVersion(); myTargetResourceUrl = source.getTargetResourceUrl(); + myTargetResourcePartitionId = source.getTargetResourcePartitionId(); } public String getSourcePath() { @@ -270,6 +277,15 @@ public class ResourceLink extends BaseResourceIndex { myId = theId; } + public PartitionablePartitionId getTargetResourcePartitionId() { + return myTargetResourcePartitionId; + } + + public ResourceLink setTargetResourcePartitionId(PartitionablePartitionId theTargetResourcePartitionId) { + myTargetResourcePartitionId = theTargetResourcePartitionId; + return this; + } + @Override public void clearHashes() { // nothing right now @@ -363,23 +379,113 @@ public class ResourceLink extends BaseResourceIndex { return retVal; } - /** - * @param theTargetResourceVersion This should only be populated if the reference actually had a version - */ public static ResourceLink forLocalReference( - String theSourcePath, - ResourceTable theSourceResource, - String theTargetResourceType, - Long theTargetResourcePid, - String theTargetResourceId, - Date theUpdated, - @Nullable Long theTargetResourceVersion) { + ResourceLinkForLocalReferenceParams theResourceLinkForLocalReferenceParams) { + ResourceLink retVal = new ResourceLink(); - retVal.setSourcePath(theSourcePath); - retVal.setSourceResource(theSourceResource); - retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId); - retVal.setTargetResourceVersion(theTargetResourceVersion); - retVal.setUpdated(theUpdated); + retVal.setSourcePath(theResourceLinkForLocalReferenceParams.getSourcePath()); + retVal.setSourceResource(theResourceLinkForLocalReferenceParams.getSourceResource()); + retVal.setTargetResource( + theResourceLinkForLocalReferenceParams.getTargetResourceType(), + theResourceLinkForLocalReferenceParams.getTargetResourcePid(), + theResourceLinkForLocalReferenceParams.getTargetResourceId()); + + retVal.setTargetResourcePartitionId( + theResourceLinkForLocalReferenceParams.getTargetResourcePartitionablePartitionId()); + retVal.setTargetResourceVersion(theResourceLinkForLocalReferenceParams.getTargetResourceVersion()); + retVal.setUpdated(theResourceLinkForLocalReferenceParams.getUpdated()); + return retVal; } + + public static class ResourceLinkForLocalReferenceParams { + private String mySourcePath; + private ResourceTable mySourceResource; + private String myTargetResourceType; + private Long myTargetResourcePid; + private String myTargetResourceId; + private Date myUpdated; + private Long myTargetResourceVersion; + private PartitionablePartitionId myTargetResourcePartitionablePartitionId; + + public static ResourceLinkForLocalReferenceParams instance() { + return new ResourceLinkForLocalReferenceParams(); + } + + public String getSourcePath() { + return mySourcePath; + } + + public ResourceLinkForLocalReferenceParams setSourcePath(String theSourcePath) { + mySourcePath = theSourcePath; + return this; + } + + public ResourceTable getSourceResource() { + return mySourceResource; + } + + public ResourceLinkForLocalReferenceParams setSourceResource(ResourceTable theSourceResource) { + mySourceResource = theSourceResource; + return this; + } + + public String getTargetResourceType() { + return myTargetResourceType; + } + + public ResourceLinkForLocalReferenceParams setTargetResourceType(String theTargetResourceType) { + myTargetResourceType = theTargetResourceType; + return this; + } + + public Long getTargetResourcePid() { + return myTargetResourcePid; + } + + public ResourceLinkForLocalReferenceParams setTargetResourcePid(Long theTargetResourcePid) { + myTargetResourcePid = theTargetResourcePid; + return this; + } + + public String getTargetResourceId() { + return myTargetResourceId; + } + + public ResourceLinkForLocalReferenceParams setTargetResourceId(String theTargetResourceId) { + myTargetResourceId = theTargetResourceId; + return this; + } + + public Date getUpdated() { + return myUpdated; + } + + public ResourceLinkForLocalReferenceParams setUpdated(Date theUpdated) { + myUpdated = theUpdated; + return this; + } + + public Long getTargetResourceVersion() { + return myTargetResourceVersion; + } + + /** + * @param theTargetResourceVersion This should only be populated if the reference actually had a version + */ + public ResourceLinkForLocalReferenceParams setTargetResourceVersion(Long theTargetResourceVersion) { + myTargetResourceVersion = theTargetResourceVersion; + return this; + } + + public PartitionablePartitionId getTargetResourcePartitionablePartitionId() { + return myTargetResourcePartitionablePartitionId; + } + + public ResourceLinkForLocalReferenceParams setTargetResourcePartitionablePartitionId( + PartitionablePartitionId theTargetResourcePartitionablePartitionId) { + myTargetResourcePartitionablePartitionId = theTargetResourcePartitionablePartitionId; + return this; + } + } } 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 51c2d79fea9..354996c66f1 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 @@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceLink.ResourceLinkForLocalReferenceParams; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; import ca.uhn.fhir.jpa.model.entity.StorageSettings; @@ -71,6 +72,8 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import static ca.uhn.fhir.jpa.model.config.PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED; +import static ca.uhn.fhir.jpa.model.entity.ResourceLink.forLocalReference; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -105,26 +108,6 @@ public class SearchParamExtractorService { mySearchParamExtractor = theSearchParamExtractor; } - public void extractFromResource( - RequestPartitionId theRequestPartitionId, - RequestDetails theRequestDetails, - ResourceIndexedSearchParams theParams, - ResourceTable theEntity, - IBaseResource theResource, - TransactionDetails theTransactionDetails, - boolean theFailOnInvalidReference) { - extractFromResource( - theRequestPartitionId, - theRequestDetails, - theParams, - ResourceIndexedSearchParams.withSets(), - theEntity, - theResource, - theTransactionDetails, - theFailOnInvalidReference, - ISearchParamExtractor.ALL_PARAMS); - } - /** * This method is responsible for scanning a resource for all of the search parameter instances. * I.e. for all search parameters defined for @@ -702,14 +685,18 @@ public class SearchParamExtractorService { * need to resolve it again */ myResourceLinkResolver.validateTypeOrThrowException(type); - resourceLink = ResourceLink.forLocalReference( - thePathAndRef.getPath(), - theEntity, - typeString, - resolvedTargetId.getId(), - targetId, - transactionDate, - targetVersionId); + + ResourceLinkForLocalReferenceParams params = ResourceLinkForLocalReferenceParams.instance() + .setSourcePath(thePathAndRef.getPath()) + .setSourceResource(theEntity) + .setTargetResourceType(typeString) + .setTargetResourcePid(resolvedTargetId.getId()) + .setTargetResourceId(targetId) + .setUpdated(transactionDate) + .setTargetResourceVersion(targetVersionId) + .setTargetResourcePartitionablePartitionId(resolvedTargetId.getPartitionablePartitionId()); + + resourceLink = forLocalReference(params); } else if (theFailOnInvalidReference) { @@ -748,6 +735,7 @@ public class SearchParamExtractorService { } else { // Cache the outcome in the current transaction in case there are more references JpaPid persistentId = JpaPid.fromId(resourceLink.getTargetResourcePid()); + persistentId.setPartitionablePartitionId(resourceLink.getTargetResourcePartitionId()); theTransactionDetails.addResolvedResourceId(referenceElement, persistentId); } @@ -757,11 +745,15 @@ public class SearchParamExtractorService { * Just assume the reference is valid. This is used for in-memory matching since there * is no expectation of a database in this situation */ - ResourceTable target; - target = new ResourceTable(); - target.setResourceType(typeString); - resourceLink = ResourceLink.forLocalReference( - thePathAndRef.getPath(), theEntity, typeString, null, targetId, transactionDate, targetVersionId); + ResourceLinkForLocalReferenceParams params = ResourceLinkForLocalReferenceParams.instance() + .setSourcePath(thePathAndRef.getPath()) + .setSourceResource(theEntity) + .setTargetResourceType(typeString) + .setTargetResourceId(targetId) + .setUpdated(transactionDate) + .setTargetResourceVersion(targetVersionId); + + resourceLink = forLocalReference(params); } theNewParams.myLinks.add(resourceLink); @@ -912,19 +904,24 @@ public class SearchParamExtractorService { RequestDetails theRequest, TransactionDetails theTransactionDetails) { JpaPid resolvedResourceId = (JpaPid) theTransactionDetails.getResolvedResourceId(theNextId); + if (resolvedResourceId != null) { String targetResourceType = theNextId.getResourceType(); Long targetResourcePid = resolvedResourceId.getId(); String targetResourceIdPart = theNextId.getIdPart(); Long targetVersion = theNextId.getVersionIdPartAsLong(); - return ResourceLink.forLocalReference( - thePathAndRef.getPath(), - theEntity, - targetResourceType, - targetResourcePid, - targetResourceIdPart, - theUpdateTime, - targetVersion); + + ResourceLinkForLocalReferenceParams params = ResourceLinkForLocalReferenceParams.instance() + .setSourcePath(thePathAndRef.getPath()) + .setSourceResource(theEntity) + .setTargetResourceType(targetResourceType) + .setTargetResourcePid(targetResourcePid) + .setTargetResourceId(targetResourceIdPart) + .setUpdated(theUpdateTime) + .setTargetResourceVersion(targetVersion) + .setTargetResourcePartitionablePartitionId(resolvedResourceId.getPartitionablePartitionId()); + + return ResourceLink.forLocalReference(params); } /* @@ -936,8 +933,7 @@ public class SearchParamExtractorService { IResourceLookup targetResource; if (myPartitionSettings.isPartitioningEnabled()) { - if (myPartitionSettings.getAllowReferencesAcrossPartitions() - == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) { + if (myPartitionSettings.getAllowReferencesAcrossPartitions() == ALLOWED_UNQUALIFIED) { // Interceptor: Pointcut.JPA_CROSS_PARTITION_REFERENCE_DETECTED if (CompositeInterceptorBroadcaster.hasHooks( @@ -981,21 +977,25 @@ public class SearchParamExtractorService { Long targetResourcePid = targetResource.getPersistentId().getId(); String targetResourceIdPart = theNextId.getIdPart(); Long targetVersion = theNextId.getVersionIdPartAsLong(); - return ResourceLink.forLocalReference( - thePathAndRef.getPath(), - theEntity, - targetResourceType, - targetResourcePid, - targetResourceIdPart, - theUpdateTime, - targetVersion); + + ResourceLinkForLocalReferenceParams params = ResourceLinkForLocalReferenceParams.instance() + .setSourcePath(thePathAndRef.getPath()) + .setSourceResource(theEntity) + .setTargetResourceType(targetResourceType) + .setTargetResourcePid(targetResourcePid) + .setTargetResourceId(targetResourceIdPart) + .setUpdated(theUpdateTime) + .setTargetResourceVersion(targetVersion) + .setTargetResourcePartitionablePartitionId( + targetResource.getPersistentId().getPartitionablePartitionId()); + + return forLocalReference(params); } private RequestPartitionId determineResolverPartitionId(@Nonnull RequestPartitionId theRequestPartitionId) { RequestPartitionId targetRequestPartitionId = theRequestPartitionId; if (myPartitionSettings.isPartitioningEnabled() - && myPartitionSettings.getAllowReferencesAcrossPartitions() - == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) { + && myPartitionSettings.getAllowReferencesAcrossPartitions() == ALLOWED_UNQUALIFIED) { targetRequestPartitionId = RequestPartitionId.allPartitions(); } return targetRequestPartitionId; diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java index a1ad93d1f2d..f1f61388f5e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java @@ -37,7 +37,7 @@ public class ResourceIndexedSearchParamsTest { @Test public void matchResourceLinksStringCompareToLong() { - ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date(), null); + ResourceLink link = getResourceLinkForLocalReference(LONG_ID); myParams.getResourceLinks().add(link); ReferenceParam referenceParam = getReferenceParam(STRING_ID); @@ -47,7 +47,7 @@ public class ResourceIndexedSearchParamsTest { @Test public void matchResourceLinksStringCompareToString() { - ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date(), null); + ResourceLink link = getResourceLinkForLocalReference(STRING_ID); myParams.getResourceLinks().add(link); ReferenceParam referenceParam = getReferenceParam(STRING_ID); @@ -57,7 +57,7 @@ public class ResourceIndexedSearchParamsTest { @Test public void matchResourceLinksLongCompareToString() { - ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date(), null); + ResourceLink link = getResourceLinkForLocalReference(STRING_ID); myParams.getResourceLinks().add(link); ReferenceParam referenceParam = getReferenceParam(LONG_ID); @@ -67,7 +67,7 @@ public class ResourceIndexedSearchParamsTest { @Test public void matchResourceLinksLongCompareToLong() { - ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date(), null); + ResourceLink link = getResourceLinkForLocalReference(LONG_ID); myParams.getResourceLinks().add(link); ReferenceParam referenceParam = getReferenceParam(LONG_ID); @@ -75,6 +75,21 @@ public class ResourceIndexedSearchParamsTest { assertTrue(result); } + private ResourceLink getResourceLinkForLocalReference(String theTargetResourceId){ + + ResourceLink.ResourceLinkForLocalReferenceParams params = ResourceLink.ResourceLinkForLocalReferenceParams + .instance() + .setSourcePath("organization") + .setSourceResource(mySource) + .setTargetResourceType("Organization") + .setTargetResourcePid(123L) + .setTargetResourceId(theTargetResourceId) + .setUpdated(new Date()); + + return ResourceLink.forLocalReference(params); + + } + private ReferenceParam getReferenceParam(String theId) { ReferenceParam retVal = new ReferenceParam(); retVal.setValue(theId); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java index c2d763d091d..fa0870d4e67 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.jpa.dao.index; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; @@ -29,6 +27,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.when; @@ -87,13 +87,17 @@ public class IdHelperServiceTest { "Patient", 123l, "RED", - new Date() + new Date(), + null, + null }; Object[] blueView = new Object[] { "Patient", 456l, "BLUE", - new Date() + new Date(), + null, + null }; // when @@ -155,11 +159,13 @@ public class IdHelperServiceTest { String resourceType = "Patient"; String resourceForcedId = "AAA"; - Object[] forcedIdView = new Object[4]; + Object[] forcedIdView = new Object[6]; forcedIdView[0] = resourceType; forcedIdView[1] = 1L; forcedIdView[2] = resourceForcedId; forcedIdView[3] = null; + forcedIdView[4] = null; + forcedIdView[5] = null; Collection testForcedIdViews = new ArrayList<>(); testForcedIdViews.add(forcedIdView); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java index 93e503049e9..a657e542f67 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java @@ -90,13 +90,11 @@ import java.util.stream.Collectors; import static ca.uhn.fhir.util.TestUtil.sleepAtLeast; import static org.apache.commons.lang3.StringUtils.countMatches; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -173,7 +171,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { // Look up the referenced subject/patient String sql = selectQueries.get(0).getSql(true, false).toLowerCase(); assertThat(sql).contains(" from hfj_resource "); - assertEquals(0, StringUtils.countMatches(selectQueries.get(0).getSql(true, false).toLowerCase(), "partition")); + assertEquals(2, StringUtils.countMatches(selectQueries.get(0).getSql(true, false).toLowerCase(), "partition")); runInTransaction(() -> { List resLinks = myResourceLinkDao.findAll(); @@ -181,6 +179,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertEquals(2, resLinks.size()); assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid()); + assertEquals(myPartitionId, resLinks.get(0).getTargetResourcePartitionId().getPartitionId()); + assertLocalDateFromDbMatches(myPartitionDate, resLinks.get(0).getTargetResourcePartitionId().getPartitionDate()); }); } @@ -465,6 +465,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertEquals(1, resourceLinks.size()); assertEquals(myPartitionId, resourceLinks.get(0).getPartitionId().getPartitionId().intValue()); assertLocalDateFromDbMatches(myPartitionDate, resourceLinks.get(0).getPartitionId().getPartitionDate()); + assertEquals(myPartitionId, resourceLinks.get(0).getTargetResourcePartitionId().getPartitionId().intValue()); + assertLocalDateFromDbMatches(myPartitionDate, resourceLinks.get(0).getTargetResourcePartitionId().getPartitionDate()); // HFJ_RES_PARAM_PRESENT List presents = mySearchParamPresentDao.findAllForResource(resourceTable); @@ -1375,7 +1377,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { // Only the read columns should be used, no criteria use partition assertThat(searchSql).as(searchSql).contains("PARTITION_ID IN ('1')"); - assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1); + assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID,")).as(searchSql).isEqualTo(1); + assertThat(StringUtils.countMatches(searchSql, "PARTITION_DATE")).as(searchSql).isEqualTo(1); } // Read in null Partition @@ -1428,7 +1431,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false).toUpperCase(); ourLog.info("Search SQL:\n{}", searchSql); assertThat(searchSql).as(searchSql).contains("PARTITION_ID IN ('1')"); - assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1); + assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID,")).as(searchSql).isEqualTo(1); + assertThat(StringUtils.countMatches(searchSql, "PARTITION_DATE")).as(searchSql).isEqualTo(1); // Second SQL performs the search searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase(); @@ -2086,9 +2090,11 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { myCaptureQueriesListener.logSelectQueriesForCurrentThread(1); assertThat(outcome.getResources(0, 1)).hasSize(1); - String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); ourLog.info("Search SQL:\n{}", searchSql); - assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1); + assertThat(searchSql).as(searchSql).contains("PARTITION_ID in ('1')"); + assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID,")).as(searchSql).isEqualTo(1); + assertThat(StringUtils.countMatches(searchSql, "PARTITION_DATE")).as(searchSql).isEqualTo(1); } 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 index 68fb51a390e..7ca089ac3a0 100644 --- 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 @@ -1,6 +1,5 @@ package ca.uhn.fhir.jpa.search.reindex; -import static org.junit.jupiter.api.Assertions.assertEquals; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings; @@ -17,22 +16,22 @@ 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 org.htmlunit.html.HtmlPage; -import org.htmlunit.html.HtmlTable; +import jakarta.annotation.Nonnull; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; +import org.htmlunit.html.HtmlPage; +import org.htmlunit.html.HtmlTable; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jakarta.annotation.Nonnull; import java.io.IOException; import java.math.BigDecimal; import java.util.Collections; import java.util.Date; -import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests the narrative generation in {@link InstanceReindexServiceImpl}. This is a separate test @@ -135,7 +134,7 @@ public class InstanceReindexServiceImplNarrativeR5Test { public void testIndexResourceLink() throws IOException { // Setup ResourceIndexedSearchParams newParams = newParams(); - newParams.myLinks.add(ResourceLink.forLocalReference("Observation.subject", myEntity, "Patient", 123L, "123", new Date(), 555L)); + newParams.myLinks.add(getResourceLinkForLocalReference()); // Test Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true, Collections.emptyList()); @@ -311,4 +310,19 @@ public class InstanceReindexServiceImplNarrativeR5Test { return ResourceIndexedSearchParams.withSets(); } + private ResourceLink getResourceLinkForLocalReference(){ + + ResourceLink.ResourceLinkForLocalReferenceParams params = ResourceLink.ResourceLinkForLocalReferenceParams + .instance() + .setSourcePath("Observation.subject") + .setSourceResource(myEntity) + .setTargetResourceType("Patient") + .setTargetResourcePid(123L) + .setTargetResourceId("123") + .setUpdated(new Date()) + .setTargetResourceVersion(555L); + + return ResourceLink.forLocalReference(params); + } + }