Allow reading from multiple partitions (#2198)

* Partitioning rework

* Work on partition improvements

* Partition updates

* Work on partitiong

* Test fixes

* Add docs

* Add changelog

* Resolve FIXME

* Test fixes

* Test fixes

* Test fixes

* Compile fix

* Fix compile error

* Test fix

* Test fixes
This commit is contained in:
James Agnew 2020-11-30 17:59:52 -05:00 committed by GitHub
parent 283834ed1d
commit c309737166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1076 additions and 441 deletions

View File

@ -470,9 +470,6 @@ public class FhirContext {
/**
* Returns the name of a given resource class.
*
* @param theResourceType
* @return
*/
public String getResourceType(final Class<? extends IBaseResource> theResourceType) {
return getResourceDefinition(theResourceType).getName();
@ -603,7 +600,7 @@ public class FhirContext {
/**
* Set the restful client factory
*
* @param theRestfulClientFactory
* @param theRestfulClientFactory The new client factory (must not be null)
*/
public void setRestfulClientFactory(final IRestfulClientFactory theRestfulClientFactory) {
Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null");

View File

@ -20,12 +20,23 @@ package ca.uhn.fhir.interceptor.model;
* #L%
*/
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;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
/**
* @since 5.0.0
@ -35,15 +46,25 @@ public class RequestPartitionId {
private static final RequestPartitionId ALL_PARTITIONS = new RequestPartitionId();
private final LocalDate myPartitionDate;
private final boolean myAllPartitions;
private final Integer myPartitionId;
private final String myPartitionName;
private final List<Integer> myPartitionIds;
private final List<String> myPartitionNames;
/**
* Constructor for a single partition
*/
private RequestPartitionId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
myPartitionId = thePartitionId;
myPartitionName = thePartitionName;
myPartitionIds = toListOrNull(thePartitionId);
myPartitionNames = toListOrNull(thePartitionName);
myPartitionDate = thePartitionDate;
myAllPartitions = false;
}
/**
* Constructor for a multiple partition
*/
private RequestPartitionId(@Nullable List<String> thePartitionName, @Nullable List<Integer> thePartitionId, @Nullable LocalDate thePartitionDate) {
myPartitionIds = toListOrNull(thePartitionId);
myPartitionNames = toListOrNull(thePartitionName);
myPartitionDate = thePartitionDate;
myAllPartitions = false;
}
@ -54,8 +75,8 @@ public class RequestPartitionId {
private RequestPartitionId() {
super();
myPartitionDate = null;
myPartitionName = null;
myPartitionId = null;
myPartitionNames = null;
myPartitionIds = null;
myAllPartitions = true;
}
@ -69,28 +90,26 @@ public class RequestPartitionId {
}
@Nullable
public String getPartitionName() {
return myPartitionName;
public List<String> getPartitionNames() {
return myPartitionNames;
}
@Nullable
public Integer getPartitionId() {
return myPartitionId;
@Nonnull
public List<Integer> getPartitionIds() {
Validate.notNull(myPartitionIds, "Partition IDs have not been set");
return myPartitionIds;
}
@Override
public String toString() {
return "RequestPartitionId[id=" + getPartitionId() + ", name=" + getPartitionName() + "]";
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
if (hasPartitionIds()) {
b.append("ids", getPartitionIds());
}
/**
* Returns the partition ID (numeric) as a string, or the string "null"
*/
public String getPartitionIdStringOrNullString() {
if (myPartitionId == null) {
return "null";
if (hasPartitionNames()) {
b.append("names", getPartitionNames());
}
return myPartitionId.toString();
return b.build();
}
@Override
@ -108,8 +127,8 @@ public class RequestPartitionId {
return new EqualsBuilder()
.append(myAllPartitions, that.myAllPartitions)
.append(myPartitionDate, that.myPartitionDate)
.append(myPartitionId, that.myPartitionId)
.append(myPartitionName, that.myPartitionName)
.append(myPartitionIds, that.myPartitionIds)
.append(myPartitionNames, that.myPartitionNames)
.isEquals();
}
@ -118,11 +137,82 @@ public class RequestPartitionId {
return new HashCodeBuilder(17, 37)
.append(myPartitionDate)
.append(myAllPartitions)
.append(myPartitionId)
.append(myPartitionName)
.append(myPartitionIds)
.append(myPartitionNames)
.toHashCode();
}
@Nullable
public Integer getFirstPartitionIdOrNull() {
if (myPartitionIds != null) {
return myPartitionIds.get(0);
}
return null;
}
public String getFirstPartitionNameOrNull() {
if (myPartitionNames != null) {
return myPartitionNames.get(0);
}
return null;
}
/**
* Returns true if this request partition contains only one partition ID and it is the DEFAULT partition ID (null)
*/
public boolean isDefaultPartition() {
return getPartitionIds().size() == 1 && getPartitionIds().get(0) == null;
}
public boolean hasPartitionId(Integer thePartitionId) {
Validate.notNull(myPartitionIds, "Partition IDs not set");
return myPartitionIds.contains(thePartitionId);
}
public boolean hasPartitionIds() {
return myPartitionIds != null;
}
public boolean hasPartitionNames() {
return myPartitionNames != null;
}
public boolean hasDefaultPartitionId() {
return getPartitionIds().contains(null);
}
public List<Integer> getPartitionIdsWithoutDefault() {
return getPartitionIds().stream().filter(t -> t != null).collect(Collectors.toList());
}
@Nullable
private static <T> List<T> toListOrNull(@Nullable Collection<T> theList) {
if (theList != null) {
if (theList.size() == 1) {
return Collections.singletonList(theList.iterator().next());
}
return Collections.unmodifiableList(new ArrayList<>(theList));
}
return null;
}
@Nullable
private static <T> List<T> toListOrNull(@Nullable T theObject) {
if (theObject != null) {
return Collections.singletonList(theObject);
}
return null;
}
@SafeVarargs
@Nullable
private static <T> List<T> toListOrNull(@Nullable T... theObject) {
if (theObject != null) {
return Arrays.asList(theObject);
}
return null;
}
@Nonnull
public static RequestPartitionId allPartitions() {
return ALL_PARTITIONS;
@ -130,17 +220,27 @@ public class RequestPartitionId {
@Nonnull
public static RequestPartitionId defaultPartition() {
return fromPartitionId(null);
return fromPartitionIds(Collections.singletonList(null));
}
@Nonnull
public static RequestPartitionId fromPartitionId(@Nullable Integer thePartitionId) {
return fromPartitionId(thePartitionId, null);
return fromPartitionIds(Collections.singletonList(thePartitionId));
}
@Nonnull
public static RequestPartitionId fromPartitionId(@Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
return new RequestPartitionId(null, thePartitionId, thePartitionDate);
return new RequestPartitionId(null, Collections.singletonList(thePartitionId), thePartitionDate);
}
@Nonnull
public static RequestPartitionId fromPartitionIds(@Nonnull Collection<Integer> thePartitionIds) {
return new RequestPartitionId(null, toListOrNull(thePartitionIds), null);
}
@Nonnull
public static RequestPartitionId fromPartitionIds(Integer... thePartitionIds) {
return new RequestPartitionId(null, toListOrNull(thePartitionIds), null);
}
@Nonnull
@ -153,6 +253,16 @@ public class RequestPartitionId {
return new RequestPartitionId(thePartitionName, null, thePartitionDate);
}
@Nonnull
public static RequestPartitionId fromPartitionNames(@Nullable List<String> thePartitionNames) {
return new RequestPartitionId(toListOrNull(thePartitionNames), null, null);
}
@Nonnull
public static RequestPartitionId fromPartitionNames(String... thePartitionNames) {
return new RequestPartitionId(toListOrNull(thePartitionNames), null, null);
}
@Nonnull
public static RequestPartitionId fromPartitionIdAndName(@Nullable Integer thePartitionId, @Nullable String thePartitionName) {
return new RequestPartitionId(thePartitionName, thePartitionId, null);
@ -163,13 +273,25 @@ public class RequestPartitionId {
return new RequestPartitionId(thePartitionName, thePartitionId, thePartitionDate);
}
@Nonnull
public static RequestPartitionId forPartitionIdsAndNames(List<String> thePartitionNames, List<Integer> thePartitionIds, LocalDate thePartitionDate) {
return new RequestPartitionId(thePartitionNames, thePartitionIds, thePartitionDate);
}
/**
* Create a string representation suitable for use as a cache key. Null aware.
* <p>
* Returns the partition IDs (numeric) as a joined string with a space between, using the string "null" for any null values
*/
public static String stringifyForKey(RequestPartitionId theRequestPartitionId) {
String retVal = "(null)";
if (theRequestPartitionId != null) {
retVal = theRequestPartitionId.getPartitionIdStringOrNullString();
public static String stringifyForKey(@Nonnull RequestPartitionId theRequestPartitionId) {
String retVal = "(all partitions)";
if (!theRequestPartitionId.isAllPartitions()) {
assert theRequestPartitionId.hasPartitionIds();
retVal = theRequestPartitionId
.getPartitionIds()
.stream()
.map(t -> defaultIfNull(t, "null").toString())
.collect(Collectors.joining(" "));
}
return retVal;
}

View File

@ -171,12 +171,10 @@ ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specif
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.noIdSupplied=No Partition ID supplied
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.missingPartitionIdOrName=Partition must have an ID and a Name
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantCreatePartition0=Can not create a partition with ID 0 (this is a reserved value)
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.unknownPartitionId=No partition exists with ID {0}
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.invalidName=Partition name "{0}" is not valid
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantCreateDuplicatePartitionName=Partition name "{0}" is already defined
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantDeleteDefaultPartition=Can not delete default partition
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantRenameDefaultPartition=Can not rename default partition
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantCreateDefaultPartition=Can not create partition with name "DEFAULT"
ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0}

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2198
title: "It is now possible for read operations (read/history/search/etc) in a partitioned server to read across more than one
partition if the partitioning interceptor indicates multiple partitions."

View File

@ -43,10 +43,23 @@ When a resource is **updated**, the partition ID and date from the previous vers
When a **read operation** is being performed (e.g. a read, search, history, etc.), a separate [interceptor hook](#partition-interceptors) is invoked in order to determine whether the operation should target a specific partition. The outcome of this hook determines how the partitioning manifests itself to the end user:
* The system can be configured to operate as a **multitenant** solution by configuring the partition interceptor to scope all read operations to read data only from the partition that request has access to.```
* The system can be configured to operate as a **multitenant** solution by configuring the partition interceptor to scope all read operations to read data only from the partition that request has access to.
* The system can be configured to operate with logical segments by configuring the partition interceptor to scope read operations to access all partitions.
# Partitioning and Resource IDs
In a partitioned repository, it is important to understand that only a single pool of resource IDs exists. In other words, only one resource with the ID `Patient/1` can exist across all partitions, and it must be in a single partition.
This fact can have security implications:
* A client might be blocked from creating `Patient/ABC` in the partition they have access to because this ID is already in use in another partition.
* In a server using the default configuration of SEQUENTIAL_NUMERIC [Server ID Strategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-api/ca/uhn/fhir/jpa/api/config/DaoConfig.html#setResourceServerIdStrategy(ca.uhn.fhir.jpa.api.config.DaoConfig.IdStrategyEnum)) a client may be able to infer the IDs of resources in other partitions based on the ID they were assigned.
These considerations can be addressed by using UUID Server ID Strategy, and disallowing client-assigned IDs.
# Partition Interceptors
In order to implement partitioning, an interceptor must be registered against the interceptor registry (either the REST Server registry, or the JPA Server registry will work).
@ -67,6 +80,9 @@ The criteria for determining the partition will depend on your use case. For exa
A hook against the [`Pointcut.STORAGE_PARTITION_IDENTIFY_READ`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_READ) pointcut must be registered, and this hook method will be invoked every time a resource is created in order to determine the partition to assign the resource to.
<cdr:fail cdr:afterVersion="5.4.0"/>
As of HAPI FHIR 5.3.0, the *Identify Partition for Read* hook method may return multiple partition names or IDs. If more than one partition is identified, the server will search in all identified partitions.
## Examples
See [Partition Interceptor Examples](./partition_interceptor_examples.html) for various samples of how partitioning interceptors can be set up.

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
import ca.uhn.fhir.jpa.model.entity.BaseTag;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
@ -962,7 +963,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// 7. Add partition information
if (myPartitionSettings.isPartitioningEnabled()) {
RequestPartitionId partitionId = theEntity.getPartitionId();
PartitionablePartitionId partitionId = theEntity.getPartitionId();
if (partitionId != null && partitionId.getPartitionId() != null) {
PartitionEntity persistedPartition = myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId());
retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId());

View File

@ -1102,21 +1102,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Verify that the resource is for the correct partition
if (!requestPartitionId.isAllPartitions()) {
if (requestPartitionId.getPartitionId() == null) {
if (entity.getPartitionId().getPartitionId() != null) {
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
entity = null;
}
} else if (entity.getPartitionId().getPartitionId() != null) {
if (!requestPartitionId.getPartitionId().equals(entity.getPartitionId().getPartitionId())) {
if (entity.getPartitionId() != null && entity.getPartitionId().getPartitionId() != null) {
if (!requestPartitionId.hasPartitionId(entity.getPartitionId().getPartitionId())) {
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
entity = null;
}
} else {
// Entity Partition ID is null
if (!requestPartitionId.hasPartitionId(null)) {
ourLog.debug("Performing a read for PartitionId=null but entity has partition: {}", entity.getPartitionId());
entity = null;
}
}
}
if (entity == null) {
throw new ResourceNotFoundException(theId);
@ -1145,6 +1143,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
Validate.notNull(entity);
validateResourceType(entity);
if (theCheckForForcedId) {

View File

@ -142,10 +142,15 @@ public class HistoryBuilder {
List<Predicate> predicates = new ArrayList<>();
if (!thePartitionId.isAllPartitions()) {
if (thePartitionId.getPartitionId() != null) {
predicates.add(theCriteriaBuilder.equal(theFrom.get("myPartitionIdValue").as(Integer.class), thePartitionId.getPartitionId()));
} else {
if (thePartitionId.isDefaultPartition()) {
predicates.add(theCriteriaBuilder.isNull(theFrom.get("myPartitionIdValue").as(Integer.class)));
} else if (thePartitionId.hasDefaultPartitionId()) {
predicates.add(theCriteriaBuilder.or(
theCriteriaBuilder.isNull(theFrom.get("myPartitionIdValue").as(Integer.class)),
theFrom.get("myPartitionIdValue").as(Integer.class).in(thePartitionId.getPartitionIdsWithoutDefault())
));
} else {
predicates.add(theFrom.get("myPartitionIdValue").as(Integer.class).in(thePartitionId.getPartitionIds()));
}
}

View File

@ -42,6 +42,7 @@ import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -953,7 +954,7 @@ public class LegacySearchBuilder implements ISearchBuilder {
From<?, ResourceIndexedCompositeStringUnique> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COMPOSITE_UNIQUE, null);
if (!theRequestPartitionId.isAllPartitions()) {
Integer partitionId = theRequestPartitionId.getPartitionId();
Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull();
Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId);
myQueryStack.addPredicate(predicate);
}

View File

@ -44,8 +44,11 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myPartitionId.myPartitionId IS NULL AND myResourceType = :resource_type AND myForcedId = :forced_id")
Optional<Long> findByPartitionIdNullAndTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myPartitionId.myPartitionId = :partition_id AND myResourceType = :resource_type AND myForcedId = :forced_id")
Optional<Long> findByPartitionIdAndTypeAndForcedId(@Param("partition_id") Integer thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myPartitionId.myPartitionId IN :partition_id AND myResourceType = :resource_type AND myForcedId = :forced_id")
Optional<Long> findByPartitionIdAndTypeAndForcedId(@Param("partition_id") Collection<Integer> thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE (myPartitionId.myPartitionId IN :partition_id OR myPartitionId.myPartitionId IS NULL) AND myResourceType = :resource_type AND myForcedId = :forced_id")
Optional<Long> findByPartitionIdOrNullAndTypeAndForcedId(@Param("partition_id") Collection<Integer> thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
Optional<ForcedId> findByResourcePid(@Param("resource_pid") Long theResourcePid);
@ -65,8 +68,15 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
* 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 f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myPartitionIdValue = :partition_id AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
Collection<Object[]> findByTypeAndForcedIdInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId, @Param("partition_id") Integer thePartitionId);
@Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myPartitionIdValue IN ( :partition_id ) AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
Collection<Object[]> findByTypeAndForcedIdInPartitionIds(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId, @Param("partition_id") Collection<Integer> thePartitionId);
/**
* 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 f.myForcedId, f.myResourcePid FROM ForcedId f WHERE (myPartitionIdValue IS NULL OR myPartitionIdValue IN ( :partition_id )) AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
Collection<Object[]> findByTypeAndForcedIdInPartitionIdsOrNullPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId, @Param("partition_id") Collection<Integer> thePartitionId);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
@ -110,8 +120,8 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue = :partition_id")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds, @Param("partition_id") Integer thePartitionId);
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IN :partition_id")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds, @Param("partition_id") Collection<Integer> thePartitionId);
/**
@ -127,4 +137,15 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
/**
* 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 " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND (f.myPartitionIdValue IS NULL OR f.myPartitionIdValue IN :partition_id)")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(@Param("resource_type") String theNextResourceType, @Param("forced_id") Collection<String> theNextIds, @Param("forced_id") List<Integer> thePartitionIdsWithoutDefault);
}

View File

@ -12,7 +12,6 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/*
* #%L
@ -65,12 +64,31 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("DELETE FROM ResourceTable t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
/**
* 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)")
Collection<Object[]> findLookupFieldsByResourcePid(@Param("pid") List<Long> thePids);
@Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue = :partition_id")
Collection<Object[]> findLookupFieldsByResourcePidInPartition(@Param("pid") List<Long> thePids, @Param("partition_id") Integer thePartitionId);
/**
* 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) AND t.myPartitionIdValue IN :partition_id")
Collection<Object[]> findLookupFieldsByResourcePidInPartitionIds(@Param("pid") List<Long> thePids, @Param("partition_id") Collection<Integer> thePartitionId);
/**
* 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) 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);
/**
* 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) AND t.myPartitionIdValue IS NULL")
Collection<Object[]> findLookupFieldsByResourcePidInPartitionNull(@Param("pid") List<Long> thePids);
}

View File

@ -40,11 +40,10 @@ public class EmpiLinkDeleteSvc {
/**
* Delete all EmpiLink records with any reference to this resource. (Used by Expunge.)
* @param theResource
* @return the number of records deleted
*/
public int deleteWithAnyReferenceTo(IBaseResource theResource) {
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement(), null);
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement());
int removed = myEmpiLinkDao.deleteWithAnyReferenceToPid(pid);
if (removed > 0) {
ourLog.info("Removed {} EMPI links with references to {}", removed, theResource.getIdElement().toVersionless());
@ -53,7 +52,7 @@ public class EmpiLinkDeleteSvc {
}
public int deleteNonRedirectWithWithAnyReferenceTo(IBaseResource theResource) {
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement(), null);
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement());
int removed = myEmpiLinkDao.deleteWithAnyReferenceToPidAndMatchResultNot(pid, EmpiMatchResultEnum.REDIRECT);
if (removed > 0) {
ourLog.info("Removed {} non-redirect EMPI links with references to {}", removed, theResource.getIdElement().toVersionless());

View File

@ -68,7 +68,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
IResourceLookup resolvedResource;
String idPart = theSourceResourceId.getIdPart();
try {
resolvedResource = myIdHelperService.resolveResourceIdentity(theRequestPartitionId, theResourceType, idPart, theRequest);
resolvedResource = myIdHelperService.resolveResourceIdentity(theRequestPartitionId, theResourceType, idPart);
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource);
} catch (ResourceNotFoundException e) {

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.dao.index;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
@ -33,7 +32,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.QueryChunker;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -44,10 +42,7 @@ import org.apache.commons.lang3.Validate;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
@ -62,7 +57,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -87,7 +81,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
@Service
public class IdHelperService {
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
private static final String RESOURCE_PID = "RESOURCE_PID";
@Autowired
@ -97,8 +90,6 @@ public class IdHelperService {
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private FhirContext myFhirCtx;
@Autowired
private MemoryCacheService myMemoryCacheService;
@ -114,14 +105,25 @@ public class IdHelperService {
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId) throws ResourceNotFoundException {
// We only pass 1 input in so only 0..1 will come back
IdDt id = new IdDt(theResourceType, theResourceId);
Collection<IResourceLookup> matches = translateForcedIdToPids(theRequestPartitionId, theRequestDetails, Collections.singletonList(id));
assert matches.size() <= 1;
Collection<IResourceLookup> matches = translateForcedIdToPids(theRequestPartitionId, Collections.singletonList(id));
if (matches.isEmpty()) {
throw new ResourceNotFoundException(id);
}
if (matches.size() > 1) {
/*
* This means that:
* 1. There are two resources with the exact same resource type and forced id
* 2. The unique constraint on this column-pair has been dropped
*/
String msg = myFhirCtx.getLocalizer().getMessage(IdHelperService.class, "nonUniqueForcedId");
throw new PreconditionFailedException(msg);
}
return matches.iterator().next();
}
@ -137,10 +139,10 @@ public class IdHelperService {
Long retVal;
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
if (myDaoConfig.isDeleteEnabled()) {
retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, theId);
retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, theId).getResourceId();
} else {
String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + theResourceType + "/" + theId;
retVal = myMemoryCacheService.get(MemoryCacheService.CacheEnum.PERSISTENT_ID, key, t -> resolveResourceIdentity(theRequestPartitionId, theResourceType, theId));
retVal = myMemoryCacheService.get(MemoryCacheService.CacheEnum.PERSISTENT_ID, key, t -> resolveResourceIdentity(theRequestPartitionId, theResourceType, theId).getResourceId());
}
} else {
@ -187,9 +189,10 @@ public class IdHelperService {
} else {
String partitionIdStringForKey = RequestPartitionId.stringifyForKey(theRequestPartitionId);
for (Iterator<String> idIterator = nextIds.iterator(); idIterator.hasNext(); ) {
String nextId = idIterator.next();
String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + nextResourceType + "/" + nextId;
String key = partitionIdStringForKey + "/" + nextResourceType + "/" + nextId;
Long nextCachedPid = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.PERSISTENT_ID, key);
if (nextCachedPid != null) {
idIterator.remove();
@ -203,10 +206,12 @@ public class IdHelperService {
if (theRequestPartitionId.isAllPartitions()) {
views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
} else {
if (theRequestPartitionId.getPartitionId() != null) {
views = myForcedIdDao.findByTypeAndForcedIdInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId());
} else {
if (theRequestPartitionId.isDefaultPartition()) {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds);
} else if (theRequestPartitionId.hasDefaultPartitionId()) {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionIdsOrNullPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
} else {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionIds(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
}
}
for (Object[] nextView : views) {
@ -214,7 +219,7 @@ public class IdHelperService {
Long pid = (Long) nextView[1];
retVal.add(new ResourcePersistentId(pid));
String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + nextResourceType + "/" + forcedId;
String key = partitionIdStringForKey + "/" + nextResourceType + "/" + forcedId;
myMemoryCacheService.put(MemoryCacheService.CacheEnum.PERSISTENT_ID, key, pid);
}
}
@ -261,35 +266,7 @@ public class IdHelperService {
return typeToIds;
}
private Long resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, @Nonnull String theResourceType, @Nonnull String theId) {
Optional<Long> pid;
if (theRequestPartitionId.isAllPartitions()) {
try {
pid = myForcedIdDao.findByTypeAndForcedId(theResourceType, theId);
} catch (IncorrectResultSizeDataAccessException e) {
/*
* This means that:
* 1. There are two resources with the exact same resource type and forced id
* 2. The unique constraint on this column-pair has been dropped
*/
String msg = myFhirCtx.getLocalizer().getMessage(IdHelperService.class, "nonUniqueForcedId");
throw new PreconditionFailedException(msg);
}
} else {
if (theRequestPartitionId.getPartitionId() == null) {
pid = myForcedIdDao.findByPartitionIdNullAndTypeAndForcedId(theResourceType, theId);
} else {
pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(theRequestPartitionId.getPartitionId(), theResourceType, theId);
}
}
if (!pid.isPresent()) {
throw new ResourceNotFoundException(new IdDt(theResourceType, theId));
}
return pid.get();
}
private Collection<IResourceLookup> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest, Collection<IIdType> theId) {
private Collection<IResourceLookup> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, Collection<IIdType> theId) {
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
if (theId.isEmpty()) {
@ -333,10 +310,12 @@ public class IdHelperService {
if (theRequestPartitionId.isAllPartitions()) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
} else {
if (theRequestPartitionId.getPartitionId() != null) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId());
} else {
if (theRequestPartitionId.isDefaultPartition()) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds);
} else if (theRequestPartitionId.hasDefaultPartitionId()) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(nextResourceType, nextIds, theRequestPartitionId.getPartitionIdsWithoutDefault());
} else {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
}
}
@ -379,10 +358,12 @@ public class IdHelperService {
if (theRequestPartitionId.isAllPartitions()) {
lookup = myResourceTableDao.findLookupFieldsByResourcePid(thePidsToResolve);
} else {
if (theRequestPartitionId.getPartitionId() != null) {
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartition(thePidsToResolve, theRequestPartitionId.getPartitionId());
} else {
if (theRequestPartitionId.isDefaultPartition()) {
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(thePidsToResolve);
} else if (theRequestPartitionId.hasDefaultPartitionId()) {
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionIdsOrNullPartition(thePidsToResolve, theRequestPartitionId.getPartitionIdsWithoutDefault());
} else {
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionIds(thePidsToResolve, theRequestPartitionId.getPartitionIds());
}
}
lookup
@ -448,23 +429,14 @@ public class IdHelperService {
@Nonnull
public Long getPidOrThrowException(IIdType theId) {
return getPidOrThrowException(theId, null);
}
@Nonnull
public Long getPidOrThrowException(IAnyResource theResource) {
return (Long) theResource.getUserData(RESOURCE_PID);
}
@Nonnull
public Long getPidOrThrowException(IIdType theId, RequestDetails theRequestDetails) {
List<IIdType> ids = Collections.singletonList(theId);
List<ResourcePersistentId> resourcePersistentIds = this.resolveResourcePersistentIdsWithCache(RequestPartitionId.allPartitions(), ids);
return resourcePersistentIds.get(0).getIdAsLong();
}
public Map<Long, IIdType> getPidToIdMap(Collection<IIdType> theIds, RequestDetails theRequestDetails) {
return theIds.stream().collect(Collectors.toMap(this::getPidOrThrowException, Function.identity()));
@Nonnull
public Long getPidOrThrowException(IAnyResource theResource) {
return (Long) theResource.getUserData(RESOURCE_PID);
}
public IIdType resourceIdFromPidOrThrowException(Long thePid) {

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
@ -99,7 +100,7 @@ public class SearchParamWithInlineReferencesExtractor {
RequestPartitionId partitionId;
if (myPartitionSettings.isPartitioningEnabled()) {
partitionId = theEntity.getPartitionId();
partitionId = PartitionablePartitionId.toRequestPartitionId(theEntity.getPartitionId());
} else {
partitionId = RequestPartitionId.allPartitions();
}

View File

@ -92,10 +92,10 @@ abstract class BasePredicateBuilder {
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, From<?, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
if (!theRequestPartitionId.isAllPartitions()) {
if (theRequestPartitionId.getPartitionId() != null) {
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), theRequestPartitionId.getPartitionId()));
} else {
if (theRequestPartitionId.isDefaultPartition()) {
myQueryStack.addPredicate(myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue")));
} else {
myQueryStack.addPredicate(theJoin.get("myPartitionIdValue").in(theRequestPartitionId.getPartitionIds()));
}
}
myQueryStack.addPredicateWithImplicitTypeSelection(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
@ -184,12 +184,11 @@ abstract class BasePredicateBuilder {
void addPartitionIdPredicate(RequestPartitionId theRequestPartitionId, From<?, ? extends BasePartitionable> theJoin, List<Predicate> theCodePredicates) {
if (!theRequestPartitionId.isAllPartitions()) {
Integer partitionId = theRequestPartitionId.getPartitionId();
Predicate partitionPredicate;
if (partitionId != null) {
partitionPredicate = myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue").as(Integer.class), partitionId);
} else {
if (theRequestPartitionId.isDefaultPartition()) {
partitionPredicate = myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue").as(Integer.class));
} else {
partitionPredicate = theJoin.get("myPartitionIdValue").as(Integer.class).in(theRequestPartitionId.getPartitionIds());
}
myQueryStack.addPredicate(partitionPredicate);
}

View File

@ -63,8 +63,8 @@ class QueryRootEntryResourceTable extends QueryRootEntry {
}
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myDeleted")));
if (!myRequestPartitionId.isAllPartitions()) {
if (myRequestPartitionId.getPartitionId() != null) {
addPredicate(myCriteriaBuilder.equal(getRoot().get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId()));
if (!myRequestPartitionId.isDefaultPartition()) {
addPredicate(getRoot().get("myPartitionIdValue").as(Integer.class).in(myRequestPartitionId.getPartitionIds()));
} else {
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myPartitionIdValue").as(Integer.class)));
}

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.model.primitive.IdDt;
@ -32,6 +33,7 @@ import ca.uhn.fhir.rest.api.Constants;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Subselect;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
@ -199,8 +201,13 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
}
@Override
public RequestPartitionId getPartitionId() {
return RequestPartitionId.fromPartitionId(myPartitionId);
@Nullable
public PartitionablePartitionId getPartitionId() {
if (myPartitionId != null) {
return new PartitionablePartitionId(myPartitionId, null);
} else {
return null;
}
}
public byte[] getResource() {

View File

@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.partition;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import javax.annotation.Nullable;
public interface IPartitionLookupSvc {
/**
@ -33,6 +35,7 @@ public interface IPartitionLookupSvc {
/**
* @throws ResourceNotFoundException If the name is not known
*/
@Nullable
PartitionEntity getPartitionByName(String theName) throws ResourceNotFoundException;
/**

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.partition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.github.benmanes.caffeine.cache.CacheLoader;
@ -47,9 +48,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
public static final int DEFAULT_PERSISTED_PARTITION_ID = 0;
public static final String DEFAULT_PERSISTED_PARTITION_NAME = "DEFAULT";
private static final String DEFAULT_PERSISTED_PARTITION_DESC = "Default partition";
private static final Pattern PARTITION_NAME_VALID_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+");
private static final Logger ourLog = LoggerFactory.getLogger(PartitionLookupSvcImpl.class);
@ -76,23 +74,14 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new IdToPartitionCacheLoader());
myTxTemplate = new TransactionTemplate(myTxManager);
// Create default partition definition if it doesn't already exist
myTxTemplate.executeWithoutResult(t -> {
if (myPartitionDao.findById(DEFAULT_PERSISTED_PARTITION_ID).isPresent() == false) {
ourLog.info("Creating default partition definition");
PartitionEntity partitionEntity = new PartitionEntity();
partitionEntity.setId(DEFAULT_PERSISTED_PARTITION_ID);
partitionEntity.setName(DEFAULT_PERSISTED_PARTITION_NAME);
partitionEntity.setDescription(DEFAULT_PERSISTED_PARTITION_DESC);
myPartitionDao.save(partitionEntity);
}
});
}
@Override
public PartitionEntity getPartitionByName(String theName) {
Validate.notBlank(theName, "The name must not be null or blank");
if (JpaConstants.DEFAULT_PARTITION_NAME.equals(theName)) {
return null;
}
return myNameToPartitionCache.get(theName);
}
@ -114,11 +103,6 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
validateHaveValidPartitionIdAndName(thePartition);
validatePartitionNameDoesntAlreadyExist(thePartition.getName());
if (thePartition.getId() == DEFAULT_PERSISTED_PARTITION_ID) {
String msg = myFhirCtx.getLocalizer().getMessage(PartitionLookupSvcImpl.class, "cantCreatePartition0");
throw new InvalidRequestException(msg);
}
ourLog.info("Creating new partition with ID {} and Name {}", thePartition.getId(), thePartition.getName());
myPartitionDao.save(thePartition);
@ -141,13 +125,6 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
validatePartitionNameDoesntAlreadyExist(thePartition.getName());
}
if (DEFAULT_PERSISTED_PARTITION_ID == thePartition.getId()) {
if (!DEFAULT_PERSISTED_PARTITION_NAME.equals(thePartition.getName())) {
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantRenameDefaultPartition");
throw new InvalidRequestException(msg);
}
}
existingPartition.setName(thePartition.getName());
existingPartition.setDescription(thePartition.getDescription());
myPartitionDao.save(existingPartition);
@ -160,11 +137,6 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
public void deletePartition(Integer thePartitionId) {
validatePartitionIdSupplied(myFhirCtx, thePartitionId);
if (DEFAULT_PERSISTED_PARTITION_ID == thePartitionId) {
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantDeleteDefaultPartition");
throw new InvalidRequestException(msg);
}
Optional<PartitionEntity> partition = myPartitionDao.findById(thePartitionId);
if (!partition.isPresent()) {
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "unknownPartitionId", thePartitionId);
@ -189,6 +161,11 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
throw new InvalidRequestException(msg);
}
if (thePartition.getName().equals(JpaConstants.DEFAULT_PARTITION_NAME)) {
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantCreateDefaultPartition");
throw new InvalidRequestException(msg);
}
if (!PARTITION_NAME_VALID_PATTERN.matcher(thePartition.getName()).matches()) {
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "invalidName", thePartition.getName());
throw new InvalidRequestException(msg);

View File

@ -27,9 +27,9 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -39,7 +39,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooks;
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject;
@ -102,9 +105,9 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
requestPartitionId = null;
}
validatePartition(requestPartitionId, theResourceType, Pointcut.STORAGE_PARTITION_IDENTIFY_READ);
validateRequestPartitionNotNull(requestPartitionId, Pointcut.STORAGE_PARTITION_IDENTIFY_READ);
return normalizeAndNotifyHooks(requestPartitionId, theRequest);
return validateNormalizeAndNotifyHooksForRead(requestPartitionId, theRequest);
}
return RequestPartitionId.allPartitions();
@ -132,48 +135,29 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
String resourceName = myFhirContext.getResourceType(theResource);
validatePartition(requestPartitionId, resourceName, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE);
validateSinglePartitionForCreate(requestPartitionId, resourceName, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE);
return normalizeAndNotifyHooks(requestPartitionId, theRequest);
return validateNormalizeAndNotifyHooksForRead(requestPartitionId, theRequest);
}
return RequestPartitionId.allPartitions();
}
/**
* If the partition only has a name but not an ID, this method resolves the ID
* If the partition only has a name but not an ID, this method resolves the ID.
* <p>
* If the partition has an ID but not a name, the name is resolved.
* <p>
* If the partition has both, they are validated to ensure that they correspond.
*/
@Nonnull
private RequestPartitionId normalizeAndNotifyHooks(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest) {
private RequestPartitionId validateNormalizeAndNotifyHooksForRead(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest) {
RequestPartitionId retVal = theRequestPartitionId;
if (retVal.getPartitionName() != null) {
PartitionEntity partition;
try {
partition = myPartitionConfigSvc.getPartitionByName(retVal.getPartitionName());
} catch (IllegalArgumentException e) {
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperSvc.class, "unknownPartitionName", retVal.getPartitionName());
throw new ResourceNotFoundException(msg);
}
if (retVal.getPartitionId() != null) {
Validate.isTrue(retVal.getPartitionId().equals(partition.getId()), "Partition name %s does not match ID %n", retVal.getPartitionName(), retVal.getPartitionId());
} else {
retVal = RequestPartitionId.forPartitionIdAndName(partition.getId(), retVal.getPartitionName(), retVal.getPartitionDate());
}
} else if (retVal.getPartitionId() != null) {
PartitionEntity partition;
try {
partition = myPartitionConfigSvc.getPartitionById(retVal.getPartitionId());
} catch (IllegalArgumentException e) {
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperSvc.class, "unknownPartitionId", retVal.getPartitionId());
throw new ResourceNotFoundException(msg);
}
retVal = RequestPartitionId.forPartitionIdAndName(partition.getId(), partition.getName(), retVal.getPartitionDate());
if (retVal.getPartitionNames() != null) {
retVal = validateAndNormalizePartitionNames(retVal);
} else if (retVal.hasPartitionIds()) {
retVal = validateAndNormalizePartitionIds(retVal);
}
// Note: It's still possible that the partition only has a date but no name/id
@ -188,27 +172,117 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
}
private void validatePartition(RequestPartitionId theRequestPartitionId, @Nonnull String theResourceName, Pointcut thePointcut) {
if (theRequestPartitionId == null) {
throw new InternalErrorException("No interceptor provided a value for pointcut: " + thePointcut);
private RequestPartitionId validateAndNormalizePartitionIds(RequestPartitionId theRequestPartitionId) {
List<String> names = null;
for (int i = 0; i < theRequestPartitionId.getPartitionIds().size(); i++) {
PartitionEntity partition;
Integer id = theRequestPartitionId.getPartitionIds().get(i);
if (id == null) {
partition = null;
} else {
try {
partition = myPartitionConfigSvc.getPartitionById(id);
} catch (IllegalArgumentException e) {
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperSvc.class, "unknownPartitionId", theRequestPartitionId.getPartitionIds().get(i));
throw new ResourceNotFoundException(msg);
}
}
if (theRequestPartitionId.getPartitionId() != null) {
if (theRequestPartitionId.getPartitionNames() != null) {
if (partition == null) {
Validate.isTrue(theRequestPartitionId.getPartitionIds().get(i) == null, "Partition %s must not have an ID", JpaConstants.DEFAULT_PARTITION_NAME);
} else {
Validate.isTrue(Objects.equals(theRequestPartitionId.getPartitionIds().get(i), partition.getId()), "Partition name %s does not match ID %n", theRequestPartitionId.getPartitionNames().get(i), theRequestPartitionId.getPartitionIds().get(i));
}
} else {
if (names == null) {
names = new ArrayList<>();
}
if (partition != null) {
names.add(partition.getName());
} else {
names.add(null);
}
}
}
if (names != null) {
return RequestPartitionId.forPartitionIdsAndNames(names, theRequestPartitionId.getPartitionIds(), theRequestPartitionId.getPartitionDate());
}
return theRequestPartitionId;
}
private RequestPartitionId validateAndNormalizePartitionNames(RequestPartitionId theRequestPartitionId) {
List<Integer> ids = null;
for (int i = 0; i < theRequestPartitionId.getPartitionNames().size(); i++) {
PartitionEntity partition;
try {
partition = myPartitionConfigSvc.getPartitionByName(theRequestPartitionId.getPartitionNames().get(i));
} catch (IllegalArgumentException e) {
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperSvc.class, "unknownPartitionName", theRequestPartitionId.getPartitionNames().get(i));
throw new ResourceNotFoundException(msg);
}
if (theRequestPartitionId.hasPartitionIds()) {
if (partition == null) {
Validate.isTrue(theRequestPartitionId.getPartitionIds().get(i) == null, "Partition %s must not have an ID", JpaConstants.DEFAULT_PARTITION_NAME);
} else {
Validate.isTrue(Objects.equals(theRequestPartitionId.getPartitionIds().get(i), partition.getId()), "Partition name %s does not match ID %n", theRequestPartitionId.getPartitionNames().get(i), theRequestPartitionId.getPartitionIds().get(i));
}
} else {
if (ids == null) {
ids = new ArrayList<>();
}
if (partition != null) {
ids.add(partition.getId());
} else {
ids.add(null);
}
}
}
if (ids != null) {
return RequestPartitionId.forPartitionIdsAndNames(theRequestPartitionId.getPartitionNames(), ids, theRequestPartitionId.getPartitionDate());
}
return theRequestPartitionId;
}
private void validateSinglePartitionForCreate(RequestPartitionId theRequestPartitionId, @Nonnull String theResourceName, Pointcut thePointcut) {
validateRequestPartitionNotNull(theRequestPartitionId, thePointcut);
if (theRequestPartitionId.hasPartitionIds()) {
validateSinglePartitionIdOrNameForCreate(theRequestPartitionId.getPartitionIds());
}
validateSinglePartitionIdOrNameForCreate(theRequestPartitionId.getPartitionNames());
// Make sure we're not using one of the conformance resources in a non-default partition
if ((theRequestPartitionId.hasPartitionIds() && !theRequestPartitionId.getPartitionIds().contains(null)) ||
(theRequestPartitionId.hasPartitionNames() && !theRequestPartitionId.getPartitionNames().contains(JpaConstants.DEFAULT_PARTITION_NAME))) {
if (myPartitioningBlacklist.contains(theResourceName)) {
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperSvc.class, "blacklistedResourceTypeForPartitioning", theResourceName);
throw new UnprocessableEntityException(msg);
}
// Make sure the partition exists
try {
myPartitionConfigSvc.getPartitionById(theRequestPartitionId.getPartitionId());
} catch (IllegalArgumentException e) {
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperSvc.class, "unknownPartitionId", theRequestPartitionId.getPartitionId());
throw new InvalidRequestException(msg);
}
}
private void validateRequestPartitionNotNull(RequestPartitionId theTheRequestPartitionId, Pointcut theThePointcut) {
if (theTheRequestPartitionId == null) {
throw new InternalErrorException("No interceptor provided a value for pointcut: " + theThePointcut);
}
}
private void validateSinglePartitionIdOrNameForCreate(@Nullable List<?> thePartitionIds) {
if (thePartitionIds != null && thePartitionIds.size() != 1) {
throw new InternalErrorException("RequestPartitionId must contain a single partition for create operations, found: " + thePartitionIds);
}
}
}

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.InCondition;
import com.healthmarketscience.sqlbuilder.NotCondition;
import com.healthmarketscience.sqlbuilder.UnaryCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
@ -35,6 +36,7 @@ import java.util.List;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toAndPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toEqualToOrInPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toOrPredicate;
public abstract class BaseJoiningPredicateBuilder extends BasePredicateBuilder {
@ -70,12 +72,16 @@ public abstract class BaseJoiningPredicateBuilder extends BasePredicateBuilder {
public Condition createPartitionIdPredicate(RequestPartitionId theRequestPartitionId) {
if (theRequestPartitionId != null && !theRequestPartitionId.isAllPartitions()) {
Condition condition;
Integer partitionId = theRequestPartitionId.getPartitionId();
if (partitionId != null) {
Object placeholder = generatePlaceholder(partitionId);
condition = BinaryCondition.equalTo(getPartitionIdColumn(), placeholder);
} else {
if (theRequestPartitionId.isDefaultPartition()) {
condition = UnaryCondition.isNull(getPartitionIdColumn());
} else if (theRequestPartitionId.hasDefaultPartitionId()) {
List<String> placeholders = generatePlaceholders(theRequestPartitionId.getPartitionIdsWithoutDefault());
UnaryCondition partitionNullPredicate = UnaryCondition.isNull(getPartitionIdColumn());
InCondition partitionIdsPredicate = new InCondition(getPartitionIdColumn(), placeholders);
condition = toOrPredicate(partitionNullPredicate, partitionIdsPredicate);
} else {
List<String> placeholders = generatePlaceholders(theRequestPartitionId.getPartitionIds());
condition = new InCondition(getPartitionIdColumn(), placeholders);
}
return condition;
} else {

View File

@ -22,6 +22,7 @@ import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
@ -185,6 +186,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected IPartitionLookupSvc myPartitionConfigSvc;
@Autowired
protected IPartitionDao myPartitionDao;
@Autowired
protected ITermReadSvc myHapiTerminologySvc;
@Autowired
protected CachingValidationSupport myCachingValidationSupport;

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
@ -19,6 +20,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.SqlQuery;
@ -35,6 +37,7 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -74,6 +77,7 @@ import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.startsWith;
@ -90,8 +94,11 @@ import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
static final String PARTITION_1 = "PART-1";
static final String PARTITION_2 = "PART-2";
static final String PARTITION_3 = "PART-3";
static final String PARTITION_4 = "PART-4";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PartitioningSqlR4Test.class);
private MyReadWriteInterceptor myPartitionInterceptor;
private LocalDate myPartitionDate;
private LocalDate myPartitionDate2;
@ -142,11 +149,17 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
myPartitionInterceptor = new MyReadWriteInterceptor();
myInterceptorRegistry.registerInterceptor(myPartitionInterceptor);
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(1).setName("PART-1"));
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(2).setName("PART-2"));
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(3).setName("PART-3"));
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(3).setName(PARTITION_3));
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(4).setName(PARTITION_4));
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
// Ensure the partition names are resolved
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(JpaConstants.DEFAULT_PARTITION_NAME, PARTITION_1, PARTITION_2, PARTITION_3, PARTITION_4));
myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true));
}
@Test
@ -162,7 +175,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(id).orElseThrow(IllegalArgumentException::new);
assertEquals(RequestPartitionId.defaultPartition(), resourceTable.getPartitionId());
assertEquals(null, resourceTable.getPartitionId());
});
}
@ -341,8 +354,10 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(id).orElseThrow(IllegalArgumentException::new);
assertNull(resourceTable.getPartitionId().getPartitionId());
assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
PartitionablePartitionId partitionId = resourceTable.getPartitionId();
assertNotNull(partitionId);
assertNull(partitionId.getPartitionId());
assertEquals(myPartitionDate, partitionId.getPartitionDate());
});
}
@ -393,7 +408,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(RequestPartitionId.defaultPartition(), resourceTable.getPartitionId());
assertEquals(null, resourceTable.getPartitionId());
});
}
@ -615,8 +630,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
// HFJ_FORCED_ID
List<ForcedId> forcedIds = myForcedIdDao.findAll();
assertEquals(2, forcedIds.size());
assertEquals(null, forcedIds.get(0).getPartitionId().getPartitionId());
assertEquals(null, forcedIds.get(1).getPartitionId().getPartitionId());
assertEquals(null, forcedIds.get(0).getPartitionId());
assertEquals(null, forcedIds.get(1).getPartitionId());
});
}
@ -883,6 +898,114 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
}
}
@Test
public void testRead_PidId_MultiplePartitionNames() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue());
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue());
createPatient(withPartition(2), withActiveTrue());
IIdType patientId3 = createPatient(withPartition(3), withActiveTrue());
// Two partitions - Found
{
myCaptureQueriesListener.clear();
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(PARTITION_1, PARTITION_2));
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
// Only the read columns should be used, but no selectors on partition ID
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as "), searchSql);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
// Two partitions including default - Found
{
myCaptureQueriesListener.clear();
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(PARTITION_1, JpaConstants.DEFAULT_PARTITION_NAME));
IdType gotId1 = myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientIdNull, gotId1);
// Only the read columns should be used, but no selectors on partition ID
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as "), searchSql);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
// Two partitions - Not Found
{
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(PARTITION_1, PARTITION_2));
try {
myPatientDao.read(patientId3, mySrd);
fail();
} catch (ResourceNotFoundException e) {
// good
}
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(PARTITION_1, PARTITION_2));
try {
myPatientDao.read(patientIdNull, mySrd);
fail();
} catch (ResourceNotFoundException e) {
// good
}
}
}
@Test
public void testRead_PidId_MultiplePartitionIds() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue());
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue());
createPatient(withPartition(2), withActiveTrue());
IIdType patientId3 = createPatient(withPartition(3), withActiveTrue());
// Two partitions - Found
{
myCaptureQueriesListener.clear();
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionIds(1, 2));
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
// Only the read columns should be used, but no selectors on partition ID
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as "), searchSql);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
// Two partitions including default - Found
{
myCaptureQueriesListener.clear();
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionIds(1, null));
IdType gotId1 = myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientIdNull, gotId1);
// Only the read columns should be used, but no selectors on partition ID
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as "), searchSql);
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
// Two partitions - Not Found
{
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(PARTITION_1, PARTITION_2));
try {
myPatientDao.read(patientId3, mySrd);
fail();
} catch (ResourceNotFoundException e) {
// good
}
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(PARTITION_1, PARTITION_2));
try {
myPatientDao.read(patientIdNull, mySrd);
fail();
} catch (ResourceNotFoundException e) {
// good
}
}
}
@Test
public void testRead_PidId_DefaultPartition() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue());
@ -1030,7 +1153,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1047,7 +1170,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1072,11 +1195,11 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IN ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'true'"), searchSql);
}
@ -1089,11 +1212,11 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"));
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IN ('1')"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'false'"));
}
}
@ -1113,7 +1236,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1130,7 +1253,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1156,7 +1279,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1183,12 +1306,12 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IN ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '-3438137196820602023'"), searchSql);
}
@ -1211,12 +1334,12 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IN ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'"), searchSql);
}
@ -1237,7 +1360,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdDefault));
assertThat(ids, contains(patientIdDefault));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1262,7 +1385,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1282,13 +1405,56 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
@Test
public void testSearch_NoParams_SearchMultiplePartitionsByName_NoDefault() {
createPatient(withPartition(null), withActiveTrue());
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue());
IIdType patientId2 = createPatient(withPartition(2), withActiveTrue());
createPatient(withPartition(3), withActiveTrue());
addReadPartitions(PARTITION_1, PARTITION_2);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1, patientId2));
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(searchSql, containsString("PARTITION_ID IN ('1','2')"));
}
@Test
public void testSearch_NoParams_SearchMultiplePartitionsByName_WithDefault() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue());
createPatient(withPartition(1), withActiveTrue());
IIdType patientId2 = createPatient(withPartition(2), withActiveTrue());
createPatient(withPartition(3), withActiveTrue());
addReadPartitions(JpaConstants.DEFAULT_PARTITION_NAME, PARTITION_2);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids.toString(), ids, Matchers.containsInAnyOrder(patientIdNull, patientId2));
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(sql, sql, containsString("PARTITION_ID IN ('2')"));
assertThat(sql, sql, containsString("PARTITION_ID IS NULL"));
}
@Test
public void testSearch_DateParam_SearchAllPartitions() {
myPartitionSettings.setIncludePartitionInSearchHashes(false);
@ -1309,7 +1475,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1325,7 +1491,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1341,7 +1507,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1357,7 +1523,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1371,9 +1537,9 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
public void testSearch_DateParam_SearchSpecificPartitions() {
myPartitionSettings.setIncludePartitionInSearchHashes(false);
IIdType patientIdNull = createPatient(withPartition(null), withBirthdate("2020-04-20"));
createPatient(withPartition(null), withBirthdate("2020-04-20"));
IIdType patientId1 = createPatient(withPartition(1), withBirthdate("2020-04-20"));
IIdType patientId2 = createPatient(withPartition(2), withBirthdate("2020-04-20"));
createPatient(withPartition(2), withBirthdate("2020-04-20"));
createPatient(withPartition(null), withBirthdate("2021-04-20"));
createPatient(withPartition(1), withBirthdate("2021-04-20"));
createPatient(withPartition(2), withBirthdate("2021-04-20"));
@ -1390,7 +1556,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1406,7 +1572,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1422,7 +1588,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1438,7 +1604,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1453,8 +1619,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
myPartitionSettings.setIncludePartitionInSearchHashes(false);
IIdType patientIdNull = createPatient(withPartition(null), withBirthdate("2020-04-20"));
IIdType patientId1 = createPatient(withPartition(1), withBirthdate("2020-04-20"));
IIdType patientId2 = createPatient(withPartition(2), withBirthdate("2020-04-20"));
createPatient(withPartition(1), withBirthdate("2020-04-20"));
createPatient(withPartition(2), withBirthdate("2020-04-20"));
createPatient(withPartition(null), withBirthdate("2021-04-20"));
createPatient(withPartition(1), withBirthdate("2021-04-20"));
createPatient(withPartition(2), withBirthdate("2021-04-20"));
@ -1468,7 +1634,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1484,7 +1650,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1500,7 +1666,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1516,7 +1682,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1581,7 +1747,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1603,7 +1769,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1628,7 +1794,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1636,6 +1802,73 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@Test
public void testSearch_StringParam_SearchMultiplePartitions() {
IIdType patientIdNull = createPatient(withPartition(null), withFamily("FAMILY"));
IIdType patientId1 = createPatient(withPartition(1), withFamily("FAMILY"));
IIdType patientId2 = createPatient(withPartition(2), withFamily("FAMILY"));
createPatient(withPartition(3), withFamily("FAMILY"));
createPatient(withPartition(null), withFamily("BLAH"));
createPatient(withPartition(1), withFamily("BLAH"));
createPatient(withPartition(2), withFamily("BLAH"));
createPatient(withPartition(3), withFamily("BLAH"));
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
map.setLoadSynchronous(true);
// Match two partitions
{
addReadPartition(1, 2);
myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids.toString(), ids, Matchers.containsInAnyOrder(patientId1, patientId2));
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(searchSql, containsString("PARTITION_ID IN ('1','2')"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
// Match two partitions including null
{
addReadPartition(1, null);
myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids.toString(), ids, Matchers.containsInAnyOrder(patientId1, patientIdNull));
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(searchSql, containsString("PARTITION_ID IS NULL"));
assertThat(searchSql, containsString("PARTITION_ID IN ('1')"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
}
@Test
public void testSearch_StringParam_SearchMultiplePartitions_IncludePartitionInHashes() {
myPartitionSettings.setIncludePartitionInSearchHashes(true);
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAMILY"));
map.setLoadSynchronous(true);
addReadPartition(1, 2);
try {
myPatientDao.search(map);
fail();
} catch (InternalErrorException e) {
assertEquals("Can not search multiple partitions when partitions are included in search hashes", e.getMessage());
}
}
@Test
public void testSearch_StringParam_SearchAllPartitions_IncludePartitionInHashes() {
myPartitionSettings.setIncludePartitionInSearchHashes(true);
@ -1671,7 +1904,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull));
assertThat(ids, contains(patientIdNull));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1698,7 +1931,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1722,7 +1955,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1739,7 +1972,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1));
assertThat(ids, contains(patientIdNull, patientId1));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1771,7 +2004,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
assertThat(ids.toString(), ids, Matchers.contains(patientIdNull));
assertThat(ids.toString(), ids, contains(patientIdNull));
}
@Test
@ -1791,7 +2024,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1813,7 +2046,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1837,7 +2070,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1864,7 +2097,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
assertThat(ids, contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1889,7 +2122,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
assertThat(ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1912,7 +2145,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(id));
assertThat(ids, contains(id));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1935,7 +2168,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(id));
assertThat(ids, contains(id));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
@ -1970,11 +2203,11 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId));
assertThat(ids, contains(observationId));
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IN ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.SRC_PATH = 'Observation.subject'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.TARGET_RESOURCE_ID = '" + patientId.getIdPartAsLong() + "'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
@ -2000,14 +2233,14 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IIdType observationId = createObservation(withPartition(null), withSubject(patientId));
addReadDefaultPartition();
;
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId));
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(observationId));
assertThat(ids, contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql);
@ -2044,11 +2277,11 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId));
assertThat(ids, contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID='1'"), searchSql);
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID in ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "and forcedid0_.RESOURCE_TYPE='Patient'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
@ -2080,10 +2313,10 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(ids, contains(observationId)); // FIXME: move up
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID is null"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.RESOURCE_TYPE='Patient'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
@ -2129,7 +2362,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.history(id, null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
assertThat(ids, contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
@ -2192,7 +2425,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.history(id, null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
assertThat(ids, contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
@ -2233,7 +2466,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.history(id, null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
assertThat(ids, contains(id.withVersion("2").getValue(), id.withVersion("1").getValue()));
}
@Test
@ -2262,26 +2495,27 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = mySystemDao.history(null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Count
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(1, countMatches(searchSql, "count("));
assertEquals(1, countMatches(searchSql, "PARTITION_ID='1'"));
ourLog.info("SQL:{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false).toUpperCase();
assertEquals(1, countMatches(sql, "COUNT("), sql);
assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql);
// Fetch history
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(1, countMatches(searchSql, "PARTITION_ID='1'"));
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase();
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql);
// Fetch history resource
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase());
assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase());
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, false);
sql = sql.replace(" ", "").toUpperCase();
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_ID="), sql);
assertEquals(0, countMatches(sql, "PARTITION_IDIN"), sql);
}
@Test
@ -2299,7 +2533,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = mySystemDao.history(null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
@ -2320,6 +2554,42 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase());
}
@Test
public void testHistory_Server_MultiplePartitions() {
String idNull1 = createPatient(withPartition(null), withBirthdate("2020-01-01")).toUnqualifiedVersionless().getValue();
sleepAtLeast(10);
String idNull2 = createPatient(withPartition(null), withBirthdate("2020-01-01")).toUnqualifiedVersionless().getValue();
sleepAtLeast(10);
String id21 = createPatient(withPartition(2), withBirthdate("2020-01-01")).toUnqualifiedVersionless().getValue();
sleepAtLeast(10);
String id31 = createPatient(withPartition(3), withBirthdate("2020-01-01")).toUnqualifiedVersionless().getValue();
sleepAtLeast(10);
String id22 = createPatient(withPartition(2), withBirthdate("2020-01-01")).toUnqualifiedVersionless().getValue();
sleepAtLeast(10);
String id32 = createPatient(withPartition(3), withBirthdate("2020-01-01")).toUnqualifiedVersionless().getValue();
// Multiple Partitions
{
addReadPartition(2, null);
myCaptureQueriesListener.clear();
IBundleProvider results = mySystemDao.history(null, null, mySrd);
assertEquals(4, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, contains(id22, id21, idNull2, idNull1));
}
// Multiple Partitions With Null
{
addReadPartition(2, 3);
myCaptureQueriesListener.clear();
IBundleProvider results = mySystemDao.history(null, null, mySrd);
assertEquals(4, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, contains(id32, id22, id31, id21));
}
}
@Test
public void testHistory_Type_AllPartitions() {
addReadAllPartitions();
@ -2346,25 +2616,25 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.history(null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Count
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false).toUpperCase();
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "count("));
assertEquals(1, countMatches(sql, "PARTITION_ID='1'"));
assertEquals(1, countMatches(sql, "COUNT("), sql);
assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql);
// Fetch history resources
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase();
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "PARTITION_ID='1'"));
assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql);
// Resolve forced ID
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, false).toUpperCase();
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_ID='1'"));
assertEquals(0, countMatches(sql, "PARTITION_ID IN ('1')"), sql);
}
@ -2383,7 +2653,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
IBundleProvider results = myPatientDao.history(null, null, mySrd);
assertEquals(2, results.sizeOrThrowNpe());
List<String> ids = toUnqualifiedIdValues(results);
assertThat(ids, Matchers.contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue()));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
@ -2414,8 +2684,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
verify(interceptor, times(1)).invoke(eq(Pointcut.STORAGE_PARTITION_SELECTED), captor.capture());
RequestPartitionId partitionId = captor.getValue().get(RequestPartitionId.class);
assertEquals(1, partitionId.getPartitionId().intValue());
assertEquals("PART-1", partitionId.getPartitionName());
assertEquals(1, partitionId.getPartitionIds().get(0).intValue());
assertEquals("PART-1", partitionId.getPartitionNames().get(0));
} finally {
myInterceptorRegistry.unregisterInterceptor(interceptor);
@ -2471,9 +2741,15 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
myPartitionInterceptor.addCreatePartition(requestPartitionId);
}
private void addReadPartition(Integer thePartitionId) {
private void addReadPartition(Integer... thePartitionId) {
Validate.notNull(thePartitionId);
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionId(thePartitionId, null));
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionIds(thePartitionId));
}
private void addReadPartitions(String... thePartitionNames) {
Validate.notNull(thePartitionNames);
Validate.isTrue(thePartitionNames.length > 0);
myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(thePartitionNames));
}
private void addReadDefaultPartition() {

View File

@ -50,18 +50,6 @@ public class PartitionSettingsSvcImplTest extends BaseJpaR4Test {
}
@Test
public void testDeletePartition_TryToDeleteDefault() {
try {
myPartitionConfigSvc.deletePartition(0);
fail();
} catch (InvalidRequestException e) {
assertEquals("Can not delete default partition", e.getMessage());
}
}
@Test
public void testUpdatePartition_TryToUseExistingName() {
@ -92,14 +80,14 @@ public class PartitionSettingsSvcImplTest extends BaseJpaR4Test {
@Test
public void testUpdatePartition_TryToRenameDefault() {
PartitionEntity partition = new PartitionEntity();
partition.setId(0);
partition.setId(null);
partition.setName("NAME123");
partition.setDescription("A description");
try {
myPartitionConfigSvc.updatePartition(partition);
fail();
} catch (InvalidRequestException e) {
assertEquals("Can not rename default partition", e.getMessage());
assertEquals("Partition must have an ID and a Name", e.getMessage());
}
}
@ -141,21 +129,6 @@ public class PartitionSettingsSvcImplTest extends BaseJpaR4Test {
}
@Test
public void testCreatePartition_0Blocked() {
PartitionEntity partition = new PartitionEntity();
partition.setId(0);
partition.setName("NAME123");
partition.setDescription("A description");
try {
myPartitionConfigSvc.createPartition(partition);
fail();
} catch (InvalidRequestException e) {
assertEquals("Can not create a partition with ID 0 (this is a reserved value)", e.getMessage());
}
}
@Test
public void testUpdatePartition_UnknownPartitionBlocked() {
PartitionEntity partition = new PartitionEntity();

View File

@ -27,7 +27,7 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.DEFAULT_PERSISTED_PARTITION_NAME;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.DEFAULT_PARTITION_NAME;
public abstract class BaseMultitenantResourceProviderR4Test extends BaseResourceProviderR4Test implements ITestDataBuilder {
@ -88,7 +88,7 @@ public abstract class BaseMultitenantResourceProviderR4Test extends BaseResource
private void createTenants() {
myTenantClientInterceptor.setTenantId(DEFAULT_PERSISTED_PARTITION_NAME);
myTenantClientInterceptor.setTenantId(DEFAULT_PARTITION_NAME);
myClient
.operation()

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
@ -65,15 +66,15 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static Server ourServer;
private static DatabaseBackedPagingProvider ourPagingProvider;
private static GenericWebApplicationContext ourWebApplicationContext;
private static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
protected IGenericClient myClient;
@Autowired
protected SubscriptionLoader mySubscriptionLoader;
@Autowired
protected DaoRegistry myDaoRegistry;
@Autowired
protected IPartitionDao myPartitionDao;
ResourceCountCache myResourceCountsCache;
private TerminologyUploaderProvider myTerminologyUploaderProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
public BaseResourceProviderR4Test() {
super();
@ -163,7 +164,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext());
myValidationSupport = wac.getBean(IValidationSupport.class);
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
confProvider.setSearchParamRegistry(ourSearchParamRegistry);

View File

@ -1,5 +1,8 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import org.hl7.fhir.instance.model.api.IIdType;
@ -15,6 +18,8 @@ import java.util.Date;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -31,19 +36,34 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
}
@Test
public void testCreateAndRead() {
public void testCreateAndRead_NamedTenant() {
// Create patients
IIdType idA = createPatient(withTenant(TENANT_A), withActiveTrue());
createPatient(withTenant(TENANT_B), withActiveFalse());
runInTransaction(() -> {
PartitionEntity partition = myPartitionDao.findForName(TENANT_A).orElseThrow(() -> new IllegalStateException());
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(() -> new IllegalStateException());
assertEquals(partition.getId(), resourceTable.getPartitionId().getPartitionId());
});
// Now read back
myTenantClientInterceptor.setTenantId(TENANT_A);
Patient response = myClient.read().resource(Patient.class).withId(idA).execute();
assertTrue(response.getActive());
// Update resource (should remain in correct partition)
createPatient(withActiveFalse(), withId(idA));
// Now read back
response = myClient.read().resource(Patient.class).withId(idA.withVersion("2")).execute();
assertFalse(response.getActive());
myTenantClientInterceptor.setTenantId(TENANT_B);
try {
myClient.read().resource(Patient.class).withId(idA).execute();
@ -53,6 +73,47 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
}
}
@Test
public void testCreateAndRead_DefaultTenant() {
// Create patients
IIdType idA = createPatient(withTenant(JpaConstants.DEFAULT_PARTITION_NAME), withActiveTrue());
createPatient(withTenant(TENANT_B), withActiveFalse());
runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(() -> new IllegalStateException());
assertNull(resourceTable.getPartitionId());
});
// Now read back
myTenantClientInterceptor.setTenantId(JpaConstants.DEFAULT_PARTITION_NAME);
Patient response = myClient.read().resource(Patient.class).withId(idA).execute();
assertTrue(response.getActive());
// Update resource (should remain in correct partition)
createPatient(withActiveFalse(), withId(idA));
// Now read back
response = myClient.read().resource(Patient.class).withId(idA.withVersion("2")).execute();
assertFalse(response.getActive());
// Try reading from wrong partition
myTenantClientInterceptor.setTenantId(TENANT_B);
try {
myClient.read().resource(Patient.class).withId(idA).execute();
fail();
} catch (ResourceNotFoundException e) {
// good
}
}
@Test
public void testCreate_InvalidTenant() {

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.migrate.tasks;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.EmpiLink;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConceptMap;
@ -748,7 +749,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.withColumns("HASH_IDENTITY", "SP_LATITUDE", "SP_LONGITUDE");
spidxCoords
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.5")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")))
.setColumnName("HASH_IDENTITY")
);
}
@ -771,7 +772,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.dropIndex("20180903.9", "IDX_SP_DATE");
spidxDate
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.10")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")))
.setColumnName("HASH_IDENTITY")
);
}
@ -792,7 +793,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.withColumns("HASH_IDENTITY", "SP_VALUE");
spidxNumber
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.14")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")))
.setColumnName("HASH_IDENTITY")
);
}
@ -829,9 +830,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.withColumns("HASH_IDENTITY_SYS_UNITS", "SP_VALUE");
spidxQuantity
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.22")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS")))
.setColumnName("HASH_IDENTITY")
);
}
@ -861,8 +862,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxString
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.28")
.setColumnName("HASH_NORM_PREFIX")
.addCalculator("HASH_NORM_PREFIX", t -> ResourceIndexedSearchParamString.calculateHashNormalized(new PartitionSettings(), null, new ModelConfig(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_VALUE_NORMALIZED")))
.addCalculator("HASH_EXACT", t -> ResourceIndexedSearchParamString.calculateHashExact(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE_EXACT")))
.addCalculator("HASH_NORM_PREFIX", t -> ResourceIndexedSearchParamString.calculateHashNormalized(new PartitionSettings(), RequestPartitionId.defaultPartition(), new ModelConfig(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_VALUE_NORMALIZED")))
.addCalculator("HASH_EXACT", t -> ResourceIndexedSearchParamString.calculateHashExact(new PartitionSettings(), (ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId) null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE_EXACT")))
);
}
@ -909,10 +910,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxToken
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.39")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")))
.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")))
.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")))
.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")))
.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")))
);
}
@ -939,8 +940,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxUri
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.44")
.setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_URI", t -> ResourceIndexedSearchParamUri.calculateHashUri(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_URI")))
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), (RequestPartitionId)null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_URI", t -> ResourceIndexedSearchParamUri.calculateHashUri(new PartitionSettings(), (RequestPartitionId)null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_URI")))
);
}
@ -973,7 +974,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
Boolean present = columnToBoolean(t.get("SP_PRESENT"));
String resType = (String) t.get("RES_TYPE");
String paramName = (String) t.get("PARAM_NAME");
Long hash = SearchParamPresent.calculateHashPresence(new PartitionSettings(), null, resType, paramName, present);
Long hash = SearchParamPresent.calculateHashPresence(new PartitionSettings(), (RequestPartitionId)null, resType, paramName, present);
consolidateSearchParamPresenceIndexesTask.executeSql("HFJ_RES_PARAM_PRESENT", "update HFJ_RES_PARAM_PRESENT set HASH_PRESENCE = ? where PID = ?", hash, pid);
});
version.addTask(consolidateSearchParamPresenceIndexesTask);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
@ -42,7 +43,7 @@ public class ArbitrarySqlTaskTest extends BaseTest {
Boolean present = (Boolean) t.get("SP_PRESENT");
String resType = (String) t.get("RES_TYPE");
String paramName = (String) t.get("PARAM_NAME");
Long hash = SearchParamPresent.calculateHashPresence(new PartitionSettings(), null, resType, paramName, present);
Long hash = SearchParamPresent.calculateHashPresence(new PartitionSettings(), RequestPartitionId.defaultPartition(), resType, paramName, present);
task.executeSql("HFJ_RES_PARAM_PRESENT", "update HFJ_RES_PARAM_PRESENT set HASH_PRESENT = ? where PID = ?", hash, pid);
});

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
@ -27,10 +28,10 @@ public class CalculateHashesTest extends BaseTest {
CalculateHashesTask task = new CalculateHashesTask(VersionEnum.V3_5_0, "1");
task.setTableName("HFJ_SPIDX_TOKEN");
task.setColumnName("HASH_IDENTITY");
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.setBatchSize(1);
getMigrator().addTask(task);
@ -77,10 +78,10 @@ public class CalculateHashesTest extends BaseTest {
CalculateHashesTask task = new CalculateHashesTask(VersionEnum.V3_5_0, "1");
task.setTableName("HFJ_SPIDX_TOKEN");
task.setColumnName("HASH_IDENTITY");
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")));
task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM")));
task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE")));
task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getParamName(), t.getString("SP_VALUE")));
task.setBatchSize(3);
getMigrator().addTask(task);

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Embedded;
@ -42,18 +41,18 @@ public class BasePartitionable implements Serializable {
@Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
private Integer myPartitionIdValue;
@Nonnull
public RequestPartitionId getPartitionId() {
if (myPartitionId != null) {
return myPartitionId.toPartitionId();
} else {
return RequestPartitionId.defaultPartition();
@Nullable
public PartitionablePartitionId getPartitionId() {
return myPartitionId;
}
public void setPartitionId(PartitionablePartitionId thePartitionId) {
myPartitionId = thePartitionId;
}
public void setPartitionId(@Nullable RequestPartitionId theRequestPartitionId) {
if (theRequestPartitionId != null) {
myPartitionId = new PartitionablePartitionId(theRequestPartitionId.getPartitionId(), theRequestPartitionId.getPartitionDate());
myPartitionId = new PartitionablePartitionId(theRequestPartitionId.getFirstPartitionIdOrNull(), theRequestPartitionId.getPartitionDate());
} else {
myPartitionId = null;
}

View File

@ -20,10 +20,12 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
@ -179,6 +181,11 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
return myModelConfig;
}
public static long calculateHashIdentity(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashIdentity(thePartitionSettings, requestPartitionId, theResourceType, theParamName);
}
public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
}
@ -190,8 +197,12 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
Hasher hasher = HASH_FUNCTION.newHasher();
if (thePartitionSettings.isPartitioningEnabled() && thePartitionSettings.isIncludePartitionInSearchHashes() && theRequestPartitionId != null) {
if (theRequestPartitionId.getPartitionId() != null) {
hasher.putInt(theRequestPartitionId.getPartitionId());
if (theRequestPartitionId.getPartitionIds().size() > 1) {
throw new InternalErrorException("Can not search multiple partitions when partitions are included in search hashes");
}
Integer partitionId = theRequestPartitionId.getFirstPartitionIdOrNull();
if (partitionId != null) {
hasher.putInt(partitionId);
}
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import javax.annotation.Nullable;
import java.util.Date;
public interface IBaseResourceEntity {
@ -51,5 +52,6 @@ public interface IBaseResourceEntity {
boolean isHasTags();
RequestPartitionId getPartitionId();
@Nullable
PartitionablePartitionId getPartitionId();
}

View File

@ -22,9 +22,11 @@ package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.validation.constraints.Null;
import java.time.LocalDate;
@Embeddable
@ -83,4 +85,13 @@ public class PartitionablePartitionId implements Cloneable {
public RequestPartitionId toPartitionId() {
return RequestPartitionId.fromPartitionId(getPartitionId(), getPartitionDate());
}
@Nullable
public static RequestPartitionId toRequestPartitionId(@Nullable PartitionablePartitionId theRequestPartitionId) {
if (theRequestPartitionId != null) {
return theRequestPartitionId.toPartitionId();
} else {
return null;
}
}
}

View File

@ -104,4 +104,5 @@ public class ResourceHistoryProvenanceEntity extends BasePartitionable {
return myId;
}
}

View File

@ -20,9 +20,18 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.io.Serializable;
@Embeddable
@ -53,31 +62,11 @@ public class ResourceHistoryTag extends BaseTag implements Serializable {
@Column(name = "RES_ID", nullable = false)
private Long myResourceId;
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public Long getResourceId() {
return myResourceId;
}
public void setResourceId(Long theResourceId) {
myResourceId = theResourceId;
}
public ResourceHistoryTag() {
}
public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag, RequestPartitionId theRequestPartitionId) {
public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag, PartitionablePartitionId theRequestPartitionId) {
setTag(theTag);
setResource(theResourceHistoryTable);
setResourceId(theResourceHistoryTable.getResourceId());
@ -85,6 +74,22 @@ public class ResourceHistoryTag extends BaseTag implements Serializable {
setPartitionId(theRequestPartitionId);
}
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public Long getResourceId() {
return myResourceId;
}
public void setResourceId(Long theResourceId) {
myResourceId = theResourceId;
}
public ResourceHistoryTable getResourceHistory() {
return myResourceHistory;
}

View File

@ -283,10 +283,20 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return retval;
}
public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashSystemAndUnits(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashUnits(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashUnits(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theUnits);
}
public static long calculateHashUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUnits);
}

View File

@ -270,10 +270,20 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return defaultString(getValueNormalized()).startsWith(normalizedString);
}
public static long calculateHashExact(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashExact(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValueExact);
}
public static long calculateHashExact(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact);
}
public static long calculateHashNormalized(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashNormalized(thePartitionSettings, requestPartitionId, theModelConfig, theResourceType, theParamName, theValueNormalized);
}
public static long calculateHashNormalized(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) {
/*
* If we're not allowing contained searches, we'll add the first

View File

@ -286,14 +286,29 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return retVal;
}
public static long calculateHashSystem(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashSystem(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem);
}
public static long calculateHashSystem(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, trim(theSystem));
}
public static long calculateHashSystemAndValue(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theValue) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashSystemAndValue(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem, theValue);
}
public static long calculateHashSystemAndValue(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theValue) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue));
}
public static long calculateHashValue(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValue) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashValue(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theValue);
}
public static long calculateHashValue(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValue) {
String value = trim(theValue);
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);

View File

@ -209,6 +209,11 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return defaultString(getUri()).equalsIgnoreCase(uri.getValueNotNull());
}
public static long calculateHashUri(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUri) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashUri(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theUri);
}
public static long calculateHashUri(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUri) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUri);
}

View File

@ -20,13 +20,23 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
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;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = "HFJ_RES_TAG", uniqueConstraints = {
@ -52,10 +62,17 @@ public class ResourceTag extends BaseTag {
@Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourceId;
/**
* Constructor
*/
public ResourceTag() {
super();
}
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag, RequestPartitionId theRequestPartitionId) {
/**
* Constructor
*/
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag, PartitionablePartitionId theRequestPartitionId) {
setTag(theTag);
setResource(theResourceTable);
setResourceId(theResourceTable.getId());

View File

@ -126,6 +126,11 @@ public class SearchParamPresent extends BasePartitionable implements Serializabl
myPartitionSettings = thePartitionSettings;
}
public static long calculateHashPresence(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, Boolean thePresent) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashPresence(thePartitionSettings, requestPartitionId, theResourceType, theParamName, thePresent);
}
public static long calculateHashPresence(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, Boolean thePresent) {
String string = thePresent != null ? Boolean.toString(thePresent) : Boolean.toString(false);
return BaseResourceIndexedSearchParam.hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, string);

View File

@ -204,6 +204,11 @@ public class JpaConstants {
public static final String EXT_SEARCHPARAM_PHONETIC_ENCODER = "http://hapifhir.io/fhir/StructureDefinition/searchparameter-phonetic-encoder";
public static final String VALUESET_FILTER_DISPLAY = "display";
/**
* The name of the default partition
*/
public static final String DEFAULT_PARTITION_NAME = "DEFAULT";
/**
* Non-instantiable
*/

View File

@ -215,7 +215,8 @@ public class RuleBuilder implements IAuthRuleBuilder {
if (theResource != null) {
RequestPartitionId partitionId = (RequestPartitionId) theResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (partitionId != null) {
if (!myTenantIds.contains(partitionId.getPartitionName())) {
String partitionNameOrNull = partitionId.getFirstPartitionNameOrNull();
if (partitionNameOrNull == null || !myTenantIds.contains(partitionNameOrNull)) {
return !myOutcome;
}
}