Work on multitenancy

This commit is contained in:
jamesagnew 2020-03-29 18:16:04 -04:00
parent 691f2c4e9a
commit 62d867902f
50 changed files with 1085 additions and 612 deletions

View File

@ -27,7 +27,11 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.*; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** /**
* Value for {@link Hook#value()} * Value for {@link Hook#value()}
@ -374,7 +378,6 @@ public enum Pointcut {
), ),
/** /**
* <b>Server Hook:</b> * <b>Server Hook:</b>
* This method is called after the server implementation method has been called, but before any attempt * This method is called after the server implementation method has been called, but before any attempt
@ -1323,14 +1326,16 @@ public enum Pointcut {
/** /**
* <b>Storage Hook:</b> * <b>Storage Hook:</b>
* Invoked before an <code>$expunge</code> operation on all data (expungeEverything) is called. * Invoked before FHIR <b>create</b> operation to request the identification of the partition ID to be associated
* with the resource being created. This hook will only be called if partitioning is enabled in the JPA
* server.
* <p> * <p>
* Hooks will be passed a reference to a counter containing the current number of records that have been deleted.
* If the hook deletes any records, the hook is expected to increment this counter by the number of records deleted.
* </p>
* Hooks may accept the following parameters: * Hooks may accept the following parameters:
* </p>
* <ul> * <ul>
* <li>
* org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned. * org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned.
* </li>
* <li> * <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
@ -1346,18 +1351,53 @@ public enum Pointcut {
* </li> * </li>
* </ul> * </ul>
* <p> * <p>
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.model.entity.TenantId</code> or <code>null</code>. * Hooks should return an instance of <code>ca.uhn.fhir.jpa.model.entity.PartitionId</code> or <code>null</code>.
* </p> * </p>
*/ */
STORAGE_TENANT_IDENTIFY_CREATE ( STORAGE_PARTITION_IDENTIFY_CREATE(
// Return type // Return type
"ca.uhn.fhir.jpa.model.entity.TenantId", "ca.uhn.fhir.jpa.model.entity.PartitionId",
// Params // Params
"org.hl7.fhir.instance.model.api.IBaseResource", "org.hl7.fhir.instance.model.api.IBaseResource",
"ca.uhn.fhir.rest.api.server.RequestDetails", "ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
), ),
/**
* <b>Storage Hook:</b>
* Invoked before FHIR read/access operation (e.g. <b>read/vread</b>, <b>search</b>, <b>history</b>, etc.) operation to request the
* identification of the partition ID to be associated with the resource being created. This hook will only be called if
* partitioning is enabled in the JPA server.
* <p>
* Hooks may accept the following parameters:
* </p>
* <ul>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. Note that the bean
* properties are not all guaranteed to be populated, depending on how early during processing the
* exception occurred.
* </li>
* <li>
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
* </li>
* </ul>
* <p>
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.model.entity.PartitionId</code> or <code>null</code>.
* </p>
*/
STORAGE_PARTITION_IDENTIFY_READ(
// Return type
"ca.uhn.fhir.jpa.model.entity.PartitionId",
// Params
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
/** /**
* <b>Performance Tracing Hook:</b> * <b>Performance Tracing Hook:</b>
* This hook is invoked when any informational messages generated by the * This hook is invoked when any informational messages generated by the
@ -1759,9 +1799,7 @@ public enum Pointcut {
* This pointcut is used only for unit tests. Do not use in production code as it may be changed or * This pointcut is used only for unit tests. Do not use in production code as it may be changed or
* removed at any time. * removed at any time.
*/ */
TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName()) TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName());
;
private final List<String> myParameterTypes; private final List<String> myParameterTypes;
private final Class<?> myReturnType; private final Class<?> myReturnType;

View File

@ -136,5 +136,8 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1} ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1} ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}" ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"

View File

@ -20,6 +20,7 @@
"contactName": "Kevin Mayfield", "contactName": "Kevin Mayfield",
"contactEmail": "Kevin.mayfield@mayfield-is.co.uk", "contactEmail": "Kevin.mayfield@mayfield-is.co.uk",
"link": "https://data.developer.nhs.uk/ccri/exp", "link": "https://data.developer.nhs.uk/ccri/exp",
"city": "UK",
"lat": 55.378052, "lat": 55.378052,
"lon": -3.435973, "lon": -3.435973,
"added": "2019-08-19" "added": "2019-08-19"
@ -29,6 +30,7 @@
"contactName": "David Hay", "contactName": "David Hay",
"contactEmail": "david.hay25@gmail.com", "contactEmail": "david.hay25@gmail.com",
"link": "http://clinfhir.com", "link": "http://clinfhir.com",
"city": "New Zealand",
"lat": -42.651737, "lat": -42.651737,
"lon": 171.926909, "lon": 171.926909,
"added": "2019-08-18" "added": "2019-08-18"

View File

@ -6,6 +6,7 @@
<ul> <ul>
<li>Hibernate ORM (JPA): 5.4.6 -&gt; 5.4.12</li> <li>Hibernate ORM (JPA): 5.4.6 -&gt; 5.4.12</li>
<li>Hibernate Search (JPA): 5.11.3 -&gt; 5.11.5</li> <li>Hibernate Search (JPA): 5.11.3 -&gt; 5.11.5</li>
<li>Guava (JPA): 28.0 -&gt; 28.2</li>
</ul>" </ul>"
- item: - item:
issue: "1583" issue: "1583"

View File

@ -44,6 +44,7 @@ page.server_jpa.architecture=Architecture
page.server_jpa.configuration=Configuration page.server_jpa.configuration=Configuration
page.server_jpa.search=Search page.server_jpa.search=Search
page.server_jpa.performance=Performance page.server_jpa.performance=Performance
page.server_jpa.partitioning=Partitioning
page.server_jpa.upgrading=Upgrade Guide page.server_jpa.upgrading=Upgrade Guide
section.interceptors.title=Interceptors section.interceptors.title=Interceptors

View File

@ -0,0 +1,18 @@
# Partitioning
# Limitations
Partitioning is a relatively new feature in HAPI FHIR and has a number of known limitations. If you are intending to use partitioning for achieving a multi-tenant architecture it is important to carefully consider these limitations.
None of the limitations listed here are considered permanent. Over time the HAPI FHIR team are hoping to make all of these features partition aware.
* **Subscriptions may not be partitioned**: All subscriptions must be placed in the default partition, and subscribers will receive deliveries for any matching resources from all partitions.
* **Conformance resources may not be partitioned**: The following resources must be placed in the default partition, and will be shared for any validation activities across all partitions:
* StructureDefinition
* Questionnaire
* ValueSet
* CodeSystem
* ConceptMap
* **Bulk Operations are not partition aware**: Bulk export operations will export data across all partitions.

View File

@ -221,7 +221,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
map.setLastUpdated(new DateRangeParam(job.getSince(), null)); map.setLastUpdated(new DateRangeParam(job.getSince(), null));
} }
IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null); IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null, null);
storeResultsToFiles(nextCollection, sb, resultIterator, jobResourceCounter, jobStopwatch); storeResultsToFiles(nextCollection, sb, resultIterator, jobResourceCounter, jobStopwatch);
} }

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl; import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices; import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices; import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
@ -231,6 +232,11 @@ public abstract class BaseConfig {
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer()); return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
} }
@Bean
public RequestPartitionHelperService requestPartitionHelperService() {
return new RequestPartitionHelperService();
}
@Bean @Bean
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() { public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor(); return new PersistenceExceptionTranslationPostProcessor();

View File

@ -219,7 +219,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
retVal.setResourceType(theEntity.getResourceType()); retVal.setResourceType(theEntity.getResourceType());
retVal.setForcedId(theId.getIdPart()); retVal.setForcedId(theId.getIdPart());
retVal.setResource(theEntity); retVal.setResource(theEntity);
retVal.setTenantId(theEntity.getTenantId()); retVal.setPartitionId(theEntity.getPartitionId());
theEntity.setForcedId(retVal); theEntity.setForcedId(retVal);
} }
} }
@ -1095,7 +1095,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity(); ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity();
provenance.setResourceHistoryTable(historyEntry); provenance.setResourceHistoryTable(historyEntry);
provenance.setResourceTable(entity); provenance.setResourceTable(entity);
provenance.setTenantId(entity.getTenantId()); provenance.setPartitionId(entity.getPartitionId());
if (haveRequestId) { if (haveRequestId) {
provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH)); provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH));
} }

View File

