Move forced-id to HFJ_RESOURCE - step 2 (#4803)
* First cut at forced-id step 2 Start using new fhir_id column in hfj_resource instead. * demote fixme for build * Start changelog * Merge cleanups * forced-id migration various fixme cleanups * checkstyle * fix bad conversions to computeIfAbsent * Ugh. Lame checkstyle. * Revert optimistic null assert * missed import. * Fixup broken tests * Fix invalid test * Add missing index annotation
This commit is contained in:
parent
af1d46fb04
commit
ee369269f8
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: change
|
||||
issue: 4803
|
||||
title: "Internal client-assigned ids are now resolved within the HFJ_RESOURCE table, avoiding a join to HFJ_FORCED_ID"
|
|
@ -21,10 +21,10 @@ package ca.uhn.fhir.jpa.config;
|
|||
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceSearchUrlEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.system.HapiSystemProperties;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
@ -43,7 +44,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HapiFhirHibernateJpaDialect.class);
|
||||
private HapiLocalizer myLocalizer;
|
||||
static final String RESOURCE_VERSION_CONSTRAINT_FAILURE = "resourceVersionConstraintFailure";
|
||||
private final HapiLocalizer myLocalizer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -61,7 +63,7 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DataAccessException convertHibernateAccessException(HibernateException theException) {
|
||||
protected DataAccessException convertHibernateAccessException(@Nonnull HibernateException theException) {
|
||||
return convertHibernateAccessException(theException, null);
|
||||
}
|
||||
|
||||
|
@ -86,22 +88,17 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
|
|||
if (isNotBlank(constraintName)) {
|
||||
constraintName = constraintName.toUpperCase();
|
||||
if (constraintName.contains(ResourceHistoryTable.IDX_RESVER_ID_VER)) {
|
||||
throw new ResourceVersionConflictException(Msg.code(823)
|
||||
+ messageToPrepend
|
||||
+ myLocalizer.getMessage(
|
||||
HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure"));
|
||||
throw new ResourceVersionConflictException(
|
||||
Msg.code(823) + makeErrorMessage(messageToPrepend, RESOURCE_VERSION_CONSTRAINT_FAILURE));
|
||||
}
|
||||
if (constraintName.contains(ResourceIndexedComboStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) {
|
||||
throw new ResourceVersionConflictException(Msg.code(824)
|
||||
+ messageToPrepend
|
||||
+ myLocalizer.getMessage(
|
||||
HapiFhirHibernateJpaDialect.class,
|
||||
"resourceIndexedCompositeStringUniqueConstraintFailure"));
|
||||
+ makeErrorMessage(
|
||||
messageToPrepend, "resourceIndexedCompositeStringUniqueConstraintFailure"));
|
||||
}
|
||||
if (constraintName.contains(ForcedId.IDX_FORCEDID_TYPE_FID)) {
|
||||
throw new ResourceVersionConflictException(Msg.code(825)
|
||||
+ messageToPrepend
|
||||
+ myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "forcedIdConstraintFailure"));
|
||||
if (constraintName.contains(ResourceTable.IDX_RES_FHIR_ID)) {
|
||||
throw new ResourceVersionConflictException(
|
||||
Msg.code(825) + makeErrorMessage(messageToPrepend, "forcedIdConstraintFailure"));
|
||||
}
|
||||
if (constraintName.contains(ResourceSearchUrlEntity.RES_SEARCH_URL_COLUMN_NAME)) {
|
||||
throw super.convertHibernateAccessException(theException);
|
||||
|
@ -124,21 +121,24 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
|
|||
* StressTestR4Test method testMultiThreadedUpdateSameResourceInTransaction()
|
||||
*/
|
||||
if (theException instanceof org.hibernate.StaleStateException) {
|
||||
String msg = messageToPrepend
|
||||
+ myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure");
|
||||
throw new ResourceVersionConflictException(Msg.code(826) + msg);
|
||||
throw new ResourceVersionConflictException(
|
||||
Msg.code(826) + makeErrorMessage(messageToPrepend, RESOURCE_VERSION_CONSTRAINT_FAILURE));
|
||||
}
|
||||
if (theException instanceof org.hibernate.PessimisticLockException) {
|
||||
PessimisticLockException ex = (PessimisticLockException) theException;
|
||||
String sql = defaultString(ex.getSQL()).toUpperCase();
|
||||
if (sql.contains(ResourceHistoryTable.HFJ_RES_VER)) {
|
||||
String msg = messageToPrepend
|
||||
+ myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure");
|
||||
throw new ResourceVersionConflictException(Msg.code(827) + msg);
|
||||
throw new ResourceVersionConflictException(
|
||||
Msg.code(827) + makeErrorMessage(messageToPrepend, RESOURCE_VERSION_CONSTRAINT_FAILURE));
|
||||
}
|
||||
}
|
||||
|
||||
DataAccessException retVal = super.convertHibernateAccessException(theException);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String makeErrorMessage(String thePrefix, String theMessageKey) {
|
||||
return thePrefix + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, theMessageKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -644,6 +644,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
private void createForcedIdIfNeeded(
|
||||
ResourceTable theEntity, String theResourceId, boolean theCreateForPureNumericIds) {
|
||||
// TODO MB delete this in step 3
|
||||
if (isNotBlank(theResourceId) && theEntity.getForcedId() == null) {
|
||||
if (theCreateForPureNumericIds || !IdHelperService.isValidPid(theResourceId)) {
|
||||
ForcedId forcedId = new ForcedId();
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.data.custom.IForcedIdQueries;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
|
@ -27,17 +26,16 @@ import org.springframework.data.jpa.repository.Query;
|
|||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Legacy forced_id implementation.
|
||||
*
|
||||
* @deprecated we now have a fhir_id column directly on HFJ_RESOURCE.
|
||||
* No runtime code should query this table except for deletions by PK.
|
||||
* To be deleted in 2024 (zero-downtime).
|
||||
*/
|
||||
@Deprecated(since = "6.7")
|
||||
@Repository
|
||||
public interface IForcedIdDao extends JpaRepository<ForcedId, Long>, IHapiFhirJpaRepository, IForcedIdQueries {
|
||||
|
||||
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid IN (:resource_pids)")
|
||||
List<ForcedId> findAllByResourcePid(@Param("resource_pids") List<Long> theResourcePids);
|
||||
|
||||
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
|
||||
Optional<ForcedId> findByResourcePid(@Param("resource_pid") Long theResourcePid);
|
||||
public interface IForcedIdDao extends JpaRepository<ForcedId, Long>, IHapiFhirJpaRepository {
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.data.custom.IForcedIdQueries;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
|
@ -36,7 +37,8 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
|
||||
@Transactional(propagation = Propagation.MANDATORY)
|
||||
public interface IResourceTableDao extends JpaRepository<ResourceTable, Long>, IHapiFhirJpaRepository {
|
||||
public interface IResourceTableDao
|
||||
extends JpaRepository<ResourceTable, Long>, IHapiFhirJpaRepository, IForcedIdQueries {
|
||||
|
||||
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myDeleted IS NOT NULL")
|
||||
Slice<Long> findIdsOfDeletedResources(Pageable thePageable);
|
||||
|
|
|
@ -27,9 +27,12 @@ import javax.persistence.EntityManager;
|
|||
import javax.persistence.PersistenceContext;
|
||||
|
||||
@Component
|
||||
// Don't change the name of this class. Spring Data requires the name to match.
|
||||
// See https://stackoverflow.com/questions/11880924/how-to-add-custom-method-to-spring-data-jpa
|
||||
public class IForcedIdDaoImpl implements IForcedIdQueries {
|
||||
/**
|
||||
* Custom query implementations.
|
||||
* Don't change the name of this class. Spring Data requires the name to match.
|
||||
* https://stackoverflow.com/questions/11880924/how-to-add-custom-method-to-spring-data-jpa
|
||||
*/
|
||||
public class IResourceTableDaoImpl implements IForcedIdQueries {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager myEntityManager;
|
||||
|
@ -51,11 +54,9 @@ public class IForcedIdDaoImpl implements IForcedIdQueries {
|
|||
*/
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoType(
|
||||
String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted) {
|
||||
String query = "" + "SELECT "
|
||||
+ " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted "
|
||||
+ "FROM ForcedId f "
|
||||
+ "JOIN ResourceTable t ON t.myId = f.myResourcePid "
|
||||
+ "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )";
|
||||
String query = "SELECT t.myResourceType, t.id, t.myFhirId, t.myDeleted "
|
||||
+ "FROM ResourceTable t "
|
||||
+ "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id )";
|
||||
|
||||
if (theExcludeDeleted) {
|
||||
query += " AND t.myDeleted IS NULL";
|
||||
|
@ -78,11 +79,9 @@ public class IForcedIdDaoImpl implements IForcedIdQueries {
|
|||
Collection<String> theForcedIds,
|
||||
Collection<Integer> thePartitionId,
|
||||
boolean theExcludeDeleted) {
|
||||
String query = "" + "SELECT "
|
||||
+ " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted "
|
||||
+ "FROM ForcedId f "
|
||||
+ "JOIN ResourceTable t ON t.myId = f.myResourcePid "
|
||||
+ "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IN ( :partition_id )";
|
||||
String query = "SELECT t.myResourceType, t.id, t.myFhirId, t.myDeleted "
|
||||
+ "FROM ResourceTable t "
|
||||
+ "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND t.myPartitionIdValue IN ( :partition_id )";
|
||||
|
||||
if (theExcludeDeleted) {
|
||||
query += " AND t.myDeleted IS NULL";
|
||||
|
@ -103,11 +102,9 @@ public class IForcedIdDaoImpl implements IForcedIdQueries {
|
|||
*/
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted) {
|
||||
String query = "" + "SELECT "
|
||||
+ " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted "
|
||||
+ "FROM ForcedId f "
|
||||
+ "JOIN ResourceTable t ON t.myId = f.myResourcePid "
|
||||
+ "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IS NULL";
|
||||
String query = "SELECT t.myResourceType, t.id, t.myFhirId, t.myDeleted "
|
||||
+ "FROM ResourceTable t "
|
||||
+ "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND t.myPartitionIdValue IS NULL";
|
||||
|
||||
if (theExcludeDeleted) {
|
||||
query += " AND t.myDeleted IS NULL";
|
||||
|
@ -130,11 +127,9 @@ public class IForcedIdDaoImpl implements IForcedIdQueries {
|
|||
Collection<String> theForcedIds,
|
||||
List<Integer> thePartitionIdsWithoutDefault,
|
||||
boolean theExcludeDeleted) {
|
||||
String query = "" + "SELECT "
|
||||
+ " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted "
|
||||
+ "FROM ForcedId f "
|
||||
+ "JOIN ResourceTable t ON t.myId = f.myResourcePid "
|
||||
+ "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND (f.myPartitionIdValue IS NULL OR f.myPartitionIdValue IN ( :partition_id ))";
|
||||
String query = "SELECT t.myResourceType, t.id, t.myFhirId, t.myDeleted "
|
||||
+ "FROM ResourceTable t "
|
||||
+ "WHERE t.myResourceType = :resource_type AND t.myFhirId IN ( :forced_id ) AND (t.myPartitionIdValue IS NULL OR t.myPartitionIdValue IN ( :partition_id ))";
|
||||
|
||||
if (theExcludeDeleted) {
|
||||
query += " AND t.myDeleted IS NULL";
|
|
@ -25,20 +25,17 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.model.PersistentIdToForcedIdMap;
|
||||
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.JpaResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.jpa.util.QueryChunker;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.storage.BaseResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
|
@ -104,9 +101,6 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0];
|
||||
public static final String RESOURCE_PID = "RESOURCE_PID";
|
||||
|
||||
@Autowired
|
||||
protected IForcedIdDao myForcedIdDao;
|
||||
|
||||
@Autowired
|
||||
protected IResourceTableDao myResourceTableDao;
|
||||
|
||||
|
@ -270,6 +264,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
*
|
||||
* @throws ResourceNotFoundException If the ID can not be found
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public JpaPid resolveResourcePersistentIds(
|
||||
@Nonnull RequestPartitionId theRequestPartitionId,
|
||||
|
@ -327,7 +322,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
@Override
|
||||
@Nonnull
|
||||
public List<JpaPid> resolveResourcePersistentIdsWithCache(
|
||||
RequestPartitionId theRequestPartitionId, List<IIdType> theIds, boolean theOnlyForcedIds) {
|
||||
@Nonnull RequestPartitionId theRequestPartitionId, List<IIdType> theIds, boolean theOnlyForcedIds) {
|
||||
assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive();
|
||||
|
||||
List<JpaPid> retVal = new ArrayList<>(theIds.size());
|
||||
|
@ -375,20 +370,19 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
RequestPartitionId theRequestPartitionId, List<IIdType> theIds, List<JpaPid> theOutputListToPopulate) {
|
||||
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<Tuple> criteriaQuery = cb.createTupleQuery();
|
||||
Root<ForcedId> from = criteriaQuery.from(ForcedId.class);
|
||||
Root<ResourceTable> from = criteriaQuery.from(ResourceTable.class);
|
||||
|
||||
/*
|
||||
* We don't currently have an index that satisfies these three columns, but the
|
||||
* index IDX_FORCEDID_TYPE_FID does include myResourceType and myForcedId
|
||||
* so we're at least minimizing the amount of data we fetch. A largescale test
|
||||
* on Postgres does confirm that this lookup does use the index and is pretty
|
||||
* performant.
|
||||
* IDX_RES_FHIR_ID covers these columns, but RES_ID is only INCLUDEd.
|
||||
* Only PG, and MSSql support INCLUDE COLUMNS.
|
||||
* @see AddIndexTask.generateSql
|
||||
*/
|
||||
criteriaQuery.multiselect(
|
||||
from.get("myResourcePid").as(Long.class),
|
||||
from.get("myId").as(Long.class),
|
||||
from.get("myResourceType").as(String.class),
|
||||
from.get("myForcedId").as(String.class));
|
||||
from.get("myFhirId").as(String.class));
|
||||
|
||||
// one create one clause per id.
|
||||
List<Predicate> predicates = new ArrayList<>(theIds.size());
|
||||
for (IIdType next : theIds) {
|
||||
|
||||
|
@ -399,12 +393,13 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
andPredicates.add(typeCriteria);
|
||||
}
|
||||
|
||||
Predicate idCriteria = cb.equal(from.get("myForcedId").as(String.class), next.getIdPart());
|
||||
Predicate idCriteria = cb.equal(from.get("myFhirId").as(String.class), next.getIdPart());
|
||||
andPredicates.add(idCriteria);
|
||||
getOptionalPartitionPredicate(theRequestPartitionId, cb, from).ifPresent(andPredicates::add);
|
||||
predicates.add(cb.and(andPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
|
||||
}
|
||||
|
||||
// join all the clauses as OR
|
||||
criteriaQuery.where(cb.or(predicates.toArray(EMPTY_PREDICATE_ARRAY)));
|
||||
|
||||
TypedQuery<Tuple> query = myEntityManager.createQuery(criteriaQuery);
|
||||
|
@ -432,7 +427,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
* 3. If the requested partition search is not all partition, return the request partition as predicate.
|
||||
*/
|
||||
private Optional<Predicate> getOptionalPartitionPredicate(
|
||||
RequestPartitionId theRequestPartitionId, CriteriaBuilder cb, Root<ForcedId> from) {
|
||||
RequestPartitionId theRequestPartitionId, CriteriaBuilder cb, Root<ResourceTable> from) {
|
||||
if (myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()) {
|
||||
return Optional.empty();
|
||||
} else if (theRequestPartitionId.isDefaultPartition() && myPartitionSettings.getDefaultPartitionId() == null) {
|
||||
|
@ -488,7 +483,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
return myMemoryCacheService.get(
|
||||
MemoryCacheService.CacheEnum.PID_TO_FORCED_ID,
|
||||
theId.getId(),
|
||||
pid -> myForcedIdDao.findByResourcePid(pid).map(ForcedId::asTypedFhirResourceId));
|
||||
pid -> myResourceTableDao.findById(pid).map(ResourceTable::asTypedFhirResourceId));
|
||||
}
|
||||
|
||||
private ListMultimap<String, String> organizeIdsByResourceType(Collection<IIdType> theIds) {
|
||||
|
@ -538,37 +533,35 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
for (Iterator<String> forcedIdIterator = nextIds.iterator(); forcedIdIterator.hasNext(); ) {
|
||||
String nextForcedId = forcedIdIterator.next();
|
||||
String nextKey = nextResourceType + "/" + nextForcedId;
|
||||
IResourceLookup cachedLookup =
|
||||
IResourceLookup<JpaPid> cachedLookup =
|
||||
myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP, nextKey);
|
||||
if (cachedLookup != null) {
|
||||
forcedIdIterator.remove();
|
||||
if (!retVal.containsKey(nextForcedId)) {
|
||||
retVal.put(nextForcedId, new ArrayList<>());
|
||||
}
|
||||
retVal.get(nextForcedId).add(cachedLookup);
|
||||
retVal.computeIfAbsent(nextForcedId, id -> new ArrayList<>())
|
||||
.add(cachedLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIds.size() > 0) {
|
||||
if (!nextIds.isEmpty()) {
|
||||
Collection<Object[]> views;
|
||||
assert isNotBlank(nextResourceType);
|
||||
|
||||
if (requestPartitionId.isAllPartitions()) {
|
||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(
|
||||
views = myResourceTableDao.findAndResolveByForcedIdWithNoType(
|
||||
nextResourceType, nextIds, theExcludeDeleted);
|
||||
} else {
|
||||
if (requestPartitionId.isDefaultPartition()) {
|
||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
views = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
nextResourceType, nextIds, theExcludeDeleted);
|
||||
} else if (requestPartitionId.hasDefaultPartitionId()) {
|
||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
views = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
nextResourceType,
|
||||
nextIds,
|
||||
requestPartitionId.getPartitionIdsWithoutDefault(),
|
||||
theExcludeDeleted);
|
||||
} else {
|
||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
views = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
nextResourceType, nextIds, requestPartitionId.getPartitionIds(), theExcludeDeleted);
|
||||
}
|
||||
}
|
||||
|
@ -580,10 +573,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
Date deletedAt = (Date) next[3];
|
||||
|
||||
JpaResourceLookup lookup = new JpaResourceLookup(resourceType, resourcePid, deletedAt);
|
||||
if (!retVal.containsKey(forcedId)) {
|
||||
retVal.put(forcedId, new ArrayList<>());
|
||||
}
|
||||
retVal.get(forcedId).add(lookup);
|
||||
retVal.computeIfAbsent(forcedId, id -> new ArrayList<>()).add(lookup);
|
||||
|
||||
if (!myStorageSettings.isDeleteEnabled()) {
|
||||
String key = resourceType + "/" + forcedId;
|
||||
|
@ -616,19 +606,16 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
for (Iterator<Long> forcedIdIterator = thePidsToResolve.iterator(); forcedIdIterator.hasNext(); ) {
|
||||
Long nextPid = forcedIdIterator.next();
|
||||
String nextKey = Long.toString(nextPid);
|
||||
IResourceLookup cachedLookup =
|
||||
IResourceLookup<JpaPid> cachedLookup =
|
||||
myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP, nextKey);
|
||||
if (cachedLookup != null) {
|
||||
forcedIdIterator.remove();
|
||||
if (!theTargets.containsKey(nextKey)) {
|
||||
theTargets.put(nextKey, new ArrayList<>());
|
||||
}
|
||||
theTargets.get(nextKey).add(cachedLookup);
|
||||
theTargets.computeIfAbsent(nextKey, id -> new ArrayList<>()).add(cachedLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thePidsToResolve.size() > 0) {
|
||||
if (!thePidsToResolve.isEmpty()) {
|
||||
Collection<Object[]> lookup;
|
||||
if (theRequestPartitionId.isAllPartitions()) {
|
||||
lookup = myResourceTableDao.findLookupFieldsByResourcePid(thePidsToResolve);
|
||||
|
@ -661,7 +648,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PersistentIdToForcedIdMap translatePidsToForcedIds(Set<JpaPid> theResourceIds) {
|
||||
public PersistentIdToForcedIdMap<JpaPid> translatePidsToForcedIds(Set<JpaPid> theResourceIds) {
|
||||
assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive();
|
||||
Set<Long> thePids = theResourceIds.stream().map(JpaPid::getId).collect(Collectors.toSet());
|
||||
Map<Long, Optional<String>> retVal = new HashMap<>(
|
||||
|
@ -671,11 +658,11 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
thePids.stream().filter(t -> !retVal.containsKey(t)).collect(Collectors.toList());
|
||||
|
||||
new QueryChunker<Long>().chunk(remainingPids, t -> {
|
||||
List<ForcedId> forcedIds = myForcedIdDao.findAllByResourcePid(t);
|
||||
List<ResourceTable> resourceEntities = myResourceTableDao.findAllById(t);
|
||||
|
||||
for (ForcedId forcedId : forcedIds) {
|
||||
Long nextResourcePid = forcedId.getResourceId();
|
||||
Optional<String> nextForcedId = Optional.of(forcedId.asTypedFhirResourceId());
|
||||
for (ResourceTable nextResourceEntity : resourceEntities) {
|
||||
Long nextResourcePid = nextResourceEntity.getId();
|
||||
Optional<String> nextForcedId = Optional.of(nextResourceEntity.asTypedFhirResourceId());
|
||||
retVal.put(nextResourcePid, nextForcedId);
|
||||
myMemoryCacheService.putAfterCommit(
|
||||
MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, nextResourcePid, nextForcedId);
|
||||
|
@ -688,11 +675,11 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
myMemoryCacheService.putAfterCommit(
|
||||
MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, nextResourcePid, Optional.empty());
|
||||
}
|
||||
Map<IResourcePersistentId, Optional<String>> convertRetVal = new HashMap<>();
|
||||
Map<JpaPid, Optional<String>> convertRetVal = new HashMap<>();
|
||||
retVal.forEach((k, v) -> {
|
||||
convertRetVal.put(JpaPid.fromId(k), v);
|
||||
});
|
||||
return new PersistentIdToForcedIdMap(convertRetVal);
|
||||
return new PersistentIdToForcedIdMap<>(convertRetVal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -799,7 +786,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
@Override
|
||||
public IIdType resourceIdFromPidOrThrowException(JpaPid thePid, String theResourceType) {
|
||||
Optional<ResourceTable> optionalResource = myResourceTableDao.findById(thePid.getId());
|
||||
if (!optionalResource.isPresent()) {
|
||||
if (optionalResource.isEmpty()) {
|
||||
throw new ResourceNotFoundException(Msg.code(2124) + "Requested resource not found");
|
||||
}
|
||||
return optionalResource.get().getIdDt().toVersionless();
|
||||
|
@ -820,7 +807,7 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
public Set<String> translatePidsToFhirResourceIds(Set<JpaPid> thePids) {
|
||||
assert TransactionSynchronizationManager.isSynchronizationActive();
|
||||
|
||||
PersistentIdToForcedIdMap pidToForcedIdMap = translatePidsToForcedIds(thePids);
|
||||
PersistentIdToForcedIdMap<JpaPid> pidToForcedIdMap = translatePidsToForcedIds(thePids);
|
||||
|
||||
return pidToForcedIdMap.getResolvedResourceIds();
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.entity;
|
||||
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Column;
|
||||
|
@ -60,7 +60,7 @@ public class BulkExportCollectionFileEntity implements Serializable {
|
|||
foreignKey = @ForeignKey(name = "FK_BLKEXCOLFILE_COLLECT"))
|
||||
private BulkExportCollectionEntity myCollection;
|
||||
|
||||
@Column(name = "RES_ID", length = ForcedId.MAX_FORCED_ID_LENGTH, nullable = false)
|
||||
@Column(name = "RES_ID", length = ResourceTable.MAX_FORCED_ID_LENGTH, nullable = false)
|
||||
private String myResourceId;
|
||||
|
||||
public void setCollection(BulkExportCollectionEntity theCollection) {
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
package ca.uhn.fhir.jpa.entity;
|
||||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
@ -46,13 +46,14 @@ import javax.persistence.TemporalType;
|
|||
@SuppressWarnings("SqlDialectInspection")
|
||||
@Entity
|
||||
@Immutable
|
||||
@Subselect("SELECT h.pid as pid, " + " r.res_id as res_id, "
|
||||
@Subselect("SELECT h.pid as pid, "
|
||||
+ " r.res_id as res_id, "
|
||||
+ " h.res_type as res_type, "
|
||||
+ " h.res_version as res_version, "
|
||||
+ // FHIR version
|
||||
" h.res_ver as res_ver, "
|
||||
+ // resource version
|
||||
" h.has_tags as has_tags, "
|
||||
// FHIR version
|
||||
+ " h.res_ver as res_ver, "
|
||||
// resource version
|
||||
+ " h.has_tags as has_tags, "
|
||||
+ " h.res_deleted_at as res_deleted_at, "
|
||||
+ " h.res_published as res_published, "
|
||||
+ " h.res_updated as res_updated, "
|
||||
|
@ -62,11 +63,10 @@ import javax.persistence.TemporalType;
|
|||
+ " h.PARTITION_ID as PARTITION_ID, "
|
||||
+ " p.SOURCE_URI as PROV_SOURCE_URI,"
|
||||
+ " p.REQUEST_ID as PROV_REQUEST_ID,"
|
||||
+ " f.forced_id as FORCED_PID "
|
||||
+ "FROM HFJ_RES_VER h "
|
||||
+ " LEFT OUTER JOIN HFJ_FORCED_ID f ON f.resource_pid = h.res_id "
|
||||
+ " LEFT OUTER JOIN HFJ_RES_VER_PROV p ON p.res_ver_pid = h.pid "
|
||||
+ " INNER JOIN HFJ_RESOURCE r ON r.res_id = h.res_id and r.res_ver = h.res_ver")
|
||||
+ " r.fhir_id as FHIR_ID "
|
||||
+ "FROM HFJ_RESOURCE r "
|
||||
+ " INNER JOIN HFJ_RES_VER h ON r.res_id = h.res_id and r.res_ver = h.res_ver"
|
||||
+ " LEFT OUTER JOIN HFJ_RES_VER_PROV p ON p.res_ver_pid = h.pid ")
|
||||
public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -120,13 +120,15 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
|||
@Enumerated(EnumType.STRING)
|
||||
private ResourceEncodingEnum myEncoding;
|
||||
|
||||
@Column(name = "FORCED_PID", length = ForcedId.MAX_FORCED_ID_LENGTH)
|
||||
private String myForcedPid;
|
||||
@Column(name = "FHIR_ID", length = ResourceTable.MAX_FORCED_ID_LENGTH)
|
||||
private String myFhirId;
|
||||
|
||||
@Column(name = "PARTITION_ID")
|
||||
private Integer myPartitionId;
|
||||
|
||||
public ResourceSearchView() {}
|
||||
public ResourceSearchView() {
|
||||
// public constructor for Hibernate
|
||||
}
|
||||
|
||||
public String getResourceTextVc() {
|
||||
return myResourceTextVc;
|
||||
|
@ -158,8 +160,8 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
|||
myFhirVersion = theFhirVersion;
|
||||
}
|
||||
|
||||
public String getForcedId() {
|
||||
return myForcedPid;
|
||||
public String getFhirId() {
|
||||
return myFhirId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -169,12 +171,11 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
|||
|
||||
@Override
|
||||
public IdDt getIdDt() {
|
||||
if (myForcedPid == null) {
|
||||
if (myFhirId == null) {
|
||||
Long id = myResourceId;
|
||||
return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
} else {
|
||||
return new IdDt(
|
||||
getResourceType() + '/' + getForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
return new IdDt(getResourceType() + '/' + getFhirId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
|
|||
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask;
|
||||
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateOrdinalDatesTask;
|
||||
import ca.uhn.fhir.jpa.migrate.taskdef.ColumnTypeEnum;
|
||||
import ca.uhn.fhir.jpa.migrate.taskdef.ForceIdMigrationCopyTask;
|
||||
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
|
||||
import ca.uhn.fhir.jpa.migrate.tasks.api.Builder;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
|
@ -110,6 +111,19 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
|||
.addIndex("20230911.2", "IDX_EMPi_TGT_MR_SCore")
|
||||
.unique(false)
|
||||
.withColumns("TARGET_TYPE", "MATCH_RESULT", "SCORE");
|
||||
|
||||
// Move forced_id constraints to hfj_resource and the new fhir_id column
|
||||
// Note: we leave the HFJ_FORCED_ID.IDX_FORCEDID_TYPE_FID index in place to support old writers for a while.
|
||||
version.addTask(new ForceIdMigrationCopyTask(version.getRelease(), "20231018.1"));
|
||||
|
||||
Builder.BuilderWithTableName hfjResource = version.onTable("HFJ_RESOURCE");
|
||||
hfjResource.modifyColumn("20231018.2", "FHIR_ID").nonNullable();
|
||||
hfjResource
|
||||
.addIndex("20231018.3", "IDX_RES_FHIR_ID")
|
||||
.unique(true)
|
||||
.online(true)
|
||||
.includeColumns("RES_ID")
|
||||
.withColumns("FHIR_ID", "RES_TYPE");
|
||||
}
|
||||
|
||||
protected void init680() {
|
||||
|
|
|
@ -23,7 +23,7 @@ import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
public class JpaResourceLookup implements IResourceLookup {
|
||||
public class JpaResourceLookup implements IResourceLookup<JpaPid> {
|
||||
private final String myResourceType;
|
||||
private final Long myResourcePid;
|
||||
private final Date myDeletedAt;
|
||||
|
|
|
@ -25,11 +25,9 @@ import ca.uhn.fhir.i18n.Msg;
|
|||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -38,8 +36,6 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
@ -50,9 +46,6 @@ public class ResourceReindexer {
|
|||
@Autowired
|
||||
private IResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
|
||||
@Autowired
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
|
||||
@Autowired
|
||||
private IResourceTableDao myResourceTableDao;
|
||||
|
||||
|
@ -75,21 +68,6 @@ public class ResourceReindexer {
|
|||
}
|
||||
|
||||
public void reindexResourceEntity(ResourceTable theResourceTable) {
|
||||
/*
|
||||
* This part is because from HAPI 1.5 - 1.6 we changed the format of forced ID to be "type/id" instead of just "id"
|
||||
*/
|
||||
ForcedId forcedId = theResourceTable.getForcedId();
|
||||
if (forcedId != null) {
|
||||
if (isBlank(forcedId.getResourceType())) {
|
||||
ourLog.info(
|
||||
"Updating resource {} forcedId type to {}",
|
||||
forcedId.getForcedId(),
|
||||
theResourceTable.getResourceType());
|
||||
forcedId.setResourceType(theResourceTable.getResourceType());
|
||||
myForcedIdDao.save(forcedId);
|
||||
}
|
||||
}
|
||||
|
||||
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceTable.getResourceType());
|
||||
long expectedVersion = theResourceTable.getVersion();
|
||||
IBaseResource resource = dao.readByPid(JpaPid.fromId(theResourceTable.getId()), true);
|
||||
|
|
|
@ -25,7 +25,6 @@ import ca.uhn.fhir.i18n.Msg;
|
|||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
|
||||
|
@ -111,9 +110,6 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc, IHasSc
|
|||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@Autowired
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
|
|||
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.sched.HapiJob;
|
||||
import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs;
|
||||
|
@ -2972,14 +2971,15 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
|
|||
public Optional<IBaseResource> readCodeSystemByForcedId(String theForcedId) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ResourceTable> resultList = (List<ResourceTable>) myEntityManager
|
||||
.createQuery("select f.myResource from ForcedId f "
|
||||
+ "where f.myResourceType = 'CodeSystem' and f.myForcedId = '" + theForcedId + "'")
|
||||
.createQuery("select r from ResourceTable r "
|
||||
+ "where r.myResourceType = 'CodeSystem' and r.myFhirId = :fhirId")
|
||||
.setParameter("fhirId", theForcedId)
|
||||
.getResultList();
|
||||
if (resultList.isEmpty()) return Optional.empty();
|
||||
|
||||
if (resultList.size() > 1)
|
||||
throw new NonUniqueResultException(Msg.code(911) + "More than one CodeSystem is pointed by forcedId: "
|
||||
+ theForcedId + ". Was constraint " + ForcedId.IDX_FORCEDID_TYPE_FID + " removed?");
|
||||
+ theForcedId + ". Was constraint " + ResourceTable.IDX_RES_FHIR_ID + " removed?");
|
||||
|
||||
IFhirResourceDao<CodeSystem> csDao = myDaoRegistry.getResourceDao("CodeSystem");
|
||||
IBaseResource cs = myJpaStorageResourceParser.toResource(resultList.get(0), false);
|
||||
|
|
|
@ -70,6 +70,7 @@ public abstract class BaseHasResource extends BasePartitionable
|
|||
* after an update
|
||||
*/
|
||||
@Transient
|
||||
// TODO MB forced_id delete this in step 3
|
||||
private transient String myTransientForcedId;
|
||||
|
||||
public String getTransientForcedId() {
|
||||
|
|
|
@ -64,7 +64,6 @@ import javax.persistence.UniqueConstraint;
|
|||
*/
|
||||
@Index(name = "IDX_FORCEID_FID", columnList = "FORCED_ID"),
|
||||
// @Index(name = "IDX_FORCEID_RESID", columnList = "RESOURCE_PID"),
|
||||
// TODO GGG potentiall add a type + res_id index here, specifically for deletion?
|
||||
})
|
||||
public class ForcedId extends BasePartitionable {
|
||||
|
||||
|
|
|
@ -277,12 +277,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
|||
if (getTransientForcedId() != null) {
|
||||
resourceIdPart = getTransientForcedId();
|
||||
} else {
|
||||
if (getResourceTable().getForcedId() == null) {
|
||||
Long id = getResourceId();
|
||||
resourceIdPart = id.toString();
|
||||
} else {
|
||||
resourceIdPart = getResourceTable().getForcedId().getForcedId();
|
||||
}
|
||||
resourceIdPart = getResourceTable().getFhirId();
|
||||
}
|
||||
return new IdDt(getResourceType() + '/' + resourceIdPart + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
}
|
||||
|
|
|
@ -73,20 +73,27 @@ import javax.persistence.PrePersist;
|
|||
import javax.persistence.PreUpdate;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
import javax.persistence.Version;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.entity.ResourceTable.IDX_RES_FHIR_ID;
|
||||
|
||||
@Indexed(routingBinder = @RoutingBinderRef(type = ResourceTableRoutingBinder.class))
|
||||
@Entity
|
||||
@Table(
|
||||
name = ResourceTable.HFJ_RESOURCE,
|
||||
uniqueConstraints = {},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = IDX_RES_FHIR_ID,
|
||||
columnNames = {"FHIR_ID", "RES_TYPE"})
|
||||
},
|
||||
indexes = {
|
||||
// Do not reuse previously used index name: IDX_INDEXSTATUS, IDX_RES_TYPE
|
||||
@Index(name = "IDX_RES_DATE", columnList = BaseHasResource.RES_UPDATED),
|
||||
@Index(
|
||||
name = "IDX_RES_TYPE_DEL_UPDATED",
|
||||
columnList = "RES_TYPE,RES_DELETED_AT,RES_UPDATED,PARTITION_ID,RES_ID"),
|
||||
@Index(name = "IDX_RES_RESID_UPDATED", columnList = "RES_ID,RES_UPDATED,PARTITION_ID"),
|
||||
@Index(name = "IDX_RES_RESID_UPDATED", columnList = "RES_ID, RES_UPDATED, PARTITION_ID")
|
||||
})
|
||||
@NamedEntityGraph(name = "Resource.noJoins")
|
||||
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource<JpaPid> {
|
||||
|
@ -95,6 +102,9 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
public static final String RES_TYPE = "RES_TYPE";
|
||||
private static final int MAX_LANGUAGE_LENGTH = 20;
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static final int MAX_FORCED_ID_LENGTH = 100;
|
||||
public static final String IDX_RES_FHIR_ID = "IDX_RES_FHIR_ID";
|
||||
|
||||
/**
|
||||
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
|
||||
* Note the extra config needed in HS6 for indexing transient props:
|
||||
|
@ -982,24 +992,18 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
}
|
||||
|
||||
private void populateId(IIdType retVal) {
|
||||
String resourceId;
|
||||
if (myFhirId != null && !myFhirId.isEmpty()) {
|
||||
retVal.setValue(getResourceType() + '/' + myFhirId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
resourceId = myFhirId;
|
||||
} else if (getTransientForcedId() != null) {
|
||||
// Avoid a join query if possible
|
||||
retVal.setValue(getResourceType()
|
||||
+ '/'
|
||||
+ getTransientForcedId()
|
||||
+ '/'
|
||||
+ Constants.PARAM_HISTORY
|
||||
+ '/'
|
||||
+ getVersion());
|
||||
} else if (getForcedId() == null) {
|
||||
Long id = this.getResourceId();
|
||||
retVal.setValue(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
resourceId = getTransientForcedId();
|
||||
} else if (myForcedId != null) {
|
||||
resourceId = myForcedId.getForcedId();
|
||||
} else {
|
||||
String forcedId = getForcedId().getForcedId();
|
||||
retVal.setValue(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
Long id = this.getResourceId();
|
||||
resourceId = Long.toString(id);
|
||||
}
|
||||
retVal.setValue(getResourceType() + '/' + resourceId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
}
|
||||
|
||||
public String getCreatedByMatchUrl() {
|
||||
|
@ -1047,6 +1051,10 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
myFhirId = theFhirId;
|
||||
}
|
||||
|
||||
public String asTypedFhirResourceId() {
|
||||
return getResourceType() + "/" + getFhirId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate myFhirId with server-assigned sequence id when no client-id provided.
|
||||
* We eat this complexity during insert to simplify query time with a uniform column.
|
||||
|
|
|
@ -43,6 +43,6 @@ public class ResourceTableTest {
|
|||
IdDt actual = t.getIdDt();
|
||||
|
||||
// Then
|
||||
assertTrue(actual.equals(theExpected));
|
||||
assertEquals(theExpected, actual.getValueAsString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.ClasspathUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hamcrest.core.StringContains;
|
||||
|
@ -104,7 +103,6 @@ import org.springframework.transaction.TransactionStatus;
|
|||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
@ -632,7 +630,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
if (readBackResource.getForcedId() != null) {
|
||||
assertEquals(myExpectedId, readBackResource.getForcedId().getForcedId(),
|
||||
"legacy join populated");
|
||||
assertEquals(myExpectedId, readBackView.getForcedId(),
|
||||
assertEquals(myExpectedId, readBackView.getFhirId(),
|
||||
"legacy join populated");
|
||||
} else {
|
||||
assertEquals(IdStrategyEnum.SEQUENTIAL_NUMERIC, theServerIdStrategy,
|
||||
|
|
|
@ -190,8 +190,8 @@ public class ConsumeFilesStepR4Test extends BasePartitioningR4Test {
|
|||
|
||||
assertEquals(1, myCaptureQueriesListener.logSelectQueries().size());
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false),
|
||||
either(containsString("forcedid0_.RESOURCE_TYPE='Patient' and forcedid0_.FORCED_ID='B' and (forcedid0_.PARTITION_ID is null) or forcedid0_.RESOURCE_TYPE='Patient' and forcedid0_.FORCED_ID='A' and (forcedid0_.PARTITION_ID is null)"))
|
||||
.or(containsString("forcedid0_.RESOURCE_TYPE='Patient' and forcedid0_.FORCED_ID='A' and (forcedid0_.PARTITION_ID is null) or forcedid0_.RESOURCE_TYPE='Patient' and forcedid0_.FORCED_ID='B' and (forcedid0_.PARTITION_ID is null)")));
|
||||
either(containsString("resourceta0_.RES_TYPE='Patient' and resourceta0_.FHIR_ID='B' and (resourceta0_.PARTITION_ID is null) or resourceta0_.RES_TYPE='Patient' and resourceta0_.FHIR_ID='A' and (resourceta0_.PARTITION_ID is null)"))
|
||||
.or(containsString("resourceta0_.RES_TYPE='Patient' and resourceta0_.FHIR_ID='A' and (resourceta0_.PARTITION_ID is null) or resourceta0_.RES_TYPE='Patient' and resourceta0_.FHIR_ID='B' and (resourceta0_.PARTITION_ID is null)")));
|
||||
assertEquals(52, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.dao.index;
|
|||
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
|
@ -41,7 +41,7 @@ public class IdHelperServiceTest {
|
|||
private JpaStorageSettings myStorageSettings;
|
||||
|
||||
@Mock
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
private IResourceTableDao myResourceTableDao;
|
||||
|
||||
@Mock
|
||||
private MemoryCacheService myMemoryCacheService;
|
||||
|
@ -100,7 +100,7 @@ public class IdHelperServiceTest {
|
|||
// when
|
||||
when(myStorageSettings.isDeleteEnabled())
|
||||
.thenReturn(true);
|
||||
when(myForcedIdDao.findAndResolveByForcedIdWithNoType(Mockito.anyString(),
|
||||
when(myResourceTableDao.findAndResolveByForcedIdWithNoType(Mockito.anyString(),
|
||||
Mockito.anyList(), Mockito.anyBoolean()))
|
||||
.thenReturn(Collections.singletonList(redView))
|
||||
.thenReturn(Collections.singletonList(blueView));
|
||||
|
@ -164,7 +164,7 @@ public class IdHelperServiceTest {
|
|||
|
||||
Collection<Object[]> testForcedIdViews = new ArrayList<>();
|
||||
testForcedIdViews.add(forcedIdView);
|
||||
when(myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(any(), any(), any(), anyBoolean())).thenReturn(testForcedIdViews);
|
||||
when(myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(any(), any(), any(), anyBoolean())).thenReturn(testForcedIdViews);
|
||||
|
||||
IResourceLookup<JpaPid> result = myHelperService.resolveResourceIdentity(partitionId, resourceType, resourceForcedId);
|
||||
assertEquals(forcedIdView[0], result.getResourceType());
|
||||
|
|
|
@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
|||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
|
@ -137,6 +138,7 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
protected void dropForcedIdUniqueConstraint() {
|
||||
runInTransaction(() -> {
|
||||
myEntityManager.createNativeQuery("alter table " + ForcedId.HFJ_FORCED_ID + " drop constraint " + ForcedId.IDX_FORCEDID_TYPE_FID).executeUpdate();
|
||||
myEntityManager.createNativeQuery("alter table " + ResourceTable.HFJ_RESOURCE + " drop constraint " + ResourceTable.IDX_RES_FHIR_ID).executeUpdate();
|
||||
});
|
||||
myHaveDroppedForcedIdUniqueConstraint = true;
|
||||
}
|
||||
|
|
|
@ -853,8 +853,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
|
||||
String selectQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "forcedid0_.resource_type='observation'"), selectQuery);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "forcedid0_.forced_id in ('a')"), selectQuery);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "resourceta0_.res_type='observation'"), selectQuery);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "resourceta0_.fhir_id in ('a')"), selectQuery);
|
||||
|
||||
selectQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "select t1.res_id from hfj_resource t1"), selectQuery);
|
||||
|
@ -895,8 +895,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
|
|||
|
||||
assertEquals(1, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
|
||||
String selectQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "forcedid0_.resource_type='observation'"), selectQuery);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "forcedid0_.forced_id in ('a')"), selectQuery);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "resourceta0_.res_type='observation'"), selectQuery);
|
||||
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "resourceta0_.fhir_id in ('a')"), selectQuery);
|
||||
}
|
||||
|
||||
// Search by ID where at least one ID is a numeric ID
|
||||
|
@ -1504,8 +1504,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
|
|||
|
||||
// Forced ID resolution
|
||||
resultingQueryNotFormatted = queries.get(0);
|
||||
assertThat(resultingQueryNotFormatted, containsString("RESOURCE_TYPE='Organization'"));
|
||||
assertThat(resultingQueryNotFormatted, containsString("forcedid0_.RESOURCE_TYPE='Organization' and forcedid0_.FORCED_ID='ORG1' or forcedid0_.RESOURCE_TYPE='Organization' and forcedid0_.FORCED_ID='ORG2'"));
|
||||
assertThat(resultingQueryNotFormatted, containsString("RES_TYPE='Organization'"));
|
||||
assertThat(resultingQueryNotFormatted, containsString("resourceta0_.RES_TYPE='Organization' and resourceta0_.FHIR_ID='ORG1' or resourceta0_.RES_TYPE='Organization' and resourceta0_.FHIR_ID='ORG2'"));
|
||||
|
||||
// The search itself
|
||||
resultingQueryNotFormatted = queries.get(1);
|
||||
|
|
|
@ -181,7 +181,7 @@ public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest {
|
|||
assertTrue(patient.getActive());
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(3, myCaptureQueriesListener.getSelectQueries().size());
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false), containsString("forcedid0_.PARTITION_ID in (?)"));
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false), containsString("resourceta0_.PARTITION_ID in (?)"));
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(1).getSql(false, false), containsString("where resourceta0_.PARTITION_ID=? and resourceta0_.RES_ID=?"));
|
||||
}
|
||||
|
||||
|
@ -229,7 +229,7 @@ public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest {
|
|||
assertEquals(1, outcome.size());
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(3, myCaptureQueriesListener.getSelectQueries().size());
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false), containsString("forcedid0_.PARTITION_ID in (?)"));
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false), containsString("resourceta0_.PARTITION_ID in (?)"));
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(1).getSql(false, false), containsString("t0.PARTITION_ID = ?"));
|
||||
}
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeIncludeDeleted(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeIncludeDeleted(
|
||||
"Patient", Arrays.asList(patientId)
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -189,7 +189,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoType(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoType(
|
||||
"Patient", Arrays.asList(patientId), true
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -200,7 +200,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeIncludeDeleted(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeIncludeDeleted(
|
||||
"Patient", Arrays.asList(patientId)
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -208,7 +208,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoType(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoType(
|
||||
"Patient", Arrays.asList(patientId), true
|
||||
);
|
||||
assertThat(forcedIds, hasSize(0));
|
||||
|
@ -223,7 +223,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
"Patient", Arrays.asList(patientId), false
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -231,7 +231,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
"Patient", Arrays.asList(patientId), true
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -242,7 +242,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
"Patient", Arrays.asList(patientId), false
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -250,7 +250,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
"Patient", Arrays.asList(patientId), true
|
||||
);
|
||||
assertEquals(0, forcedIds.size());
|
||||
|
@ -267,7 +267,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), false
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -275,7 +275,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), true
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -285,7 +285,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), false
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -293,7 +293,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), true
|
||||
);
|
||||
assertEquals(0, forcedIds.size());
|
||||
|
@ -310,7 +310,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), false
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -318,7 +318,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), true
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -328,7 +328,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and include deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), false
|
||||
);
|
||||
assertContainsSingleForcedId(forcedIds, patientId);
|
||||
|
@ -336,7 +336,7 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
|
|||
|
||||
// Search and filter deleted
|
||||
runInTransaction(() -> {
|
||||
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
Collection<Object[]> forcedIds = myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), true
|
||||
);
|
||||
assertEquals(0, forcedIds.size());
|
||||
|
|
|
@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
|||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
|
||||
|
@ -61,8 +60,6 @@ public class ResourceReindexingSvcImplTest {
|
|||
@Mock
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Mock
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
@Mock
|
||||
private IResourceReindexJobDao myReindexJobDao;
|
||||
@Mock
|
||||
private IResourceTableDao myResourceTableDao;
|
||||
|
|
|
@ -224,7 +224,7 @@ class ITermReadSvcTest {
|
|||
|
||||
@Test
|
||||
void getNoneReturnsOptionalEmpty() {
|
||||
when(myEntityManager.createQuery(anyString()).getResultList())
|
||||
when(myEntityManager.createQuery(anyString()).setParameter(anyString(), any()).getResultList())
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
Optional<IBaseResource> result = testedClass.readCodeSystemByForcedId("a-cs-id");
|
||||
|
@ -233,7 +233,7 @@ class ITermReadSvcTest {
|
|||
|
||||
@Test
|
||||
void getMultipleThrows() {
|
||||
when(myEntityManager.createQuery(anyString()).getResultList())
|
||||
when(myEntityManager.createQuery(anyString()).setParameter(anyString(), any()).getResultList())
|
||||
.thenReturn(Lists.newArrayList(resource1, resource2));
|
||||
|
||||
NonUniqueResultException thrown = assertThrows(
|
||||
|
@ -247,7 +247,7 @@ class ITermReadSvcTest {
|
|||
void getOneConvertToResource() {
|
||||
ReflectionTestUtils.setField(testedClass, "myDaoRegistry", myDaoRegistry);
|
||||
|
||||
when(myEntityManager.createQuery(anyString()).getResultList())
|
||||
when(myEntityManager.createQuery(anyString()).setParameter(anyString(), any()).getResultList())
|
||||
.thenReturn(Lists.newArrayList(resource1));
|
||||
when(myDaoRegistry.getResourceDao("CodeSystem")).thenReturn(myFhirResourceDao);
|
||||
when(myJpaStorageResourceParser.toResource(resource1, false)).thenReturn(myCodeSystemResource);
|
||||
|
|
|
@ -425,8 +425,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
|||
@Autowired
|
||||
protected IResourceHistoryProvenanceDao myResourceHistoryProvenanceDao;
|
||||
@Autowired
|
||||
protected IForcedIdDao myForcedIdDao;
|
||||
@Autowired
|
||||
@Qualifier("myCoverageDaoR4")
|
||||
protected IFhirResourceDao<Coverage> myCoverageDao;
|
||||
@Autowired
|
||||
|
|
|
@ -257,7 +257,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
@Autowired
|
||||
private IResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
@Autowired
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
protected IForcedIdDao myForcedIdDao;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
private final List<Object> myRegisteredInterceptors = new ArrayList<>(1);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceSearchUrlEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.PersistentObjectException;
|
||||
|
@ -36,7 +36,7 @@ public class HapiFhirHibernateJpaDialectTest {
|
|||
assertThat(outcome.getMessage(), containsString("this is a message"));
|
||||
|
||||
try {
|
||||
mySvc.convertHibernateAccessException(new ConstraintViolationException("this is a message", new SQLException("reason"), ForcedId.IDX_FORCEDID_TYPE_FID));
|
||||
mySvc.convertHibernateAccessException(new ConstraintViolationException("this is a message", new SQLException("reason"), ResourceTable.IDX_RES_FHIR_ID));
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
assertThat(e.getMessage(), containsString("The operation has failed with a client-assigned ID constraint failure"));
|
||||
|
@ -67,7 +67,7 @@ public class HapiFhirHibernateJpaDialectTest {
|
|||
assertEquals("FOO", outcome.getMessage());
|
||||
|
||||
try {
|
||||
PersistenceException exception = new PersistenceException("a message", new ConstraintViolationException("this is a message", new SQLException("reason"), ForcedId.IDX_FORCEDID_TYPE_FID));
|
||||
PersistenceException exception = new PersistenceException("a message", new ConstraintViolationException("this is a message", new SQLException("reason"), ResourceTable.IDX_RES_FHIR_ID));
|
||||
mySvc.translate(exception, "a message");
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
|||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -33,6 +34,7 @@ import java.util.List;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
@ -73,8 +75,8 @@ public class ResourceVersionSvcTest {
|
|||
*/
|
||||
private void mock_resolveResourcePersistentIdsWithCache_toReturnNothing() {
|
||||
CriteriaBuilder cb = Mockito.mock(CriteriaBuilder.class);
|
||||
CriteriaQuery<ForcedId> criteriaQuery = Mockito.mock(CriteriaQuery.class);
|
||||
Root<ForcedId> from = Mockito.mock(Root.class);
|
||||
CriteriaQuery<ResourceTable> criteriaQuery = Mockito.mock(CriteriaQuery.class);
|
||||
Root<ResourceTable> from = Mockito.mock(Root.class);
|
||||
Path path = Mockito.mock(Path.class);
|
||||
|
||||
TypedQuery<ForcedId> queryMock = Mockito.mock(TypedQuery.class);
|
||||
|
|
|
@ -50,6 +50,7 @@ public class DeleteConflictServiceTest {
|
|||
@Test
|
||||
public void noInterceptorTwoConflictsDoesntRetry() {
|
||||
ResourceTable entity = new ResourceTable();
|
||||
entity.setId(22L);
|
||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||
|
||||
List<ResourceLink> list = new ArrayList<>();
|
||||
|
|
|
@ -11,7 +11,6 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
|||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSet;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
|
||||
|
@ -599,12 +598,9 @@ public class LoincFullLoadR4SandboxIT extends BaseJpaTest {
|
|||
*/
|
||||
private void queryForSpecificValueSet() {
|
||||
runInTransaction(() -> {
|
||||
Query q = myEntityManager.createQuery("from ForcedId where myForcedId like 'LG8749-6%'");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ForcedId> fIds = (List<ForcedId>) q.getResultList();
|
||||
long res_id = fIds.stream().map(ForcedId::getId).sorted().findFirst().orElse(fail("ForcedId not found"));
|
||||
|
||||
Query q1 = myEntityManager.createQuery("from ResourceTable where id = " + res_id);
|
||||
Query q1 = myEntityManager
|
||||
.createQuery("from ResourceTable where myFhirId like :fhir_id")
|
||||
.setParameter("fhir_id", "LG8749-6%");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ResourceTable> vsList = (List<ResourceTable>) q1.getResultList();
|
||||
assertEquals(1, vsList.size());
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package ca.uhn.fhir.jpa.migrate.taskdef;
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ForceIdMigrationCopyTask extends BaseTask {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ForceIdMigrationCopyTask.class);
|
||||
|
||||
public ForceIdMigrationCopyTask(String theProductVersion, String theSchemaVersion) {
|
||||
super(theProductVersion, theSchemaVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute() throws SQLException {
|
||||
logInfo(ourLog, "Starting: migrate fhir_id from hfj_forced_id to hfj_resource.fhir_id");
|
||||
|
||||
JdbcTemplate jdbcTemplate = newJdbcTemplate();
|
||||
|
||||
Pair<Long, Long> range = jdbcTemplate.queryForObject(
|
||||
"select min(RES_ID), max(RES_ID) from HFJ_RESOURCE",
|
||||
(rs, rowNum) -> Pair.of(rs.getLong(1), rs.getLong(2)));
|
||||
|
||||
if (range == null || range.getLeft() == null) {
|
||||
logInfo(ourLog, "HFJ_RESOURCE is empty. No work to do.");
|
||||
return;
|
||||
}
|
||||
|
||||
// run update in batches.
|
||||
int rowsPerBlock = 50; // hfj_resource has roughly 50 rows per 8k block.
|
||||
int batchSize = rowsPerBlock * 2000; // a few thousand IOPS gives a batch size around a second.
|
||||
for (long batchStart = range.getLeft(); batchStart <= range.getRight(); batchStart = batchStart + batchSize) {
|
||||
long batchEnd = batchStart + batchSize;
|
||||
ourLog.info("Migrating client-assigned ids for pids: {}-{}", batchStart, batchEnd);
|
||||
|
||||
// This should be fast-ish since fhir_id isn't indexed yet,
|
||||
// and we're walking both hfj_resource and hfj_forced_id in insertion order.
|
||||
executeSql(
|
||||
"hfj_resource",
|
||||
"update hfj_resource " + "set fhir_id = coalesce( "
|
||||
+ // use first non-null value: forced_id if present, otherwise res_id
|
||||
" (select f.forced_id from hfj_forced_id f where f.resource_pid = res_id), "
|
||||
+ " cast(res_id as char(64)) "
|
||||
+ " ) "
|
||||
+ "where fhir_id is null "
|
||||
+ "and res_id >= ? and res_id < ?",
|
||||
batchStart,
|
||||
batchEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generateHashCode(HashCodeBuilder theBuilder) {
|
||||
// no-op - this is a singleton.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) {
|
||||
// no-op - this is a singleton.
|
||||
}
|
||||
}
|
|
@ -633,4 +633,8 @@ public class Builder {
|
|||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRelease() {
|
||||
return myRelease;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue