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:
michaelabuckley 2023-10-20 12:14:31 -04:00 committed by GitHub
parent af1d46fb04
commit ee369269f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 281 additions and 233 deletions

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

@ -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 {

View File

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

View File

@ -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.

View File

@ -43,6 +43,6 @@ public class ResourceTableTest {
IdDt actual = t.getIdDt();
// Then
assertTrue(actual.equals(theExpected));
assertEquals(theExpected, actual.getValueAsString());
}
}

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -633,4 +633,8 @@ public class Builder {
return this;
}
}
public String getRelease() {
return myRelease;
}
}