@ -24,10 +24,18 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.delete.DeleteConflictList; import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.*; 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.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
@ -40,18 +48,46 @@ import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.server.*; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.validation.*; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationOptions;
import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
@ -67,7 +103,14 @@ import javax.persistence.NoResultException;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -94,6 +137,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private IInstanceValidatorModule myInstanceValidator; private IInstanceValidatorModule myInstanceValidator;
private String myResourceName; private String myResourceName;
private Class<T> myResourceType; private Class<T> myResourceType;
@Autowired
private RequestPartitionHelperService myRequestPartitionHelperService;
@Override @Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) { public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
@ -161,7 +206,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
theResource.setId(UUID.randomUUID().toString()); theResource.setId(UUID.randomUUID().toString());
} }
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails); PartitionId partitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource);
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails, partitionId);
} }
@Override @Override
@ -183,7 +229,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
validateIdPresentForDelete(theId); validateIdPresentForDelete(theId);
validateDeleteEnabled(); validateDeleteEnabled();
final ResourceTable entity = readEntityLatestVersion(theId); final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) { if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version"); throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
} }
@ -390,7 +436,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
} }
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest) { private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest, PartitionId thePartitionId) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource); preProcessResourceForStorage(theResource);
@ -398,17 +444,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entity = new ResourceTable(); ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource)); entity.setResourceType(toResourceName(theResource));
if (myDaoConfig.isMultiTenancyEnabled()) { if (thePartitionId != null) {
// Interceptor call: STORAGE_TENANT_IDENTIFY_CREATE ourLog.debug("Resource has been assigned partition ID: {}", thePartitionId);
HookParams params = new HookParams() entity.setPartitionId(thePartitionId);
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
TenantId tenantId = (TenantId) doCallHooksAndReturnObject(theRequest, Pointcut.STORAGE_TENANT_IDENTIFY_CREATE, params);
if (tenantId != null) {
ourLog.debug("Resource has been assigned tenant ID: {}", tenantId);
entity.setTenantId(tenantId);
}
} }
if (isNotBlank(theIfNoneExist)) { if (isNotBlank(theIfNoneExist)) {
@ -709,7 +747,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theResourceId); throw new ResourceNotFoundException(theResourceId);
} }
ResourceTable latestVersion = readEntityLatestVersion(theResourceId); ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
if (latestVersion.getVersion() != entity.getVersion()) { if (latestVersion.getVersion() != entity.getVersion()) {
doMetaAdd(theMetaAdd, entity); doMetaAdd(theMetaAdd, entity);
} else { } else {
@ -741,7 +779,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theResourceId); throw new ResourceNotFoundException(theResourceId);
} }
ResourceTable latestVersion = readEntityLatestVersion(theResourceId); ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
if (latestVersion.getVersion() != entity.getVersion()) { if (latestVersion.getVersion() != entity.getVersion()) {
doMetaDelete(theMetaDel, entity); doMetaDelete(theMetaDel, entity);
} else { } else {
@ -817,7 +855,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
} else { } else {
entityToUpdate = readEntityLatestVersion(theId); entityToUpdate = readEntityLatestVersion(theId, theRequest);
if (theId.hasVersionIdPart()) { if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
@ -944,7 +982,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) { public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
return readEntity(theId, true, theRequest); return readEntity(theId, true, theRequest);
} }
@ -952,9 +989,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) { public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
validateResourceTypeAndThrowInvalidRequestException(theId); validateResourceTypeAndThrowInvalidRequestException(theId);
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart()); @Nullable PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(partitionId, getResourceName(), theId.getIdPart());
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong()); BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
// Verify that the resource is for the correct partition
if (partitionId != null) {
if (entity.getPartitionId() != null) {
if (!entity.getPartitionId().getPartitionId().equals(partitionId.getPartitionId())) {
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", partitionId, entity.getPartitionId());
entity = null;
}
} else {
ourLog.debug("Performing a read for PartitionId=null but entity has partition: {}", entity.getPartitionId());
entity = null;
}
}
if (entity == null) { if (entity == null) {
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
} }
@ -990,8 +1041,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return entity; return entity;
} }
protected ResourceTable readEntityLatestVersion(IIdType theId) { @NotNull
ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart()); protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails) {
PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, getResourceName());
return readEntityLatestVersion(theId, partitionId);
}
@NotNull
private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable PartitionId thePartitionId) {
validateResourceTypeAndThrowInvalidRequestException(theId);
ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(thePartitionId, getResourceName(), theId.getIdPart());
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId()); ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
if (entity == null) { if (entity == null) {
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
@ -1127,7 +1187,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid); SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest)) {
PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, partitionId)) {
while (iter.hasNext()) { while (iter.hasNext()) {
retVal.add(iter.next()); retVal.add(iter.next());
} }
@ -1231,10 +1293,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
*/ */
resourceId = theResource.getIdElement(); resourceId = theResource.getIdElement();
PartitionId partitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource);
try { try {
entity = readEntityLatestVersion(resourceId); entity = readEntityLatestVersion(resourceId, partitionId);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest); return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest, partitionId);
} }
} }
@ -1282,10 +1345,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable savedEntity = updateInternal(theRequest, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource); ResourceTable savedEntity = updateInternal(theRequest, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource);
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted); DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted);
if (!thePerformIndexing) {
outcome.setId(theResource.getIdElement());
}
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart()); String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart());
outcome.setOperationOutcome(createInfoOperationOutcome(msg)); outcome.setOperationOutcome(createInfoOperationOutcome(msg));
@ -1304,7 +1363,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (theId == null || theId.hasIdPart() == false) { if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE"); throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
} }
final ResourceTable entity = readEntityLatestVersion(theId); final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that // Validate that there are no resources pointing to the candidate that
// would prevent deletion // would prevent deletion

View File

@ -191,7 +191,7 @@ public class DaoConfig {
/** /**
* @since 5.0.0 * @since 5.0.0
*/ */
private boolean myMultiTenancyEnabled; private boolean myPartitioningEnabled;
/** /**
* Constructor * Constructor
@ -1944,21 +1944,21 @@ public class DaoConfig {
} }
/** /**
* If enabled (default is <code>false</code>) the JPA server will support multitenant queries * If enabled (default is <code>false</code>) the JPA server will support data partitioning
* *
* @since 5.0.0 * @since 5.0.0
*/ */
public void setMultiTenancyEnabled(boolean theMultiTenancyEnabled) { public void setPartitioningEnabled(boolean theMultiTenancyEnabled) {
myMultiTenancyEnabled = theMultiTenancyEnabled; myPartitioningEnabled = theMultiTenancyEnabled;
} }
/** /**
* If enabled (default is <code>false</code>) the JPA server will support multitenant queries * If enabled (default is <code>false</code>) the JPA server will support data partitioning
* *
* @since 5.0.0 * @since 5.0.0
*/ */
public boolean isMultiTenancyEnabled() { public boolean isPartitioningEnabled() {
return myMultiTenancyEnabled; return myPartitioningEnabled;
} }
public enum StoreMetaSourceInformationEnum { public enum StoreMetaSourceInformationEnum {

View File

@ -50,7 +50,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId); ResourceTable entity = readEntityLatestVersion(theId, theRequest);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -22,7 +22,9 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
@ -269,6 +271,9 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
return doSearch(theResourceName, theParams, null); return doSearch(theResourceName, theParams, null);
} }
@Autowired
private RequestPartitionHelperService myRequestPartitionHelperService;
@Transactional() @Transactional()
@Override @Override
public List<Suggestion> suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest) { public List<Suggestion> suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest) {
@ -282,7 +287,10 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) { if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
throw new InvalidRequestException("Invalid context: " + theContext); throw new InvalidRequestException("Invalid context: " + theContext);
} }
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(contextParts[0], contextParts[1]);
// FIXME: this method should require a resource type
PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, null);
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(partitionId, contextParts[0], contextParts[1]);
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.Include;
@ -37,12 +38,12 @@ import java.util.Set;
public interface ISearchBuilder { public interface ISearchBuilder {
IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest); IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest, PartitionId thePartitionId);
Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId);
void setMaxResultsToFetch(Integer theMaxResultsToFetch); void setMaxResultsToFetch(Integer theMaxResultsToFetch);
Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest);
void loadResourcesByPid(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails); void loadResourcesByPid(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails);
Set<ResourcePersistentId> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<ResourcePersistentId> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, Set<ResourcePersistentId> loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<ResourcePersistentId> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,

View File

