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 <etienne.poirier@smilecdr.com>
This commit is contained in:
Etienne Poirier 2024-08-01 09:26:10 -04:00 committed by GitHub
parent d32433e68b
commit 8384325d78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 345 additions and 126 deletions

View File

@ -0,0 +1,5 @@
---
type: add
issue: 6148
jira: SMILE-8613
title: "Added the target resource partitionId and partitionDate to the resourceLink table."

View File

@ -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<Object[]> findLookupFieldsByResourcePid(@Param("pid") List<Long> 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<Object[]> findLookupFieldsByResourcePidInPartitionIds(
@Param("pid") List<Long> thePids, @Param("partition_id") Collection<Integer> 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<Object[]> findLookupFieldsByResourcePidInPartitionIdsOrNullPartition(
@Param("pid") List<Long> thePids, @Param("partition_id") Collection<Integer> 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<Object[]> findLookupFieldsByResourcePidInPartitionNull(@Param("pid") List<Long> thePids);
@Query("SELECT t.myVersion FROM ResourceTable t WHERE t.myId = :pid")

View File

@ -56,9 +56,10 @@ public class IResourceTableDaoImpl implements IForcedIdQueries {
@Override
public Collection<Object[]> findAndResolveByForcedIdWithNoType(
String theResourceType, Collection<String> 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<String> theForcedIds,
Collection<Integer> 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<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(
String theResourceType, Collection<String> 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<String> theForcedIds,
List<Integer> 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";

View File

@ -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<JpaPid> {
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<JpaPid> {
if (myStorageSettings.getResourceClientIdStrategy() != JpaStorageSettings.ClientIdStrategyEnum.ANY) {
List<Long> 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<JpaPid> {
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<JpaPid> {
}
}
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<JpaPid> {
MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, nextResourcePid, Optional.empty());
}
Map<JpaPid, Optional<String>> 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<JpaPid> {
}
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<JpaPid> {
@Nonnull
public List<JpaPid> getPidsOrThrowException(
@Nonnull RequestPartitionId theRequestPartitionId, List<IIdType> theIds) {
List<JpaPid> resourcePersistentIds = resolveResourcePersistentIdsWithCache(theRequestPartitionId, theIds);
return resourcePersistentIds;
return resolveResourcePersistentIdsWithCache(theRequestPartitionId, theIds);
}
@Override

View File

@ -471,10 +471,26 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
}
}
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");

View File

@ -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<JpaPid> {
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<JpaPid> {
@Override
public JpaPid getPersistentId() {
return JpaPid.fromId(myResourcePid);
JpaPid jpaPid = JpaPid.fromId(myResourcePid);
jpaPid.setPartitionablePartitionId(myPartitionablePartitionId);
return jpaPid;
}
}

View File

@ -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<Long> {
private final Long myId;
private PartitionablePartitionId myPartitionablePartitionId;
private JpaPid(Long theId) {
super(null);
@ -55,6 +57,15 @@ public class JpaPid extends BaseResourcePersistentId<Long> {
myId = theId;
}
public PartitionablePartitionId getPartitionablePartitionId() {
return myPartitionablePartitionId;
}
public JpaPid setPartitionablePartitionId(PartitionablePartitionId thePartitionablePartitionId) {
myPartitionablePartitionId = thePartitionablePartitionId;
return this;
}
public static List<Long> toLongList(Collection<JpaPid> thePids) {
List<Long> retVal = new ArrayList<>(thePids.size());
for (JpaPid next : thePids) {

View File

@ -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 + '}';
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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<JpaPid> 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;

View File

@ -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);

View File

@ -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<Object[]> testForcedIdViews = new ArrayList<>();
testForcedIdViews.add(forcedIdView);

View File

@ -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<ResourceLink> 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<SearchParamPresentEntity> 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);
}

View File

@ -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);
}
}