@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -165,6 +166,7 @@ public class SearchBuilder implements ISearchBuilder {
private Integer myMaxResultsToFetch; private Integer myMaxResultsToFetch;
private Set<ResourcePersistentId> myPidSet; private Set<ResourcePersistentId> myPidSet;
private PredicateBuilder myPredicateBuilder; private PredicateBuilder myPredicateBuilder;
private PartitionId myPartitionId;
/** /**
* Constructor * Constructor
@ -181,7 +183,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
private void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) { private void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) {
myPredicateBuilder.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest); myPredicateBuilder.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, myPartitionId);
} }
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams, RequestDetails theRequest) { private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams, RequestDetails theRequest) {
@ -219,8 +221,8 @@ public class SearchBuilder implements ISearchBuilder {
} }
@Override @Override
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest) { public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, PartitionId thePartitionId) {
init(theParams, theSearchUuid); init(theParams, theSearchUuid, thePartitionId);
TypedQuery<Long> query = createQuery(null, null, true, theRequest); TypedQuery<Long> query = createQuery(null, null, true, theRequest);
return new CountQueryIterator(query); return new CountQueryIterator(query);
@ -235,8 +237,8 @@ public class SearchBuilder implements ISearchBuilder {
} }
@Override @Override
public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) { public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, PartitionId thePartitionId) {
init(theParams, theSearchRuntimeDetails.getSearchUuid()); init(theParams, theSearchRuntimeDetails.getSearchUuid(), thePartitionId);
if (myPidSet == null) { if (myPidSet == null) {
myPidSet = new HashSet<>(); myPidSet = new HashSet<>();
@ -245,13 +247,16 @@ public class SearchBuilder implements ISearchBuilder {
return new QueryIterator(theSearchRuntimeDetails, theRequest); return new QueryIterator(theSearchRuntimeDetails, theRequest);
} }
private void init(SearchParameterMap theParams, String theSearchUuid) { private void init(SearchParameterMap theParams, String theSearchUuid, PartitionId thePartitionId) {
myParams = theParams; myParams = theParams;
myCriteriaBuilder = myEntityManager.getCriteriaBuilder(); myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchUuid; mySearchUuid = theSearchUuid;
myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory); myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory);
myPartitionId = thePartitionId;
} }
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) { private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
CriteriaQuery<Long> outerQuery; CriteriaQuery<Long> outerQuery;
/* /*
@ -296,7 +301,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myParams.get(IAnyResource.SP_RES_ID) != null) { if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myResourceName, idParam.getValue()); ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myPartitionId, myResourceName, idParam.getValue());
if (myAlsoIncludePids == null) { if (myAlsoIncludePids == null) {
myAlsoIncludePids = new ArrayList<>(1); myAlsoIncludePids = new ArrayList<>(1);
} }
@ -352,6 +357,9 @@ public class SearchBuilder implements ISearchBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName)); myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
} }
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted"))); myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
if (myPartitionId != null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue"), myPartitionId.getPartitionId()));
}
} }
// Last updated // Last updated

View File

@ -39,6 +39,9 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id") @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id")
Optional<Long> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId); Optional<Long> findByTypeAndForcedId(@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 FROM ForcedId f WHERE f.myResourcePid = :resource_pid") @Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid); ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId); ResourceTable entity = readEntityLatestVersion(theId, theRequest);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -70,7 +70,7 @@ public class DaoSearchParamSynchronizer {
theEntity.getParamsQuantity().remove(next); theEntity.getParamsQuantity().remove(next);
} }
for (T next : quantitiesToAdd) { for (T next : quantitiesToAdd) {
next.setTenantId(theEntity.getTenantId()); next.setPartitionId(theEntity.getPartitionId());
myEntityManager.merge(next); myEntityManager.merge(next);
} }

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourceLookup; import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
@ -51,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -59,6 +61,7 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -132,14 +135,14 @@ public class IdHelperService {
* @throws ResourceNotFoundException If the ID can not be found * @throws ResourceNotFoundException If the ID can not be found
*/ */
@Nonnull @Nonnull
public ResourcePersistentId resolveResourcePersistentIds(String theResourceType, String theId) { public ResourcePersistentId resolveResourcePersistentIds(PartitionId thePartitionId, String theResourceType, String theId) {
Long retVal; Long retVal;
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) { if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
if (myDaoConfig.isDeleteEnabled()) { if (myDaoConfig.isDeleteEnabled()) {
retVal = resolveResourceIdentity(theResourceType, theId); retVal = resolveResourceIdentity(thePartitionId, theResourceType, theId);
} else { } else {
String key = theResourceType + "/" + theId; String key = thePartitionId + "/" + theResourceType + "/" + theId;
retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(theResourceType, theId)); retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(thePartitionId, theResourceType, theId));
} }
} else { } else {
@ -248,12 +251,18 @@ public class IdHelperService {
return typeToIds; return typeToIds;
} }
private Long resolveResourceIdentity(String theResourceType, String theId) { private Long resolveResourceIdentity(@Nullable PartitionId thePartitionId, @Nonnull String theResourceType, @Nonnull String theId) {
Long retVal; Optional<Long> pid;
retVal = myForcedIdDao if (thePartitionId != null) {
.findByTypeAndForcedId(theResourceType, theId) pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(thePartitionId.getPartitionId(), theResourceType, theId);
.orElseThrow(() -> new ResourceNotFoundException(new IdDt(theResourceType, theId))); } else {
return retVal; pid = myForcedIdDao.findByTypeAndForcedId(theResourceType, theId);
}
if (pid.isPresent() == false) {
throw new ResourceNotFoundException(new IdDt(theResourceType, theId));
}
return pid.get();
} }
private Collection<IResourceLookup> translateForcedIdToPids(RequestDetails theRequest, Collection<IIdType> theId) { private Collection<IResourceLookup> translateForcedIdToPids(RequestDetails theRequest, Collection<IIdType> theId) {
@ -274,7 +283,7 @@ public class IdHelperService {
if (!pids.isEmpty()) { if (!pids.isEmpty()) {
myResourceTableDao.findLookupFieldsByResourcePid(pids) myResourceTableDao.findLookupFieldsByResourcePid(pids)
.stream() .stream()
.map(lookup -> new ResourceLookup((String)lookup[0], (Long)lookup[1], (Date)lookup[2])) .map(lookup -> new ResourceLookup((String) lookup[0], (Long) lookup[1], (Date) lookup[2]))
.forEach(retVal::add); .forEach(retVal::add);
} }
} }

View File

@ -0,0 +1,101 @@
package ca.uhn.fhir.jpa.dao.partition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject;
public class RequestPartitionHelperService {
private final HashSet<Object> myPartitioningBlacklist;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private FhirContext myFhirContext;
public RequestPartitionHelperService() {
// FIXME: document this list
myPartitioningBlacklist = new HashSet<>();
// Infrastructure
myPartitioningBlacklist.add("Subscription");
myPartitioningBlacklist.add("SearchParameter");
// Validation
myPartitioningBlacklist.add("StructureDefinition");
myPartitioningBlacklist.add("Questionnaire");
// Terminology
myPartitioningBlacklist.add("ConceptMap");
myPartitioningBlacklist.add("CodeSystem");
myPartitioningBlacklist.add("ValueSet");
}
/**
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_READ</code> interceptor pointcut to determine the tenant for a read request
*/
@Nullable
public PartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) {
PartitionId partitionId = null;
if (myDaoConfig.isPartitioningEnabled()) {
// Interceptor call: STORAGE_PARTITION_IDENTIFY_READ
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
validatePartition(partitionId, theResourceType);
}
return partitionId;
}
/**
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_CREATE</code> interceptor pointcut to determine the tenant for a read request
*/
@Nullable
public PartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource) {
PartitionId partitionId = null;
if (myDaoConfig.isPartitioningEnabled()) {
// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
HookParams params = new HookParams()
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
validatePartition(partitionId, resourceName);
}
return partitionId;
}
private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName) {
if (thePartitionId != null) {
if (myPartitioningBlacklist.contains(theResourceName)) {
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName);
throw new InvalidRequestException(msg);
}
}
}
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
@ -125,8 +126,10 @@ abstract class BasePredicateBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(hashPresence, hash)); myQueryRoot.addPredicate(myCriteriaBuilder.equal(hashPresence, hash));
} }
void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin) { void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin, PartitionId thePartitionId) {
if (thePartitionId != null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), thePartitionId.getPartitionId()));
}
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName)); myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName)); myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing)); myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -31,5 +32,6 @@ public interface IPredicateBuilder {
Predicate addPredicate(String theResourceName, Predicate addPredicate(String theResourceName,
String theParamName, String theParamName,
List<? extends IQueryParameterType> theList, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation); SearchFilterParser.CompareOperation operation,
PartitionId thePartitionId);
} }

View File

@ -91,24 +91,24 @@ public class PredicateBuilder {
return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation); return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation);
} }
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) { public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest); myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, thePartitionId);
} }
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest) { Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest); return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest, thePartitionId);
} }
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<ResourceTable, ResourceLink> theJoin) { Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<ResourceTable, ResourceLink> theJoin) {
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin); return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
} }
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, RequestDetails theRequest) { void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, PartitionId thePartitionId) {
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequest); myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, thePartitionId);
} }
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequest); return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, thePartitionId);
} }
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin) { Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin) {

View File

@ -38,6 +38,7 @@ import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@ -98,7 +99,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
@Scope("prototype") @Scope("prototype")
class PredicateBuilderReference extends BasePredicateBuilder { class PredicateBuilderReference extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class); private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class);
private final PredicateBuilder myPredicateBuilder;
@Autowired @Autowired
IdHelperService myIdHelperService; IdHelperService myIdHelperService;
@Autowired @Autowired
@ -110,8 +111,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
@Autowired @Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster; private IInterceptorBroadcaster myInterceptorBroadcaster;
private final PredicateBuilder myPredicateBuilder;
PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) { PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
super(theSearchBuilder); super(theSearchBuilder);
myPredicateBuilder = thePredicateBuilder; myPredicateBuilder = thePredicateBuilder;
@ -125,7 +124,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String theParamName, String theParamName,
List<? extends IQueryParameterType> theList, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation, SearchFilterParser.CompareOperation operation,
RequestDetails theRequest) { RequestDetails theRequest,
PartitionId thePartitionId) {
//Is this just to ensure the chain has been split correctly??? //Is this just to ensure the chain has been split correctly???
assert theParamName.contains(".") == false; assert theParamName.contains(".") == false;
@ -177,7 +177,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
* Handle chained search, e.g. Patient?organization.name=Kwik-e-mart * Handle chained search, e.g. Patient?organization.name=Kwik-e-mart
*/ */
return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest); return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest, thePartitionId);
} }
@ -243,7 +243,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
* This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain * This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain
* on the device. * on the device.
*/ */
private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, Join<ResourceTable, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest) { private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, Join<ResourceTable, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, PartitionId thePartitionId) {
final List<Class<? extends IBaseResource>> resourceTypes; final List<Class<? extends IBaseResource>> resourceTypes;
if (!theReferenceParam.hasResourceType()) { if (!theReferenceParam.hasResourceType()) {
@ -384,7 +384,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} }
Subquery<Long> subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest); Subquery<Long> subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest, thePartitionId);
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin); Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ); Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
@ -410,9 +410,9 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String message = new StringBuilder() String message = new StringBuilder()
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ") .append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")
.append("This is less efficient than using a qualified type. ") .append("This is less efficient than using a qualified type. ")
.append("[" + theParamName + "] resolves to ["+ theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) +"].") .append("[" + theParamName + "] resolves to [" + theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) + "].")
.append("If you know what you're looking for, try qualifying it like this: ") .append("If you know what you're looking for, try qualifying it like this: ")
.append(theCandidateTargetTypes.stream().map(cls -> "[" +cls.getSimpleName() +":"+theParamName+"]").collect(Collectors.joining(" or "))) .append(theCandidateTargetTypes.stream().map(cls -> "[" + cls.getSimpleName() + ":" + theParamName + "]").collect(Collectors.joining(" or ")))
.toString(); .toString();
StorageProcessingMessage msg = new StorageProcessingMessage() StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage(message); .setMessage(message);
@ -472,7 +472,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return chainValue; return chainValue;
} }
Subquery<Long> createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest) { Subquery<Long> createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest, PartitionId thePartitionId) {
Subquery<Long> subQ = myQueryRoot.subquery(Long.class); Subquery<Long> subQ = myQueryRoot.subquery(Long.class);
/* /*
* We're doing a chain call, so push the current query root * We're doing a chain call, so push the current query root
@ -490,7 +490,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted"))); myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
if (theFoundChainMatch) { if (theFoundChainMatch) {
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest); searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, thePartitionId);
subQ.where(myQueryRoot.getPredicateArray()); subQ.where(myQueryRoot.getPredicateArray());
} }
@ -501,7 +501,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return subQ; return subQ;
} }
void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) { void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, PartitionId thePartitionId) {
if (theAndOrParams.isEmpty()) { if (theAndOrParams.isEmpty()) {
return; return;
@ -509,7 +509,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
switch (theParamName) { switch (theParamName) {
case IAnyResource.SP_RES_ID: case IAnyResource.SP_RES_ID:
myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, theRequest); myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, thePartitionId);
break; break;
case IAnyResource.SP_RES_LANGUAGE: case IAnyResource.SP_RES_LANGUAGE:
@ -518,7 +518,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break; break;
case Constants.PARAM_HAS: case Constants.PARAM_HAS:
addPredicateHas(theResourceName, theAndOrParams, theRequest); addPredicateHas(theResourceName, theAndOrParams, theRequest, thePartitionId);
break; break;
case Constants.PARAM_TAG: case Constants.PARAM_TAG:
@ -548,7 +548,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
break; break;
case REFERENCE: case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicate(theResourceName, theParamName, nextAnd, null, theRequest); addPredicate(theResourceName, theParamName, nextAnd, null, theRequest, thePartitionId);
} }
break; break;
case STRING: case STRING:
@ -613,7 +613,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
// point to do something more fancy... // point to do something more fancy...
ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryRoot.getPredicates()); ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryRoot.getPredicates());
Predicate filterPredicate = processFilter(filter, theResourceName, theRequest); Predicate filterPredicate = processFilter(filter, theResourceName, theRequest, thePartitionId);
myQueryRoot.clearPredicates(); myQueryRoot.clearPredicates();
myQueryRoot.addPredicates(holdPredicates); myQueryRoot.addPredicates(holdPredicates);
myQueryRoot.addPredicate(filterPredicate); myQueryRoot.addPredicate(filterPredicate);
@ -629,20 +629,17 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} }
} }
private Predicate processFilter(SearchFilterParser.Filter theFilter, private Predicate processFilter(SearchFilterParser.Filter theFilter, String theResourceName, RequestDetails theRequest, PartitionId thePartitionId) {
String theResourceName, RequestDetails theRequest) {
if (theFilter instanceof SearchFilterParser.FilterParameter) { if (theFilter instanceof SearchFilterParser.FilterParameter) {
return processFilterParameter((SearchFilterParser.FilterParameter) theFilter, return processFilterParameter((SearchFilterParser.FilterParameter) theFilter,
theResourceName, theRequest); theResourceName, theRequest, thePartitionId);
} else if (theFilter instanceof SearchFilterParser.FilterLogical) { } else if (theFilter instanceof SearchFilterParser.FilterLogical) {
// Left side // Left side
Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), theResourceName, theRequest, thePartitionId);
theResourceName, theRequest);
// Right side // Right side
Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, theRequest, thePartitionId);
theResourceName, theRequest);
if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) {
return myCriteriaBuilder.and(xPredicate, yPredicate); return myCriteriaBuilder.and(xPredicate, yPredicate);
@ -650,14 +647,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return myCriteriaBuilder.or(xPredicate, yPredicate); return myCriteriaBuilder.or(xPredicate, yPredicate);
} }
} else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) { } else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) {
return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), theResourceName, theRequest, thePartitionId);
theResourceName, theRequest);
} }
return null; return null;
} }
private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter, private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter,
String theResourceName, RequestDetails theRequest) { String theResourceName, RequestDetails theRequest, PartitionId thePartitionId) {
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName()); RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName());
@ -670,7 +666,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
null, null,
null, null,
theFilter.getValue()); theFilter.getValue());
return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequest); return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), thePartitionId);
} else { } else {
throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search"); throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search");
} }
@ -706,7 +702,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null; String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null;
String value = theFilter.getValue(); String value = theFilter.getValue();
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value); ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest); return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, thePartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) { } else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation()); return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation());
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) { } else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
@ -794,7 +790,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
continue; continue;
} }
Predicate predicate = null; Predicate predicate;
if ((operation == null) || if ((operation == null) ||
(operation == SearchFilterParser.CompareOperation.eq)) { (operation == SearchFilterParser.CompareOperation.eq)) {
predicate = myQueryRoot.get("myLanguage").as(String.class).in(values); predicate = myQueryRoot.get("myLanguage").as(String.class).in(values);
@ -819,6 +815,9 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} }
private Predicate addPredicateSource(List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { private Predicate addPredicateSource(List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
// Required for now
assert theOperation == SearchFilterParser.CompareOperation.eq;
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) { if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled"); String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled");
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
@ -848,7 +847,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return retVal; return retVal;
} }
private void addPredicateHas(String theResourceType, List<List<IQueryParameterType>> theHasParameters, RequestDetails theRequest) { private void addPredicateHas(String theResourceType, List<List<IQueryParameterType>> theHasParameters, RequestDetails theRequest,PartitionId thePartitionId) {
for (List<? extends IQueryParameterType> nextOrList : theHasParameters) { for (List<? extends IQueryParameterType> nextOrList : theHasParameters) {
@ -878,8 +877,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw new InvalidRequestException("Invalid resource type: " + targetResourceType); throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
} }
assert parameterName != null;
//Ensure that the name of the search param //Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val) // (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type. // exists on the target resource type.
@ -909,11 +906,11 @@ class PredicateBuilderReference extends BasePredicateBuilder {
String chainedPartOfParameter = getChainedPart(parameterName); String chainedPartOfParameter = getChainedPart(parameterName);
orValues.stream() orValues.stream()
.filter(qp -> qp instanceof ReferenceParam) .filter(qp -> qp instanceof ReferenceParam)
.map(qp -> (ReferenceParam)qp) .map(qp -> (ReferenceParam) qp)
.forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter))); .forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter)));
} }
Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest); Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest, thePartitionId);
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT); Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join); Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join);

View File

@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -58,9 +58,9 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
} }
@Nullable @Nullable
Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation); Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, thePartitionId);
if (nextPredicate != null) { if (nextPredicate != null) {
myQueryRoot.addPredicate(nextPredicate); myQueryRoot.addPredicate(nextPredicate);
@ -71,7 +71,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
} }
@Nullable @Nullable
private Predicate createPredicate(Root<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation) { private Predicate createPredicate(Root<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, PartitionId thePartitionId) {
Predicate nextPredicate = null; Predicate nextPredicate = null;
Set<ResourcePersistentId> allOrPids = null; Set<ResourcePersistentId> allOrPids = null;
@ -89,7 +89,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
if (isNotBlank(value)) { if (isNotBlank(value)) {
haveValue = true; haveValue = true;
try { try {
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theResourceName, valueAsId.getIdPart()); ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(thePartitionId, theResourceName, valueAsId.getIdPart());
orPids.add(pid); orPids.add(pid);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest // This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
@ -55,16 +56,22 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
public Predicate addPredicate(String theResourceName, public Predicate addPredicate(String theResourceName,
String theParamName, String theParamName,
List<? extends IQueryParameterType> theList, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) { SearchFilterParser.CompareOperation operation,
PartitionId thePartitionId) {
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createJoin(SearchBuilderJoinEnum.URI, theParamName); Join<ResourceTable, ResourceIndexedSearchParamUri> join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
if (theList.get(0).getMissing() != null) { if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join, thePartitionId);
return null; return null;
} }
List<Predicate> codePredicates = new ArrayList<>(); List<Predicate> codePredicates = new ArrayList<>();
if (thePartitionId != null) {
Predicate partitionPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
codePredicates.add(partitionPredicate);
}
for (IQueryParameterType nextOr : theList) { for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof UriParam) { if (nextOr instanceof UriParam) {

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId); ResourceTable entity = readEntityLatestVersion(theId, theRequest);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId); ResourceTable entity = readEntityLatestVersion(theId, theRequest);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -37,7 +37,7 @@ public interface ISearchCoordinatorSvc {
List<ResourcePersistentId> getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails); List<ResourcePersistentId> getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails);
IBundleProvider registerSearch(IFhirResourceDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails); IBundleProvider registerSearch(IFhirResourceDao<?> theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails);
/** /**
* Fetch the total number of search results for the given currently executing search, if one is currently executing and * Fetch the total number of search results for the given currently executing search, if one is currently executing and

View File

@ -25,11 +25,13 @@ import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude; import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
@ -246,7 +248,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
String resourceType = search.getResourceType(); String resourceType = search.getResourceType();
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search")); SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType); IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType);
SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails); PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, resourceType);
SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails, partitionId);
myIdToSearchTask.put(search.getUuid(), task); myIdToSearchTask.put(search.getUuid(), task);
myExecutor.submit(task); myExecutor.submit(task);
} }
@ -284,8 +287,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
theRetVal.setInterceptorBroadcaster(myInterceptorBroadcaster); theRetVal.setInterceptorBroadcaster(myInterceptorBroadcaster);
} }
@Autowired
private RequestPartitionHelperService myRequestPartitionHelperService;
@Override @Override
public IBundleProvider registerSearch(final IFhirResourceDao theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails) { public IBundleProvider registerSearch(final IFhirResourceDao<?> theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails) {
final String searchUuid = UUID.randomUUID().toString(); final String searchUuid = UUID.randomUUID().toString();
ourLog.debug("Registering new search {}", searchUuid); ourLog.debug("Registering new search {}", searchUuid);
@ -298,7 +304,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) { if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) {
ourLog.debug("Search {} is loading in synchronous mode", searchUuid); ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
return executeQuery(theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo); return executeQuery(theResourceType, theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo);
} }
/* /*
@ -369,7 +375,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails); .addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params); JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, theRequestDetails); PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theResourceType);
SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, theRequestDetails, partitionId);
myIdToSearchTask.put(search.getUuid(), task); myIdToSearchTask.put(search.getUuid(), task);
myExecutor.submit(task); myExecutor.submit(task);
@ -380,7 +388,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return retVal; return retVal;
} }
@org.jetbrains.annotations.Nullable @Nullable
private IBundleProvider findCachedQuery(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString) { private IBundleProvider findCachedQuery(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString) {
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
PersistedJpaBundleProvider foundSearchProvider = txTemplate.execute(t -> { PersistedJpaBundleProvider foundSearchProvider = txTemplate.execute(t -> {
@ -416,11 +424,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return retVal; return retVal;
}); });
if (foundSearchProvider != null) { // May be null
return foundSearchProvider; return foundSearchProvider;
} }
return null;
}
@Nullable @Nullable
private Search findSearchToUseOrNull(String theQueryString, String theResourceType) { private Search findSearchToUseOrNull(String theQueryString, String theResourceType) {
@ -441,7 +447,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return searchToUse; return searchToUse;
} }
private IBundleProvider executeQuery(SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo) { private IBundleProvider executeQuery(String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo) {
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequestDetails, theSearchUuid); SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequestDetails, theSearchUuid);
searchRuntimeDetails.setLoadSynchronous(true); searchRuntimeDetails.setLoadSynchronous(true);
@ -453,7 +459,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
// Load the results synchronously // Load the results synchronously
final List<ResourcePersistentId> pids = new ArrayList<>(); final List<ResourcePersistentId> pids = new ArrayList<>();
try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails)) { PartitionId partitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theResourceType);
try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails, partitionId)) {
while (resultIter.hasNext()) { while (resultIter.hasNext()) {
pids.add(resultIter.next()); pids.add(resultIter.next());
if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) { if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) {
@ -595,6 +602,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
private final CountDownLatch myCompletionLatch; private final CountDownLatch myCompletionLatch;
private final ArrayList<ResourcePersistentId> myUnsyncedPids = new ArrayList<>(); private final ArrayList<ResourcePersistentId> myUnsyncedPids = new ArrayList<>();
private final RequestDetails myRequest; private final RequestDetails myRequest;
private final PartitionId myPartitionId;
private Search mySearch; private Search mySearch;
private boolean myAbortRequested; private boolean myAbortRequested;
private int myCountSavedTotal = 0; private int myCountSavedTotal = 0;
@ -605,10 +613,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
private Integer myMaxResultsToFetch; private Integer myMaxResultsToFetch;
private SearchRuntimeDetails mySearchRuntimeDetails; private SearchRuntimeDetails mySearchRuntimeDetails;
private Transaction myParentTransaction; private Transaction myParentTransaction;
/** /**
* Constructor * Constructor
*/ */
protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) { protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, PartitionId thePartitionId) {
mySearch = theSearch; mySearch = theSearch;
myCallingDao = theCallingDao; myCallingDao = theCallingDao;
myParams = theParams; myParams = theParams;
@ -616,6 +625,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
myCompletionLatch = new CountDownLatch(1); myCompletionLatch = new CountDownLatch(1);
mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid()); mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid());
mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext())); mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext()));
myPartitionId = thePartitionId;
myRequest = theRequest; myRequest = theRequest;
myParentTransaction = ElasticApm.currentTransaction(); myParentTransaction = ElasticApm.currentTransaction();
} }
@ -977,7 +987,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
if (wantCount) { if (wantCount) {
ourLog.trace("Performing count"); ourLog.trace("Performing count");
ISearchBuilder sb = newSearchBuilder(); ISearchBuilder sb = newSearchBuilder();
Iterator<Long> countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest); Iterator<Long> countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest, myPartitionId);
Long count = countIterator.next(); Long count = countIterator.next();
ourLog.trace("Got count {}", count); ourLog.trace("Got count {}", count);
@ -1053,7 +1063,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
/* /*
* Construct the SQL query we'll be sending to the database * Construct the SQL query we'll be sending to the database
*/ */
try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest)) { try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest, myPartitionId)) {
assert (resultIterator != null); assert (resultIterator != null);
/* /*
@ -1105,8 +1115,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
public class SearchContinuationTask extends SearchTask { public class SearchContinuationTask extends SearchTask {
public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) { public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, PartitionId thePartitionId) {
super(theSearch, theCallingDao, theParams, theResourceType, theRequest); super(theSearch, theCallingDao, theParams, theResourceType, theRequest, thePartitionId);
} }
@Override @Override

View File

@ -66,7 +66,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
present.setResource(theResource); present.setResource(theResource);
present.setParamName(paramName); present.setParamName(paramName);
present.setPresent(next.getValue()); present.setPresent(next.getValue());
present.setTenantId(theResource.getTenantId()); present.setPartitionId(theResource.getPartitionId());
present.calculateHashes(); present.calculateHashes();
newHashToPresence.put(present.getHashPresence(), present); newHashToPresence.put(present.getHashPresence(), present);

View File

@ -117,7 +117,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
@Override @Override
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) { public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart()); return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
} }
@Transactional @Transactional
@ -291,7 +291,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource); IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(csId.getResourceType(), csId.getIdPart()); ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(null, csId.getResourceType(), csId.getIdPart());
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong()); ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
@ -551,7 +551,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
} }
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) { private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart()); return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
} }
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) { private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {

View File

@ -280,6 +280,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Autowired @Autowired
protected IResourceTagDao myResourceTagDao; protected IResourceTagDao myResourceTagDao;
@Autowired @Autowired
protected IResourceHistoryTagDao myResourceHistoryTagDao;
@Autowired
protected ISearchCoordinatorSvc mySearchCoordinatorSvc; protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Autowired(required = false) @Autowired(required = false)
protected IFulltextSearchSvc mySearchDao; protected IFulltextSearchSvc mySearchDao;

View File

@ -1,334 +0,0 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.model.entity.TenantId;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.ServletException;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;
public class MultitenantR4Test extends BaseJpaR4SystemTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantR4Test.class);
private MyInterceptor myTenantInterceptor;
private LocalDate myTenantDate;
private int myTenantId;
@After
public void after() {
myDaoConfig.setMultiTenancyEnabled(new DaoConfig().isMultiTenancyEnabled());
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
myInterceptor = null;
}
@Override
@Before
public void before() throws ServletException {
super.before();
myDaoConfig.setMultiTenancyEnabled(true);
myDaoConfig.setUniqueIndexesEnabled(true);
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myTenantDate = LocalDate.of(2020, Month.JANUARY, 14);
myTenantId = 3;
}
@Test
public void testCreateResourceNoTenant() {
Patient p = new Patient();
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertNull(resourceTable.getTenantId());
});
}
@Test
public void testCreateResourceWithTenant() {
createUniqueCompositeSp();
createRequestId();
addCreateTenant(myTenantId, myTenantDate);
addCreateTenant(myTenantId, myTenantDate);
Organization org = new Organization();
org.setName("org");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
p.getManagingOrganization().setReferenceElement(orgId);
Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate());
// HFJ_RES_VER
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
assertEquals(myTenantId, version.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, version.getTenantId().getTenantDate());
// HFJ_RES_VER_PROV
assertNotNull(version.getProvenance());
assertEquals(myTenantId, version.getProvenance().getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, version.getProvenance().getTenantId().getTenantDate());
// HFJ_SPIDX_STRING
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
assertEquals(10, strings.size());
assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate());
// HFJ_SPIDX_DATE
List<ResourceIndexedSearchParamDate> dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * ")));
assertEquals(2, dates.size());
assertEquals(myTenantId, dates.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, dates.get(0).getTenantId().getTenantDate());
assertEquals(myTenantId, dates.get(1).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, dates.get(1).getTenantId().getTenantDate());
// HFJ_RES_LINK
List<ResourceLink> resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
assertEquals(1, resourceLinks.size());
assertEquals(myTenantId, resourceLinks.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resourceLinks.get(0).getTenantId().getTenantDate());
// HFJ_RES_PARAM_PRESENT
List<SearchParamPresent> presents = mySearchParamPresentDao.findAllForResource(resourceTable);
assertEquals(myTenantId, presents.size());
assertEquals(myTenantId, presents.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, presents.get(0).getTenantId().getTenantDate());
// HFJ_IDX_CMP_STRING_UNIQ
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
assertEquals(1, uniques.size());
assertEquals(myTenantId, uniques.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, uniques.get(0).getTenantId().getTenantDate());
});
}
@Test
public void testCreateWithForcedId() {
addCreateTenant(myTenantId, myTenantDate);
addCreateTenant(myTenantId, myTenantDate);
Organization org = new Organization();
org.setId("org");
org.setName("org");
IIdType orgId = myOrganizationDao.update(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.setId("pat");
p.getManagingOrganization().setReferenceElement(orgId);
myPatientDao.update(p, mySrd);
runInTransaction(() -> {
// HFJ_FORCED_ID
List<ForcedId> forcedIds = myForcedIdDao.findAll();
assertEquals(2, forcedIds.size());
assertEquals(myTenantId, forcedIds.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, forcedIds.get(0).getTenantId().getTenantDate());
assertEquals(myTenantId, forcedIds.get(1).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, forcedIds.get(1).getTenantId().getTenantDate());
});
}
@Test
public void testUpdateResourceWithTenant() {
createRequestId();
addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
// Create a resource
Patient p = new Patient();
p.setActive(true);
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate());
});
// Update that resource
p = new Patient();
p.setId("Patient/" + patientId);
p.setActive(false);
myPatientDao.update(p, mySrd);
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myTenantId, resourceTable.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resourceTable.getTenantId().getTenantDate());
// HFJ_RES_VER
int version = 2;
ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version);
assertEquals(myTenantId, resVer.getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resVer.getTenantId().getTenantDate());
// HFJ_RES_VER_PROV
assertNotNull(resVer.getProvenance());
assertNotNull(resVer.getTenantId());
assertEquals(myTenantId, resVer.getProvenance().getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, resVer.getProvenance().getTenantId().getTenantDate());
// HFJ_SPIDX_STRING
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
assertEquals(10, strings.size());
assertEquals(myTenantId, strings.get(0).getTenantId().getTenantId().intValue());
assertEquals(myTenantDate, strings.get(0).getTenantId().getTenantDate());
});
}
@Test
public void testReadAcrossTenants() {
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(1, withActiveTrue());
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId2, gotId2);
}
@Test
public void testSearchAcrossAllTenants() {
}
private void createUniqueCompositeSp() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate");
sp.setType(Enumerations.SearchParamType.DATE);
sp.setCode("birthdate");
sp.setExpression("Patient.birthDate");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate-unique");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-birthdate");
sp.addExtension()
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegistry.forceRefresh();
}
private void addCreateTenant(int theTenantId, LocalDate theTenantDate) {
if (myTenantInterceptor == null) {
myTenantInterceptor = new MyInterceptor();
myInterceptorRegistry.registerInterceptor(myTenantInterceptor);
}
myTenantInterceptor.addCreateTenant(new TenantId(theTenantId, theTenantDate));
}
public IIdType createPatient(int theTenantId, Consumer<Patient>... theModifiers) {
addCreateTenant(theTenantId, null);
Patient p = new Patient();
for (Consumer<Patient> next : theModifiers) {
next.accept(p);
}
return myPatientDao.create(p).getId().toUnqualifiedVersionless();
}
public void createRequestId() {
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
}
private Consumer<Patient> withActiveTrue() {
return t->t.setActive(true);
}
@Interceptor
public static class MyInterceptor {
private final List<TenantId> myCreateTenantIds = new ArrayList<>();
public void addCreateTenant(TenantId theTenantId) {
Validate.notNull(theTenantId);
myCreateTenantIds.add(theTenantId);
}
@Hook(Pointcut.STORAGE_TENANT_IDENTIFY_CREATE)
public TenantId tenantIdentifyCreate() {
TenantId retVal = myCreateTenantIds.remove(0);
ourLog.info("Returning tenant ID: {}", retVal);
return retVal;
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,499 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.ServletException;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
public class PartitioningR4Test extends BaseJpaR4SystemTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PartitioningR4Test.class);
private MyInterceptor myTenantInterceptor;
private LocalDate myTenantDate;
private int myPartitionId;
@After
public void after() {
myTenantInterceptor.assertNoRemainingIds();
myDaoConfig.setPartitioningEnabled(new DaoConfig().isPartitioningEnabled());
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor);
myInterceptor = null;
}
@Override
@Before
public void before() throws ServletException {
super.before();
myDaoConfig.setPartitioningEnabled(true);
myDaoConfig.setUniqueIndexesEnabled(true);
myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myTenantDate = LocalDate.of(2020, Month.JANUARY, 14);
myPartitionId = 3;
myTenantInterceptor = new MyInterceptor();
}
@Test
public void testCreateResourceNoTenant() {
Patient p = new Patient();
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertNull(resourceTable.getPartitionId());
});
}
@Test
public void testCreateResourceWithTenant() {
createUniqueCompositeSp();
createRequestId();
addCreateTenant(myPartitionId, myTenantDate);
addCreateTenant(myPartitionId, myTenantDate);
Organization org = new Organization();
org.setName("org");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
p.getManagingOrganization().setReferenceElement(orgId);
Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
// HFJ_RES_TAG
List<ResourceTag> tags = myResourceTagDao.findAll();
assertEquals(1, tags.size());
assertEquals(myPartitionId, tags.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, tags.get(0).getPartitionId().getPartitionDate());
// HFJ_RES_VER
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L);
assertEquals(myPartitionId, version.getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, version.getPartitionId().getPartitionDate());
// HFJ_HISTORY_TAG
List<ResourceHistoryTag> historyTags = myResourceHistoryTagDao.findAll();
assertEquals(1, historyTags.size());
assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, historyTags.get(0).getPartitionId().getPartitionDate());
// HFJ_RES_VER_PROV
assertNotNull(version.getProvenance());
assertEquals(myPartitionId, version.getProvenance().getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, version.getProvenance().getPartitionId().getPartitionDate());
// HFJ_SPIDX_STRING
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
assertEquals(10, strings.size());
assertEquals(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, strings.get(0).getPartitionId().getPartitionDate());
// HFJ_SPIDX_DATE
List<ResourceIndexedSearchParamDate> dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * ")));
assertEquals(2, dates.size());
assertEquals(myPartitionId, dates.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, dates.get(0).getPartitionId().getPartitionDate());
assertEquals(myPartitionId, dates.get(1).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, dates.get(1).getPartitionId().getPartitionDate());
// HFJ_RES_LINK
List<ResourceLink> resourceLinks = myResourceLinkDao.findAllForResourceId(patientId);
assertEquals(1, resourceLinks.size());
assertEquals(myPartitionId, resourceLinks.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, resourceLinks.get(0).getPartitionId().getPartitionDate());
// HFJ_RES_PARAM_PRESENT
List<SearchParamPresent> presents = mySearchParamPresentDao.findAllForResource(resourceTable);
assertEquals(myPartitionId, presents.size());
assertEquals(myPartitionId, presents.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, presents.get(0).getPartitionId().getPartitionDate());
// HFJ_IDX_CMP_STRING_UNIQ
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceId(patientId);
assertEquals(1, uniques.size());
assertEquals(myPartitionId, uniques.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, uniques.get(0).getPartitionId().getPartitionDate());
});
}
@Test
public void testCreateWithForcedId() {
addCreateTenant(myPartitionId, myTenantDate);
addCreateTenant(myPartitionId, myTenantDate);
Organization org = new Organization();
org.setId("org");
org.setName("org");
IIdType orgId = myOrganizationDao.update(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.setId("pat");
p.getManagingOrganization().setReferenceElement(orgId);
myPatientDao.update(p, mySrd);
runInTransaction(() -> {
// HFJ_FORCED_ID
List<ForcedId> forcedIds = myForcedIdDao.findAll();
assertEquals(2, forcedIds.size());
assertEquals(myPartitionId, forcedIds.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, forcedIds.get(0).getPartitionId().getPartitionDate());
assertEquals(myPartitionId, forcedIds.get(1).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, forcedIds.get(1).getPartitionId().getPartitionDate());
});
}
@Test
public void testUpdateResourceWithTenant() {
createRequestId();
addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
addCreateTenant(3, LocalDate.of(2020, Month.JANUARY, 14));
// Create a resource
Patient p = new Patient();
p.getMeta().addTag("http://system", "code", "diisplay");
p.setActive(true);
Long patientId = myPatientDao.create(p).getId().getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
});
// Update that resource
p = new Patient();
p.setId("Patient/" + patientId);
p.setActive(false);
myPatientDao.update(p, mySrd);
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, resourceTable.getPartitionId().getPartitionDate());
// HFJ_RES_VER
int version = 2;
ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version);
assertEquals(myPartitionId, resVer.getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, resVer.getPartitionId().getPartitionDate());
// HFJ_HISTORY_TAG
List<ResourceHistoryTag> historyTags = myResourceHistoryTagDao.findAll();
assertEquals(2, historyTags.size());
assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, historyTags.get(0).getPartitionId().getPartitionDate());
assertEquals(myPartitionId, historyTags.get(1).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, historyTags.get(1).getPartitionId().getPartitionDate());
// HFJ_RES_VER_PROV
assertNotNull(resVer.getProvenance());
assertNotNull(resVer.getPartitionId());
assertEquals(myPartitionId, resVer.getProvenance().getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, resVer.getProvenance().getPartitionId().getPartitionDate());
// HFJ_SPIDX_STRING
List<ResourceIndexedSearchParamString> strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId);
ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * ")));
assertEquals(10, strings.size());
assertEquals(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue());
assertEquals(myTenantDate, strings.get(0).getPartitionId().getPartitionDate());
});
}
@Test
public void testReadAcrossTenants() {
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(2, withActiveTrue());
addReadTenant(null);
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
addReadTenant(null);
IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId2, gotId2);
}
@Test
public void testReadSpecificTenant_PidId() {
IIdType patientIdNull = createPatient(null, withActiveTrue());
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(2, withActiveTrue());
// Read in correct tenant
addReadTenant(1);
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
// Read in null tenant
addReadTenant(1);
try {
myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
} catch (ResourceNotFoundException e) {
assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
}
// Read in wrong tenant
addReadTenant(1);
try {
myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
} catch (ResourceNotFoundException e) {
assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
}
}
@Test
public void testReadSpecificTenant_ForcedId() {
IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("NULL"));
IIdType patientId1 = createPatient(1, withActiveTrue(), withId("ONE"));
IIdType patientId2 = createPatient(2, withActiveTrue(), withId("TWO"));
// Read in correct tenant
addReadTenant(1);
IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
// Read in null tenant
addReadTenant(1);
try {
myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
} catch (ResourceNotFoundException e) {
assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
}
// Read in wrong tenant
addReadTenant(1);
try {
myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless();
fail();
} catch (ResourceNotFoundException e) {
assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known"));
}
}
@Test
public void testSearch_NoParams_SearchAllTenants() {
IIdType patientIdNull = createPatient(null, withActiveTrue());
IIdType patientId1 = createPatient(1, withActiveTrue());
IIdType patientId2 = createPatient(2, withActiveTrue());
addReadTenant(null);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
@Test
public void testSearch_NoParams_SearchOneTenant() {
createPatient(null, withActiveTrue());
IIdType patientId1 = createPatient(1, withActiveTrue());
createPatient(2, withActiveTrue());
addReadTenant(1);
myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
private void createUniqueCompositeSp() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate");
sp.setType(Enumerations.SearchParamType.DATE);
sp.setCode("birthdate");
sp.setExpression("Patient.birthDate");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate-unique");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-birthdate");
sp.addExtension()
.setUrl(SearchParamConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegistry.forceRefresh();
}
private void addCreateTenant(int thePartitionId, LocalDate theTenantDate) {
registerInterceptorIfNeeded();
myTenantInterceptor.addCreateTenant(new PartitionId(thePartitionId, theTenantDate));
}
private void addReadTenant(Integer thePartitionId) {
registerInterceptorIfNeeded();
PartitionId partitionId = null;
if (thePartitionId != null) {
partitionId = new PartitionId(thePartitionId, null);
}
myTenantInterceptor.addReadTenant(partitionId);
}
private void registerInterceptorIfNeeded() {
if (!myInterceptorRegistry.getAllRegisteredInterceptors().contains(myTenantInterceptor)) {
myInterceptorRegistry.registerInterceptor(myTenantInterceptor);
}
}
public IIdType createPatient(Integer thePartitionId, Consumer<Patient>... theModifiers) {
if (thePartitionId != null) {
addCreateTenant(thePartitionId, null);
}
Patient p = new Patient();
for (Consumer<Patient> next : theModifiers) {
next.accept(p);
}
return myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
}
public void createRequestId() {
when(mySrd.getRequestId()).thenReturn("REQUEST_ID");
}
private Consumer<Patient> withActiveTrue() {
return t -> t.setActive(true);
}
private Consumer<Patient> withId(String theId) {
return t -> {
assertThat(theId, matchesPattern("[a-zA-Z0-9]+"));
t.setId("Patient/" + theId);
};
}
@Interceptor
public static class MyInterceptor {
private final List<PartitionId> myCreatePartitionIds = new ArrayList<>();
private final List<PartitionId> myReadPartitionIds = new ArrayList<>();
public void addCreateTenant(PartitionId thePartitionId) {
Validate.notNull(thePartitionId);
myCreatePartitionIds.add(thePartitionId);
}
public void addReadTenant(PartitionId thePartitionId) {
myReadPartitionIds.add(thePartitionId);
}
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
public PartitionId tenantIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
assertNotNull(theResource);
PartitionId retVal = myCreatePartitionIds.remove(0);
ourLog.info("Returning partition for create: {}", retVal);
return retVal;
}
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
public PartitionId tenantIdentifyRead(ServletRequestDetails theRequestDetails) {
PartitionId retVal = myReadPartitionIds.remove(0);
ourLog.info("Returning partition for read: {}", retVal);
return retVal;
}
public void assertNoRemainingIds() {
assertEquals(0, myCreatePartitionIds.size());
assertEquals(0, myReadPartitionIds.size());
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.PartitionId;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
@ -147,7 +148,7 @@ public class SearchCoordinatorSvcImplTest {
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300); IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300);
when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null);
assertNotNull(result.getUuid()); assertNotNull(result.getUuid());
@ -180,7 +181,7 @@ public class SearchCoordinatorSvcImplTest {
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
SlowIterator iter = new SlowIterator(pids.iterator(), 1); SlowIterator iter = new SlowIterator(pids.iterator(), 1);
when(mySearchBuilder.createQuery(any(), any(), any())).thenReturn(iter); when(mySearchBuilder.createQuery(any(), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
when(mySearchCacheSvc.save(any())).thenAnswer(t -> { when(mySearchCacheSvc.save(any())).thenAnswer(t -> {
@ -262,7 +263,7 @@ public class SearchCoordinatorSvcImplTest {
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
SlowIterator iter = new SlowIterator(pids.iterator(), 2); SlowIterator iter = new SlowIterator(pids.iterator(), 2);
when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
@ -286,7 +287,7 @@ public class SearchCoordinatorSvcImplTest {
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
SlowIterator iter = new SlowIterator(pids.iterator(), 500); SlowIterator iter = new SlowIterator(pids.iterator(), 500);
when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null);
assertNotNull(result.getUuid()); assertNotNull(result.getUuid());
@ -331,7 +332,7 @@ public class SearchCoordinatorSvcImplTest {
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
IResultIterator iter = new SlowIterator(pids.iterator(), 2); IResultIterator iter = new SlowIterator(pids.iterator(), 2);
when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
when(mySearchCacheSvc.save(any())).thenAnswer(t ->{ when(mySearchCacheSvc.save(any())).thenAnswer(t ->{
ourLog.info("Saving search"); ourLog.info("Saving search");
return t.getArgument( 0, Search.class); return t.getArgument( 0, Search.class);
@ -379,7 +380,7 @@ public class SearchCoordinatorSvcImplTest {
List<ResourcePersistentId> pids = createPidSequence(100); List<ResourcePersistentId> pids = createPidSequence(100);
SlowIterator iter = new SlowIterator(pids.iterator(), 2); SlowIterator iter = new SlowIterator(pids.iterator(), 2);
when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
@ -462,7 +463,7 @@ public class SearchCoordinatorSvcImplTest {
params.add("name", new StringParam("ANAME")); params.add("name", new StringParam("ANAME"));
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(new ResultIterator(pids.iterator())); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(PartitionId.class))).thenReturn(new ResultIterator(pids.iterator()));
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
@ -483,7 +484,7 @@ public class SearchCoordinatorSvcImplTest {
params.add("name", new StringParam("ANAME")); params.add("name", new StringParam("ANAME"));
List<ResourcePersistentId> pids = createPidSequence(800); List<ResourcePersistentId> pids = createPidSequence(800);
when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class))).thenReturn(new ResultIterator(pids.iterator())); when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class), nullable(PartitionId.class))).thenReturn(new ResultIterator(pids.iterator()));
pids = createPidSequence(110); pids = createPidSequence(110);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(eq(pids), any(Collection.class), any(List.class), anyBoolean(), nullable(RequestDetails.class)); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(eq(pids), any(Collection.class), any(List.class), anyBoolean(), nullable(RequestDetails.class));

View File

@ -59,7 +59,14 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
private Date myUpdated; private Date myUpdated;
@Embedded @Embedded
private TenantId myTenantId; private PartitionId myPartitionId;
/**
* This is here to support queries only, do not set this field directly
*/
@SuppressWarnings("unused")
@Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
private Integer myPartitionIdValue;
/** /**
* This is stored as an optimization to avoid neeind to query for this * This is stored as an optimization to avoid neeind to query for this
@ -68,12 +75,12 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi
@Transient @Transient
private transient String myTransientForcedId; private transient String myTransientForcedId;
public TenantId getTenantId() { public PartitionId getPartitionId() {
return myTenantId; return myPartitionId;
} }
public void setTenantId(TenantId theTenantId) { public void setPartitionId(PartitionId thePartitionId) {
myTenantId = theTenantId; myPartitionId = thePartitionId;
} }
public String getTransientForcedId() { public String getTransientForcedId() {

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L% * #L%
*/ */
import javax.persistence.Column;
import javax.persistence.Embedded; import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import java.io.Serializable; import java.io.Serializable;
@ -28,14 +29,21 @@ import java.io.Serializable;
public abstract class BaseResourceIndex implements Serializable { public abstract class BaseResourceIndex implements Serializable {
@Embedded @Embedded
private TenantId myTenantId; private PartitionId myPartitionId;
public TenantId getTenantId() { /**
return myTenantId; * This is here to support queries only, do not set this field directly
*/
@SuppressWarnings("unused")
@Column(name = PartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
private Integer myPartitionIdValue;
public PartitionId getPartitionId() {
return myPartitionId;
} }
public void setTenantId(TenantId theTenantId) { public void setPartitionId(PartitionId thePartitionId) {
myTenantId = theTenantId; myPartitionId = thePartitionId;
} }
public abstract Long getId(); public abstract Long getId();

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
*/ */
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
@ -38,6 +39,9 @@ public class BaseTag implements Serializable {
@Column(name = "TAG_ID", insertable = false, updatable = false) @Column(name = "TAG_ID", insertable = false, updatable = false)
private Long myTagId; private Long myTagId;
@Embedded
private PartitionId myPartitionId;
public Long getTagId() { public Long getTagId() {
return myTagId; return myTagId;
} }
@ -50,4 +54,12 @@ public class BaseTag implements Serializable {
myTag = theTag; myTag = theTag;
} }
public PartitionId getPartitionId() {
return myPartitionId;
}
public void setPartitionId(PartitionId thePartitionId) {
myPartitionId = thePartitionId;
}
} }

View File

@ -62,8 +62,9 @@ public class ForcedId {
@ColumnDefault("''") @ColumnDefault("''")
@Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true) @Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = true)
private String myResourceType; private String myResourceType;
@Embedded @Embedded
private TenantId myTenantId; private PartitionId myPartitionId;
/** /**
* Constructor * Constructor
@ -96,11 +97,11 @@ public class ForcedId {
return myId; return myId;
} }
public TenantId getTenantId() { public PartitionId getPartitionId() {
return myTenantId; return myPartitionId;
} }
public void setTenantId(TenantId theTenantId) { public void setPartitionId(PartitionId thePartitionId) {
myTenantId = theTenantId; myPartitionId = thePartitionId;
} }
} }

View File

@ -0,0 +1,66 @@
package ca.uhn.fhir.jpa.model.entity;
import javax.annotation.Nonnull;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.time.LocalDate;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
@Embeddable
public class PartitionId implements Cloneable {
static final String PARTITION_ID = "PARTITION_ID";
@Column(name = PARTITION_ID, nullable = true, insertable = true, updatable = false)
private Integer myPartitionId;
@Column(name = "PARTITION_DATE", nullable = true, insertable = true, updatable = false)
private LocalDate myPartitionDate;
/**
* Constructor
*/
public PartitionId() {
super();
}
/**
* Constructor
*/
public PartitionId(int thePartitionId, LocalDate thePartitionDate) {
setPartitionId(thePartitionId);
setPartitionDate(thePartitionDate);
}
@Nonnull
public Integer getPartitionId() {
return myPartitionId;
}
public PartitionId setPartitionId(@Nonnull Integer thePartitionId) {
myPartitionId = thePartitionId;
return this;
}
public LocalDate getPartitionDate() {
return myPartitionDate;
}
public PartitionId setPartitionDate(LocalDate thePartitionDate) {
myPartitionDate = thePartitionDate;
return this;
}
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "MethodDoesntCallSuperMethod"})
@Override
protected PartitionId clone() {
return new PartitionId()
.setPartitionId(getPartitionId())
.setPartitionDate(getPartitionDate());
}
@Override
public String toString() {
return defaultIfNull(myPartitionId, "null").toString();
}
}

View File

@ -49,7 +49,7 @@ public class ResourceHistoryProvenanceEntity {
private String myRequestId; private String myRequestId;
// FIXME: make sure this gets populated // FIXME: make sure this gets populated
@Embedded @Embedded
private TenantId myTenantId; private PartitionId myPartitionId;
/** /**
* Constructor * Constructor
@ -86,11 +86,11 @@ public class ResourceHistoryProvenanceEntity {
return myId; return myId;
} }
public TenantId getTenantId() { public PartitionId getPartitionId() {
return myTenantId; return myPartitionId;
} }
public void setTenantId(TenantId theTenantId) { public void setPartitionId(PartitionId thePartitionId) {
myTenantId = theTenantId; myPartitionId = thePartitionId;
} }
} }

View File

@ -94,7 +94,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
} }
public void addTag(ResourceTag theTag) { public void addTag(ResourceTag theTag) {
ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag()); ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag(), getPartitionId());
tag.setResourceType(theTag.getResourceType()); tag.setResourceType(theTag.getResourceType());
getTags().add(tag); getTags().add(tag);
} }
@ -106,7 +106,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
return next; return next;
} }
} }
ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag); ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag, getPartitionId());
getTags().add(historyTag); getTags().add(historyTag);
return historyTag; return historyTag;
} }

View File

@ -75,11 +75,12 @@ public class ResourceHistoryTag extends BaseTag implements Serializable {
} }
public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag) { public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag, PartitionId thePartitionId) {
setTag(theTag); setTag(theTag);
setResource(theResourceHistoryTable); setResource(theResourceHistoryTable);
setResourceId(theResourceHistoryTable.getResourceId()); setResourceId(theResourceHistoryTable.getResourceId());
setResourceType(theResourceHistoryTable.getResourceType()); setResourceType(theResourceHistoryTable.getResourceType());
setPartitionId(thePartitionId);
} }
public ResourceHistoryTable getResourceHistory() { public ResourceHistoryTable getResourceHistory() {

View File

@ -49,7 +49,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH) @Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
private String myIndexString; private String myIndexString;
@Embedded @Embedded
private TenantId myTenantId; private PartitionId myPartitionId;
/** /**
* Constructor * Constructor
@ -64,15 +64,15 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) { public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) {
setResource(theResource); setResource(theResource);
setIndexString(theIndexString); setIndexString(theIndexString);
setTenantId(theResource.getTenantId()); setPartitionId(theResource.getPartitionId());
} }
public TenantId getTenantId() { public PartitionId getPartitionId() {
return myTenantId; return myPartitionId;
} }
public void setTenantId(TenantId theTenantId) { public void setPartitionId(PartitionId thePartitionId) {
myTenantId = theTenantId; myPartitionId = thePartitionId;
} }
@Override @Override
@ -127,7 +127,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
.append("id", myId) .append("id", myId)
.append("resourceId", myResourceId) .append("resourceId", myResourceId)
.append("indexString", myIndexString) .append("indexString", myIndexString)
.append("tenant", myTenantId) .append("tenant", myPartitionId)
.toString(); .toString();
} }
} }

View File

@ -242,7 +242,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
return next; return next;
} }
} }
ResourceTag tag = new ResourceTag(this, theTag); ResourceTag tag = new ResourceTag(this, theTag, getPartitionId());
getTags().add(tag); getTags().add(tag);
return tag; return tag;
} }
@ -556,7 +556,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
retVal.setDeleted(getDeleted()); retVal.setDeleted(getDeleted());
retVal.setResourceTable(this); retVal.setResourceTable(this);
retVal.setForcedId(getForcedId()); retVal.setForcedId(getForcedId());
retVal.setTenantId(getTenantId()); retVal.setPartitionId(getPartitionId());
retVal.getTags().clear(); retVal.getTags().clear();

View File

@ -51,21 +51,15 @@ public class ResourceTag extends BaseTag {
@Column(name = "RES_ID", insertable = false, updatable = false) @Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourceId; private Long myResourceId;
@Embedded
private TenantId myTenantId;
public ResourceTag() { public ResourceTag() {
} }
public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag) { public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag, PartitionId thePartitionId) {
setTag(theTag); setTag(theTag);
setResource(theResourceTable); setResource(theResourceTable);
setResourceId(theResourceTable.getId()); setResourceId(theResourceTable.getId());
setResourceType(theResourceTable.getResourceType()); setResourceType(theResourceTable.getResourceType());
} setPartitionId(thePartitionId);
public void setTenantId(TenantId theTenantId) {
myTenantId = theTenantId;
} }
public Long getResourceId() { public Long getResourceId() {

View File

@ -53,7 +53,7 @@ public class SearchParamPresent implements Serializable {
@Column(name = "HASH_PRESENCE") @Column(name = "HASH_PRESENCE")
private Long myHashPresence; private Long myHashPresence;
@Embedded @Embedded
private TenantId myTenantId; private PartitionId myPartitionId;
/** /**
* Constructor * Constructor
@ -112,16 +112,16 @@ public class SearchParamPresent implements Serializable {
b.append("resPid", myResource.getIdDt().toUnqualifiedVersionless().getValue()); b.append("resPid", myResource.getIdDt().toUnqualifiedVersionless().getValue());
b.append("paramName", myParamName); b.append("paramName", myParamName);
b.append("present", myPresent); b.append("present", myPresent);
b.append("tenant", myTenantId); b.append("tenant", myPartitionId);
return b.build(); return b.build();
} }
public TenantId getTenantId() { public PartitionId getPartitionId() {
return myTenantId; return myPartitionId;
} }
public void setTenantId(TenantId theTenantId) { public void setPartitionId(PartitionId thePartitionId) {
myTenantId = theTenantId; myPartitionId = thePartitionId;
} }
public static long calculateHashPresence(String theResourceType, String theParamName, Boolean thePresent) { public static long calculateHashPresence(String theResourceType, String theParamName, Boolean thePresent) {

View File

@ -1,61 +0,0 @@
package ca.uhn.fhir.jpa.model.entity;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.time.LocalDate;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
@Embeddable
public class TenantId implements Cloneable {
@Column(name = "TENANT_ID", nullable = true, insertable = true, updatable = false)
private Integer myTenantId;
@Column(name = "TENANT_DATE", nullable = true, insertable = true, updatable = false)
private LocalDate myTenantDate;
/**
* Constructor
*/
public TenantId() {
super();
}
/**
* Constructor
*/
public TenantId(int theTenantId, LocalDate theTenantDate) {
setTenantId(theTenantId);
setTenantDate(theTenantDate);
}
public Integer getTenantId() {
return myTenantId;
}
public TenantId setTenantId(Integer theTenantId) {
myTenantId = theTenantId;
return this;
}
public LocalDate getTenantDate() {
return myTenantDate;
}
public TenantId setTenantDate(LocalDate theTenantDate) {
myTenantDate = theTenantDate;
return this;
}
@Override
protected TenantId clone() {
return new TenantId()
.setTenantId(getTenantId())
.setTenantDate(getTenantDate());
}
@Override
public String toString() {
return defaultIfNull(myTenantId, "null").toString();
}
}

View File

@ -637,7 +637,7 @@
<!--<derby_version>10.15.1.3</derby_version>--> <!--<derby_version>10.15.1.3</derby_version>-->
<error_prone_annotations_version>2.3.4</error_prone_annotations_version> <error_prone_annotations_version>2.3.4</error_prone_annotations_version>
<error_prone_core_version>2.3.3</error_prone_core_version> <error_prone_core_version>2.3.3</error_prone_core_version>
<guava_version>28.0-jre</guava_version> <guava_version>28.2-jre</guava_version>
<gson_version>2.8.5</gson_version> <gson_version>2.8.5</gson_version>
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version> <jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
<jaxb_api_version>2.3.1</jaxb_api_version> <jaxb_api_version>2.3.1</jaxb_api_version>