diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index bf9c63bfe69..38afad702c0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -58,6 +58,7 @@ public class RuntimeSearchParam { private final Map myUpliftRefchains = new HashMap<>(); private final ComboSearchParamType myComboSearchParamType; private final List myComponents; + private final IIdType myIdUnqualifiedVersionless; private IPhoneticEncoder myPhoneticEncoder; /** @@ -127,6 +128,7 @@ public class RuntimeSearchParam { super(); myId = theId; + myIdUnqualifiedVersionless = theId != null ? theId.toUnqualifiedVersionless() : null; myUri = theUri; myName = theName; myDescription = theDescription; @@ -214,6 +216,10 @@ public class RuntimeSearchParam { return myId; } + public IIdType getIdUnqualifiedVersionless() { + return myIdUnqualifiedVersionless; + } + public String getUri() { return myUri; } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-combo-param-tx-and-reindex-performance.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-combo-param-tx-and-reindex-performance.yaml new file mode 100644 index 00000000000..97e84de1946 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-combo-param-tx-and-reindex-performance.yaml @@ -0,0 +1,6 @@ +--- +type: perf +issue: 5885 +title: "When unique and non-unique combo parameters are in use on a server, FHIR Transaction and Reindex Job + performance has been optimized by pre-fetching all existing combo index rows for a large batch of resources + in a single database operation. This should yield a meaningful performance improvement on such systems." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-nonunique-combo-param-indexing.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-nonunique-combo-param-indexing.yaml new file mode 100644 index 00000000000..59bc0eb973a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-nonunique-combo-param-indexing.yaml @@ -0,0 +1,6 @@ +--- +type: perf +issue: 5885 +title: "Indexing for non-unique combo Search Parameters has been improved, + using a new hash-based index that should perform significantly better in + many circumstances." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-unique-combo-param-reindexing.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-unique-combo-param-reindexing.yaml new file mode 100644 index 00000000000..b3324117d6b --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5885-improve-unique-combo-param-reindexing.yaml @@ -0,0 +1,7 @@ +--- +type: perf +issue: 5885 +title: "Indexing for unique combo Search Parameters has been modified so that a hash + value is now stored. This hash value is not yet used in searching or enforcing uniqueness, + but will be in the future in order to reduce the space required to store the indexes and the + current size limitation on unique indexes." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md index 67f45c680a0..e3dd5cc4233 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md @@ -443,7 +443,9 @@ In some configurations, the partition ID is also factored into the hashes. Search Indexes -## Columns + + +## Common Search Index Columns The following columns are common to **all HFJ_SPIDX_xxx tables**. @@ -556,6 +558,8 @@ Sorting is done by the SP_VALUE_LOW column. ## Columns +Note: This table has the columns listed below, but it also has all common columns listed above in [Common Search Index Columns](#HFJ_SPIDX_common). + @@ -625,6 +629,8 @@ Range queries and sorting use the HASH_IDENTITY and SP_VALUE columns. ## Columns +Note: This table has the columns listed below, but it also has all common columns listed above in [Common Search Index Columns](#HFJ_SPIDX_common). +
@@ -660,6 +666,8 @@ Sorting is done via the HASH_IDENTITY and SP_VALUE columns. ## Columns +Note: This table has the columns listed below, but it also has all common columns listed above in [Common Search Index Columns](#HFJ_SPIDX_common). +
@@ -753,6 +761,8 @@ Sorting is done via the HASH_IDENTITY and SP_VALUE_NORMALIZED columns. ## Columns +Note: This table has the columns listed below, but it also has all common columns listed above in [Common Search Index Columns](#HFJ_SPIDX_common). +
@@ -806,6 +816,8 @@ Sorting is done via the HASH_IDENTITY and SP_VALUE columns. ## Columns +Note: This table has the columns listed below, but it also has all common columns listed above in [Common Search Index Columns](#HFJ_SPIDX_common). +
@@ -876,6 +888,8 @@ Sorting is done via the HASH_IDENTITY and SP_URI columns. ## Columns +Note: This table has the columns listed below, but it also has all common columns listed above in [Common Search Index Columns](#HFJ_SPIDX_common). +
@@ -908,3 +922,135 @@ Sorting is done via the HASH_IDENTITY and SP_URI columns.
+ +# HFJ_IDX_CMB_TOK_NU: Combo Non-Unique Search Param + +This table is used to index [Non-Unique Combo Search Parameters](https://smilecdr.com/docs/fhir_standard/fhir_search_custom_search_parameters.html#combo-search-index-parameters). + +## Columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameRelationshipsDatatypeNullableDescription
PIDLong + A unique persistent identifier for the given index row. +
RES_IDFK to HFJ_RESOURCELong + Contains the PID of the resource being indexed. +
IDX_STRINGString + This column contains a FHIR search expression indicating what is being indexed. For example, if a + non-unique combo search parameter is present which indexes a combination of Observation#code and + Observation#status, this column might contain a value such as + Observation?code=http://loinc.org|1234-5&status=final +
HASH_COMPLETELong + This column contains a hash of the value in column IDX_STRING. +
+ +
+ +# HFJ_IDX_CMP_STRING_UNIQ: Combo Unique Search Param + +This table is used to index [Unique Combo Search Parameters](https://smilecdr.com/docs/fhir_standard/fhir_search_custom_search_parameters.html#combo-search-index-parameters). + +## Columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameRelationshipsDatatypeNullableDescription
PIDLong + A unique persistent identifier for the given index row. +
RES_IDFK to HFJ_RESOURCELong + Contains the PID of the resource being indexed. +
IDX_STRINGString + This column contains a FHIR search expression indicating what is being indexed. For example, if a + unique combo search parameter is present which indexes a combination of Observation#code and + Observation#status, this column might contain a value such as + Observation?code=http://loinc.org|1234-5&status=final +
HASH_COMPLETELong + This column contains a hash of the value in column IDX_STRING. +
HASH_COMPLETE_2Long + This column contains an additional hash of the value in column IDX_STRING, using a + static salt of the value prior to the hashing. This is done in order to increase the number + of bits used to hash the index string from 64 to 128. +
+ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index ebf6936a6f4..e432143b291 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1284,10 +1284,6 @@ public abstract class BaseHapiFhirDao extends BaseStora myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params); } } - - // Synchronize composite params - mySearchParamWithInlineReferencesExtractor.storeUniqueComboParameters( - newParams, entity, existingParams); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 38100cc0519..3f0c52b3409 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -1628,6 +1628,7 @@ public abstract class BaseHapiFhirResourceDao extends B T resource = (T) myJpaStorageResourceParser.toResource(entity, false); reindexSearchParameters(resource, entity, theTransactionDetails); } catch (Exception e) { + ourLog.warn("Failure during reindex: {}", e.toString()); theReindexOutcome.addWarning("Failed to reindex resource " + entity.getIdDt() + ": " + e); myResourceTableDao.updateIndexStatus(entity.getId(), INDEX_STATUS_INDEXING_FAILED); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index e1f7749801a..0aedafefed7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -224,6 +224,17 @@ public abstract class BaseHapiFhirSystemDao extends B BaseHasResource::isHasTags, entityChunk); + prefetchByField( + "comboStringUnique", + "myParamsComboStringUnique", + ResourceTable::isParamsComboStringUniquePresent, + entityChunk); + prefetchByField( + "comboTokenNonUnique", + "myParamsComboTokensNonUnique", + ResourceTable::isParamsComboTokensNonUniquePresent, + entityChunk); + if (myStorageSettings.getIndexMissingFields() == JpaStorageSettings.IndexEnabledEnum.ENABLED) { prefetchByField("searchParamPresence", "mySearchParamPresents", r -> true, entityChunk); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java index 7c4c205a623..a654058e491 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java @@ -19,18 +19,24 @@ */ package ca.uhn.fhir.jpa.dao.index; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.StorageSettings; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.util.AddRemoveCount; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Nullable; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContextType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -44,30 +50,46 @@ import java.util.Set; @Service public class DaoSearchParamSynchronizer { - private static final Logger ourLog = LoggerFactory.getLogger(DaoSearchParamSynchronizer.class); - - @Autowired - private StorageSettings myStorageSettings; @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; + @Autowired + private JpaStorageSettings myStorageSettings; + + @Autowired + private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao; + + @Autowired + private FhirContext myFhirContext; + public AddRemoveCount synchronizeSearchParamsToDatabase( ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) { AddRemoveCount retVal = new AddRemoveCount(); - synchronize(theEntity, retVal, theParams.myStringParams, existingParams.myStringParams); - synchronize(theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams); - synchronize(theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams); - synchronize(theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams); - synchronize(theEntity, retVal, theParams.myQuantityNormalizedParams, existingParams.myQuantityNormalizedParams); - synchronize(theEntity, retVal, theParams.myDateParams, existingParams.myDateParams); - synchronize(theEntity, retVal, theParams.myUriParams, existingParams.myUriParams); - synchronize(theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams); - synchronize(theEntity, retVal, theParams.myLinks, existingParams.myLinks); - synchronize(theEntity, retVal, theParams.myComboTokenNonUnique, existingParams.myComboTokenNonUnique); + synchronize(theEntity, retVal, theParams.myStringParams, existingParams.myStringParams, null); + synchronize(theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams, null); + synchronize(theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams, null); + synchronize(theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams, null); + synchronize( + theEntity, + retVal, + theParams.myQuantityNormalizedParams, + existingParams.myQuantityNormalizedParams, + null); + synchronize(theEntity, retVal, theParams.myDateParams, existingParams.myDateParams, null); + synchronize(theEntity, retVal, theParams.myUriParams, existingParams.myUriParams, null); + synchronize(theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams, null); + synchronize(theEntity, retVal, theParams.myLinks, existingParams.myLinks, null); + synchronize(theEntity, retVal, theParams.myComboTokenNonUnique, existingParams.myComboTokenNonUnique, null); + synchronize( + theEntity, + retVal, + theParams.myComboStringUniques, + existingParams.myComboStringUniques, + new UniqueIndexPreExistenceChecker()); // make sure links are indexed theEntity.setResourceLinks(theParams.myLinks); @@ -76,20 +98,21 @@ public class DaoSearchParamSynchronizer { } @VisibleForTesting - public void setStorageSettings(StorageSettings theStorageSettings) { - this.myStorageSettings = theStorageSettings; + public void setEntityManager(EntityManager theEntityManager) { + myEntityManager = theEntityManager; } @VisibleForTesting - public void setEntityManager(EntityManager theEntityManager) { - myEntityManager = theEntityManager; + public void setStorageSettings(JpaStorageSettings theStorageSettings) { + myStorageSettings = theStorageSettings; } private void synchronize( ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection theNewParams, - Collection theExistingParams) { + Collection theExistingParams, + @Nullable IPreSaveHook theAddParamPreSaveHook) { Collection newParams = theNewParams; for (T next : newParams) { next.setPartitionId(theEntity.getPartitionId()); @@ -112,6 +135,7 @@ public class DaoSearchParamSynchronizer { Set existingParamsAsSet = new HashSet<>(theExistingParams.size()); for (Iterator iterator = theExistingParams.iterator(); iterator.hasNext(); ) { T next = iterator.next(); + next.setPlaceholderHashesIfMissing(); if (!existingParamsAsSet.add(next)) { iterator.remove(); myEntityManager.remove(next); @@ -126,6 +150,11 @@ public class DaoSearchParamSynchronizer { List paramsToRemove = subtract(theExistingParams, newParams); List paramsToAdd = subtract(newParams, theExistingParams); + + if (theAddParamPreSaveHook != null) { + theAddParamPreSaveHook.preSave(paramsToRemove, paramsToAdd); + } + tryToReuseIndexEntities(paramsToRemove, paramsToAdd); updateExistingParamsIfRequired(theExistingParams, paramsToAdd, newParams, paramsToRemove); @@ -138,6 +167,7 @@ public class DaoSearchParamSynchronizer { } myEntityManager.remove(next); } + for (T next : paramsToAdd) { myEntityManager.merge(next); } @@ -249,4 +279,64 @@ public class DaoSearchParamSynchronizer { } return retVal; } + + private interface IPreSaveHook { + + void preSave(Collection theParamsToRemove, Collection theParamsToAdd); + } + + private class UniqueIndexPreExistenceChecker implements IPreSaveHook { + + @Override + public void preSave( + Collection theParamsToRemove, + Collection theParamsToAdd) { + if (myStorageSettings.isUniqueIndexesCheckedBeforeSave()) { + for (ResourceIndexedComboStringUnique theIndex : theParamsToAdd) { + ResourceIndexedComboStringUnique existing = + myResourceIndexedCompositeStringUniqueDao.findByQueryString(theIndex.getIndexString()); + if (existing != null) { + + /* + * If we're reindexing, and the previous index row is being updated + * to add previously missing hashes, we may falsely detect that the index + * creation is going to fail. + */ + boolean existingIndexIsScheduledForRemoval = false; + for (var next : theParamsToRemove) { + if (existing == next) { + existingIndexIsScheduledForRemoval = true; + break; + } + } + if (existingIndexIsScheduledForRemoval) { + continue; + } + + String searchParameterId = "(unknown)"; + if (theIndex.getSearchParameterId() != null) { + searchParameterId = theIndex.getSearchParameterId().getValue(); + } + + String msg = myFhirContext + .getLocalizer() + .getMessage( + BaseHapiFhirDao.class, + "uniqueIndexConflictFailure", + existing.getResource().getResourceType(), + theIndex.getIndexString(), + existing.getResource() + .getIdDt() + .toUnqualifiedVersionless() + .getValue(), + searchParameterId); + + // Use ResourceVersionConflictException here because the HapiTransactionService + // catches this and can retry it if needed + throw new ResourceVersionConflictException(Msg.code(1093) + msg); + } + } + } + } + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index 99a06730dbe..846e2068031 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -19,15 +19,8 @@ */ package ca.uhn.fhir.jpa.dao.index; -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao; -import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.dao.JpaPid; -import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamWithInlineReferencesExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; @@ -36,10 +29,7 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; -import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; -import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import com.google.common.annotations.VisibleForTesting; -import jakarta.annotation.Nullable; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContextType; @@ -48,49 +38,22 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import java.util.Collection; -import java.util.stream.Collectors; - @Service @Lazy public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWithInlineReferencesExtractor implements ISearchParamWithInlineReferencesExtractor { - private static final org.slf4j.Logger ourLog = - org.slf4j.LoggerFactory.getLogger(SearchParamWithInlineReferencesExtractor.class); @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; - @Autowired - private ISearchParamRegistry mySearchParamRegistry; - @Autowired private SearchParamExtractorService mySearchParamExtractorService; - @Autowired - private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer; - - @Autowired - private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao; - - @Autowired - private PartitionSettings myPartitionSettings; - - @VisibleForTesting - public void setPartitionSettings(PartitionSettings thePartitionSettings) { - myPartitionSettings = thePartitionSettings; - } - @VisibleForTesting public void setSearchParamExtractorService(SearchParamExtractorService theSearchParamExtractorService) { mySearchParamExtractorService = theSearchParamExtractorService; } - @VisibleForTesting - public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) { - mySearchParamRegistry = theSearchParamRegistry; - } - public void populateFromResource( RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, @@ -116,103 +79,4 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit thePerformIndexing, ISearchParamExtractor.ALL_PARAMS); } - - @Nullable - private Collection findParameterIndexes( - ResourceIndexedSearchParams theParams, RuntimeSearchParam nextCompositeOf) { - Collection paramsListForCompositePart = null; - switch (nextCompositeOf.getParamType()) { - case NUMBER: - paramsListForCompositePart = theParams.myNumberParams; - break; - case DATE: - paramsListForCompositePart = theParams.myDateParams; - break; - case STRING: - paramsListForCompositePart = theParams.myStringParams; - break; - case TOKEN: - paramsListForCompositePart = theParams.myTokenParams; - break; - case QUANTITY: - paramsListForCompositePart = theParams.myQuantityParams; - break; - case URI: - paramsListForCompositePart = theParams.myUriParams; - break; - case REFERENCE: - case SPECIAL: - case COMPOSITE: - case HAS: - break; - } - if (paramsListForCompositePart != null) { - paramsListForCompositePart = paramsListForCompositePart.stream() - .filter(t -> t.getParamName().equals(nextCompositeOf.getName())) - .collect(Collectors.toList()); - } - return paramsListForCompositePart; - } - - @VisibleForTesting - public void setDaoSearchParamSynchronizer(DaoSearchParamSynchronizer theDaoSearchParamSynchronizer) { - myDaoSearchParamSynchronizer = theDaoSearchParamSynchronizer; - } - - public void storeUniqueComboParameters( - ResourceIndexedSearchParams theParams, - ResourceTable theEntity, - ResourceIndexedSearchParams theExistingParams) { - - /* - * String Uniques - */ - if (myStorageSettings.isUniqueIndexesEnabled()) { - for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract( - theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) { - ourLog.debug("Removing unique index: {}", next); - myEntityManager.remove(next); - theEntity.getParamsComboStringUnique().remove(next); - } - boolean haveNewStringUniqueParams = false; - for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract( - theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) { - if (myStorageSettings.isUniqueIndexesCheckedBeforeSave()) { - ResourceIndexedComboStringUnique existing = - myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); - if (existing != null) { - - String searchParameterId = "(unknown)"; - if (next.getSearchParameterId() != null) { - searchParameterId = next.getSearchParameterId() - .toUnqualifiedVersionless() - .getValue(); - } - - String msg = myFhirContext - .getLocalizer() - .getMessage( - BaseHapiFhirDao.class, - "uniqueIndexConflictFailure", - theEntity.getResourceType(), - next.getIndexString(), - existing.getResource() - .getIdDt() - .toUnqualifiedVersionless() - .getValue(), - searchParameterId); - - // Use ResourceVersionConflictException here because the HapiTransactionService - // catches this and can retry it if needed - throw new ResourceVersionConflictException(Msg.code(1093) + msg); - } - } - ourLog.debug("Persisting unique index: {}", next); - myEntityManager.persist(next); - haveNewStringUniqueParams = true; - } - theEntity.setParamsComboStringUniquePresent( - theParams.myComboStringUniques.size() > 0 || haveNewStringUniqueParams); - } - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 1c358a07123..258d8122008 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.migrate.tasks.api.TaskFlagEnum; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; @@ -404,6 +405,41 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { new ColumnAndNullable("RES_ID", false), new ColumnAndNullable("PARTITION_ID", true)); } + + /* + * Add hash columns to the combo param index tables + */ + { + version.onTable("HFJ_IDX_CMB_TOK_NU") + .addIndex("20240625.10", "IDX_IDXCMBTOKNU_HASHC") + .unique(false) + .withColumns("HASH_COMPLETE", "RES_ID", "PARTITION_ID"); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ") + .addColumn("20240625.20", "HASH_COMPLETE") + .nullable() + .type(ColumnTypeEnum.LONG); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ") + .addColumn("20240625.30", "HASH_COMPLETE_2") + .nullable() + .type(ColumnTypeEnum.LONG); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ") + .addTask( + new CalculateHashesTask(VersionEnum.V7_4_0, "20240625.40") { + @Override + protected boolean shouldSkipTask() { + return false; + } + }.setPidColumnName("PID") + .addCalculator( + "HASH_COMPLETE", + t -> ResourceIndexedComboStringUnique.calculateHashComplete( + t.getString("IDX_STRING"))) + .addCalculator( + "HASH_COMPLETE_2", + t -> ResourceIndexedComboStringUnique.calculateHashComplete2( + t.getString("IDX_STRING"))) + .setColumnName("HASH_COMPLETE")); + } } protected void init720() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index a2bc4a4fcf5..80b6fd51b6c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -74,6 +74,7 @@ import ca.uhn.fhir.jpa.util.SqlQueryList; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; @@ -82,6 +83,7 @@ import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParameterUtil; import ca.uhn.fhir.rest.param.ReferenceParam; @@ -1879,12 +1881,12 @@ public class SearchBuilder implements ISearchBuilder { } private void attemptComboUniqueSpProcessing( - QueryStack theQueryStack3, @Nonnull SearchParameterMap theParams, RequestDetails theRequest) { + QueryStack theQueryStack, @Nonnull SearchParameterMap theParams, RequestDetails theRequest) { RuntimeSearchParam comboParam = null; List comboParamNames = null; List exactMatchParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet()); - if (exactMatchParams.size() > 0) { + if (!exactMatchParams.isEmpty()) { comboParam = exactMatchParams.get(0); comboParamNames = new ArrayList<>(theParams.keySet()); } @@ -1906,98 +1908,138 @@ public class SearchBuilder implements ISearchBuilder { } if (comboParam != null) { - // Since we're going to remove elements below - theParams.values().forEach(this::ensureSubListsAreWritable); - - StringBuilder sb = new StringBuilder(); - sb.append(myResourceName); - sb.append("?"); - - boolean first = true; - Collections.sort(comboParamNames); - for (String nextParamName : comboParamNames) { - List> nextValues = theParams.get(nextParamName); - // TODO Hack to fix weird IOOB on the next stanza until James comes back and makes sense of this. - if (nextValues.isEmpty()) { - ourLog.error( - "query parameter {} is unexpectedly empty. Encountered while considering {} index for {}", - nextParamName, - comboParam.getName(), - theRequest.getCompleteUrl()); - sb = null; - break; - } - - if (nextValues.get(0).size() != 1) { - sb = null; - break; - } - - // Reference params are only eligible for using a composite index if they - // are qualified - RuntimeSearchParam nextParamDef = - mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); - if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { - ReferenceParam param = (ReferenceParam) nextValues.get(0).get(0); - if (isBlank(param.getResourceType())) { - sb = null; - break; - } - } - - List nextAnd = nextValues.remove(0); - IQueryParameterType nextOr = nextAnd.remove(0); - String nextOrValue = nextOr.getValueAsQueryToken(myContext); - - if (comboParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE) { - if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.STRING) { - nextOrValue = StringUtil.normalizeStringForSearchIndexing(nextOrValue); - } - } - - if (first) { - first = false; - } else { - sb.append('&'); - } - - nextParamName = UrlUtil.escapeUrlParam(nextParamName); - nextOrValue = UrlUtil.escapeUrlParam(nextOrValue); - - sb.append(nextParamName).append('=').append(nextOrValue); + if (!validateParamValuesAreValidForComboParam(theParams, comboParamNames)) { + return; } - if (sb != null) { - String indexString = sb.toString(); - ourLog.debug( - "Checking for {} combo index for query: {}", comboParam.getComboSearchParamType(), indexString); + applyComboSearchParam(theQueryStack, theParams, theRequest, comboParamNames, comboParam); + } + } - // Interceptor broadcast: JPA_PERFTRACE_INFO - StorageProcessingMessage msg = new StorageProcessingMessage() - .setMessage("Using " + comboParam.getComboSearchParamType() + " index for query for search: " - + indexString); - HookParams params = new HookParams() - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest) - .add(StorageProcessingMessage.class, msg); - CompositeInterceptorBroadcaster.doCallHooks( - myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params); + private void applyComboSearchParam( + QueryStack theQueryStack, + @Nonnull SearchParameterMap theParams, + RequestDetails theRequest, + List theComboParamNames, + RuntimeSearchParam theComboParam) { + // Since we're going to remove elements below + theParams.values().forEach(this::ensureSubListsAreWritable); - switch (comboParam.getComboSearchParamType()) { - case UNIQUE: - theQueryStack3.addPredicateCompositeUnique(indexString, myRequestPartitionId); - break; - case NON_UNIQUE: - theQueryStack3.addPredicateCompositeNonUnique(indexString, myRequestPartitionId); - break; + StringBuilder theSearchBuilder = new StringBuilder(); + theSearchBuilder.append(myResourceName); + theSearchBuilder.append("?"); + + boolean first = true; + for (String nextParamName : theComboParamNames) { + List> nextValues = theParams.get(nextParamName); + + // This should never happen, but this safety check was added along the way and + // presumably must save us in some specific race condition. I am preserving it + // in a refactor of this code base. 20240429 + if (nextValues.isEmpty()) { + ourLog.error( + "query parameter {} is unexpectedly empty. Encountered while considering {} index for {}", + nextParamName, + theComboParam.getName(), + theRequest.getCompleteUrl()); + continue; + } + + List nextAnd = nextValues.remove(0); + IQueryParameterType nextOr = nextAnd.remove(0); + String nextOrValue = nextOr.getValueAsQueryToken(myContext); + + RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); + if (theComboParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE) { + if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.STRING) { + nextOrValue = StringUtil.normalizeStringForSearchIndexing(nextOrValue); } + } - // Remove any empty parameters remaining after this - theParams.clean(); + if (first) { + first = false; + } else { + theSearchBuilder.append('&'); + } + + nextParamName = UrlUtil.escapeUrlParam(nextParamName); + nextOrValue = UrlUtil.escapeUrlParam(nextOrValue); + + theSearchBuilder.append(nextParamName).append('=').append(nextOrValue); + } + + if (theSearchBuilder != null) { + String indexString = theSearchBuilder.toString(); + ourLog.debug( + "Checking for {} combo index for query: {}", theComboParam.getComboSearchParamType(), indexString); + + // Interceptor broadcast: JPA_PERFTRACE_INFO + StorageProcessingMessage msg = new StorageProcessingMessage() + .setMessage("Using " + theComboParam.getComboSearchParamType() + " index for query for search: " + + indexString); + HookParams params = new HookParams() + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest) + .add(StorageProcessingMessage.class, msg); + CompositeInterceptorBroadcaster.doCallHooks( + myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params); + + switch (theComboParam.getComboSearchParamType()) { + case UNIQUE: + theQueryStack.addPredicateCompositeUnique(indexString, myRequestPartitionId); + break; + case NON_UNIQUE: + theQueryStack.addPredicateCompositeNonUnique(indexString, myRequestPartitionId); + break; + } + + // Remove any empty parameters remaining after this + theParams.clean(); + } + } + + private boolean validateParamValuesAreValidForComboParam( + @Nonnull SearchParameterMap theParams, List comboParamNames) { + boolean paramValuesAreValidForCombo = true; + for (String nextParamName : comboParamNames) { + List> nextValues = theParams.get(nextParamName); + + // Multiple AND parameters are not supported for unique combo params + if (nextValues.get(0).size() != 1) { + ourLog.debug( + "Search is not a candidate for unique combo searching - Multiple AND expressions found for the same parameter"); + paramValuesAreValidForCombo = false; + break; + } + + List nextAndValue = nextValues.get(0); + for (IQueryParameterType nextOrValue : nextAndValue) { + if (nextOrValue instanceof DateParam) { + if (((DateParam) nextOrValue).getPrecision() != TemporalPrecisionEnum.DAY) { + ourLog.debug( + "Search is not a candidate for unique combo searching - Date search with non-DAY precision"); + paramValuesAreValidForCombo = false; + break; + } + } + } + + // Reference params are only eligible for using a composite index if they + // are qualified + RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); + if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { + ReferenceParam param = (ReferenceParam) nextValues.get(0).get(0); + if (isBlank(param.getResourceType())) { + ourLog.debug( + "Search is not a candidate for unique combo searching - Reference with no type specified"); + paramValuesAreValidForCombo = false; + break; + } } } + return paramValuesAreValidForCombo; } private void ensureSubListsAreWritable(List> theListOfLists) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ComboNonUniqueSearchParameterPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ComboNonUniqueSearchParameterPredicateBuilder.java index d5e496664ec..af6dc0074a7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ComboNonUniqueSearchParameterPredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ComboNonUniqueSearchParameterPredicateBuilder.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.search.builder.predicate; import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder; import com.healthmarketscience.sqlbuilder.BinaryCondition; import com.healthmarketscience.sqlbuilder.Condition; @@ -27,7 +29,7 @@ import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn; public class ComboNonUniqueSearchParameterPredicateBuilder extends BaseSearchParamPredicateBuilder { - private final DbColumn myColumnIndexString; + private final DbColumn myColumnHashComplete; /** * Constructor @@ -35,11 +37,15 @@ public class ComboNonUniqueSearchParameterPredicateBuilder extends BaseSearchPar public ComboNonUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) { super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_IDX_CMB_TOK_NU")); - myColumnIndexString = getTable().addColumn("IDX_STRING"); + myColumnHashComplete = getTable().addColumn("HASH_COMPLETE"); } public Condition createPredicateHashComplete(RequestPartitionId theRequestPartitionId, String theIndexString) { - BinaryCondition predicate = BinaryCondition.equalTo(myColumnIndexString, generatePlaceholder(theIndexString)); + PartitionablePartitionId partitionId = + PartitionablePartitionId.toStoragePartition(theRequestPartitionId, getPartitionSettings()); + long hash = ResourceIndexedComboTokenNonUnique.calculateHashComplete( + getPartitionSettings(), partitionId, theIndexString); + BinaryCondition predicate = BinaryCondition.equalTo(myColumnHashComplete, generatePlaceholder(hash)); return combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java index 15807941e42..ab4b1afdefc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java @@ -370,7 +370,8 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im .collect(Collectors.joining(" or "))); } else { builder.append("If you know what you're looking for, try qualifying it using the form: '"); - builder.append(theParamName).append(":[resourceType]"); + builder.append(theParamName).append(":[resourceType]=[id] or "); + builder.append(theParamName).append("=[resourceType]/[id]"); builder.append("'"); } String message = builder.toString(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizerTest.java index 09855099a2a..d3a11cf80f7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizerTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizerTest.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jpa.dao.index; +import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.model.entity.StorageSettings; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.util.AddRemoveCount; import jakarta.persistence.EntityManager; @@ -62,7 +62,7 @@ public class DaoSearchParamSynchronizerTest { THE_SEARCH_PARAM_NUMBER.setResource(resourceTable); subject.setEntityManager(entityManager); - subject.setStorageSettings(new StorageSettings()); + subject.setStorageSettings(new JpaStorageSettings()); } @Test diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java index 17bc100c229..f9b8e8167a6 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java @@ -56,4 +56,14 @@ public abstract class BaseResourceIndex extends BasePartitionable implements Ser public abstract boolean equals(Object obj); public abstract void copyMutableValuesFrom(T theSource); + + /** + * This is called when reindexing a resource on the previously existing index rows. This method + * should set zero/0 values for the hashes, in order to avoid any calculating hashes on existing + * rows failing. This is important only in cases where hashes are not present on the existing rows, + * which would only be the case if new hash columns have been added. + */ + public void setPlaceholderHashesIfMissing() { + // nothing by default + } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedCombo.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedCombo.java new file mode 100644 index 00000000000..0605d7c0105 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedCombo.java @@ -0,0 +1,44 @@ +/*- + * #%L + * HAPI FHIR JPA Model + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package ca.uhn.fhir.jpa.model.entity; + +import jakarta.annotation.Nonnull; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Transient; +import org.hl7.fhir.instance.model.api.IIdType; + +@MappedSuperclass +public abstract class BaseResourceIndexedCombo extends BaseResourceIndex implements IResourceIndexComboSearchParameter { + + @Transient + private IIdType mySearchParameterId; + + @Override + public IIdType getSearchParameterId() { + return mySearchParameterId; + } + + @Override + public void setSearchParameterId(@Nonnull IIdType theSearchParameterId) { + assert theSearchParameterId.hasResourceType(); + assert theSearchParameterId.hasIdPart(); + mySearchParameterId = theSearchParameterId.toUnqualifiedVersionless(); + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java index 519abb6936e..11c8b091f10 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java @@ -66,7 +66,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { protected Long myHashIdentity; @GenericField - @Column(name = "SP_UPDATED") + @Column(name = "SP_UPDATED", nullable = true) @Temporal(TemporalType.TIMESTAMP) private Date myUpdated; diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/IResourceIndexComboSearchParameter.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/IResourceIndexComboSearchParameter.java index 1f979274c2d..78c8353789e 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/IResourceIndexComboSearchParameter.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/IResourceIndexComboSearchParameter.java @@ -19,6 +19,8 @@ */ package ca.uhn.fhir.jpa.model.entity; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import org.hl7.fhir.instance.model.api.IIdType; /** @@ -27,9 +29,13 @@ import org.hl7.fhir.instance.model.api.IIdType; */ public interface IResourceIndexComboSearchParameter { + /** + * Will be in the exact form [resourceType]/[id] + */ + @Nullable // if it never got set, e.g. on a row pulled from the DB IIdType getSearchParameterId(); - void setSearchParameterId(IIdType theSearchParameterId); + void setSearchParameterId(@Nonnull IIdType theSearchParameterId); String getIndexString(); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboStringUnique.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboStringUnique.java index e61941cb611..400eea6eac3 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboStringUnique.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboStringUnique.java @@ -19,6 +19,7 @@ */ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.util.SearchParamHash; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; @@ -30,7 +31,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; -import jakarta.persistence.Transient; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -39,6 +39,23 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hl7.fhir.instance.model.api.IIdType; +/** + * NOTE ON LIMITATIONS HERE + *

+ * This table does not include the partition ID in the uniqueness check. This was the case + * when this table was originally created. In other words, the uniqueness constraint does not + * include the partition column, and would therefore not be able to guarantee uniqueness + * local to a partition. + *

+ *

+ * TODO: HAPI FHIR 7.4.0 introduced hashes to this table - In a future release we should + * move the uniqueness constraint over to using them instead of the long string. At that + * time we could probably decide whether it makes sense to include the partition ID in + * the uniqueness check. Null values will be an issue there, we may need to introduce + * a rule that if you want to enforce uniqueness on a partitioned system you need a + * non-null default partition ID? + *

+ */ @Entity() @Table( name = "HFJ_IDX_CMP_STRING_UNIQ", @@ -52,7 +69,7 @@ import org.hl7.fhir.instance.model.api.IIdType; columnList = "RES_ID", unique = false) }) -public class ResourceIndexedComboStringUnique extends BasePartitionable +public class ResourceIndexedComboStringUnique extends BaseResourceIndexedCombo implements Comparable, IResourceIndexComboSearchParameter { public static final int MAX_STRING_LENGTH = 500; @@ -75,6 +92,39 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourceId; + // TODO: These hashes were added in 7.4.0 - They aren't used or indexed yet, but + // eventually we should replace the string index with a hash index in order to + // reduce the space usage. + @Column(name = "HASH_COMPLETE") + private Long myHashComplete; + + /** + * Because we'll be using these hashes to enforce uniqueness, the risk of + * collisions is bad, since it would be plain impossible to insert a row + * with a false collision here. So in order to reduce that risk, we + * double the number of bits we hash by having two hashes, effectively + * making the hash a 128 bit hash instead of just 64. + *

+ * The idea is that having two of them widens the hash from 64 bits to 128 + * bits + *

+ * If we have a value we want to guarantee uniqueness on of + * Observation?code=A, say it hashes to 12345. + * And suppose we have another value of Observation?code=B which + * also hashes to 12345. This is unlikely but not impossible. + * And if this happens, it will be impossible to add a resource with + * code B if there is already a resource with code A. + *

+ * Adding a second, salted hash reduces the likelihood of this happening, + * since it's unlikely the second hash would also collide. Not impossible + * of course, but orders of magnitude less likely still. + *

+ * + * @see #calculateHashComplete2(String) to see how this is calculated + */ + @Column(name = "HASH_COMPLETE_2") + private Long myHashComplete2; + @Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH) private String myIndexString; @@ -85,9 +135,6 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable @Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true) private Integer myPartitionIdValue; - @Transient - private IIdType mySearchParameterId; - /** * Constructor */ @@ -121,9 +168,22 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable return false; } + calculateHashes(); + ResourceIndexedComboStringUnique that = (ResourceIndexedComboStringUnique) theO; - return new EqualsBuilder().append(myIndexString, that.myIndexString).isEquals(); + EqualsBuilder b = new EqualsBuilder(); + b.append(myHashComplete, that.myHashComplete); + b.append(myHashComplete2, that.myHashComplete2); + return b.isEquals(); + } + + @Override + public void copyMutableValuesFrom(T theSource) { + ResourceIndexedComboStringUnique source = (ResourceIndexedComboStringUnique) theSource; + myIndexString = source.myIndexString; + myHashComplete = source.myHashComplete; + myHashComplete2 = source.myHashComplete2; } @Override @@ -146,9 +206,76 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable myResource = theResource; } + @Override + public Long getId() { + return myId; + } + + @Override + public void setId(Long theId) { + myId = theId; + } + + public Long getHashComplete() { + return myHashComplete; + } + + public void setHashComplete(Long theHashComplete) { + myHashComplete = theHashComplete; + } + + public Long getHashComplete2() { + return myHashComplete2; + } + + public void setHashComplete2(Long theHashComplete2) { + myHashComplete2 = theHashComplete2; + } + + @Override + public void setPlaceholderHashesIfMissing() { + super.setPlaceholderHashesIfMissing(); + if (myHashComplete == null) { + myHashComplete = 0L; + } + if (myHashComplete2 == null) { + myHashComplete2 = 0L; + } + } + + @Override + public void calculateHashes() { + if (myHashComplete == null) { + setHashComplete(calculateHashComplete(myIndexString)); + setHashComplete2(calculateHashComplete2(myIndexString)); + } + } + + public static long calculateHashComplete(String theQueryString) { + return SearchParamHash.hashSearchParam(theQueryString); + } + + public static long calculateHashComplete2(String theQueryString) { + // Just add a constant salt to the query string in order to hopefully + // further avoid collisions + String newQueryString = theQueryString + "ABC123"; + return calculateHashComplete(newQueryString); + } + + @Override + public void clearHashes() { + myHashComplete = null; + myHashComplete2 = null; + } + @Override public int hashCode() { - return new HashCodeBuilder(17, 37).append(myIndexString).toHashCode(); + calculateHashes(); + + HashCodeBuilder b = new HashCodeBuilder(17, 37); + b.append(myHashComplete); + b.append(myHashComplete2); + return b.toHashCode(); } @Override @@ -157,23 +284,9 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable .append("id", myId) .append("resourceId", myResourceId) .append("indexString", myIndexString) + .append("hashComplete", myHashComplete) + .append("hashComplete2", myHashComplete2) .append("partition", getPartitionId()) .toString(); } - - /** - * Note: This field is not persisted, so it will only be populated for new indexes - */ - @Override - public void setSearchParameterId(IIdType theSearchParameterId) { - mySearchParameterId = theSearchParameterId; - } - - /** - * Note: This field is not persisted, so it will only be populated for new indexes - */ - @Override - public IIdType getSearchParameterId() { - return mySearchParameterId; - } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java index acc5dc49d4a..2ed9371b4ee 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedComboTokenNonUnique.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.util.SearchParamHash; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; @@ -37,18 +38,17 @@ import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.hl7.fhir.instance.model.api.IIdType; - -import static ca.uhn.fhir.jpa.model.util.SearchParamHash.hashSearchParam; @Entity @Table( name = "HFJ_IDX_CMB_TOK_NU", indexes = { + // TODO: The hash index was added in 7.4.0 - In 7.6.0 we should drop the string index @Index(name = "IDX_IDXCMBTOKNU_STR", columnList = "IDX_STRING", unique = false), + @Index(name = "IDX_IDXCMBTOKNU_HASHC", columnList = "HASH_COMPLETE,RES_ID,PARTITION_ID", unique = false), @Index(name = "IDX_IDXCMBTOKNU_RES", columnList = "RES_ID", unique = false) }) -public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex +public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndexedCombo implements Comparable, IResourceIndexComboSearchParameter { @SequenceGenerator(name = "SEQ_IDXCMBTOKNU_ID", sequenceName = "SEQ_IDXCMBTOKNU_ID") @@ -76,9 +76,6 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex @Transient private transient PartitionSettings myPartitionSettings; - @Transient - private IIdType mySearchParameterId; - /** * Constructor */ @@ -105,6 +102,8 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex @Override public boolean equals(Object theO) { + calculateHashes(); + if (this == theO) { return true; } @@ -116,7 +115,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex ResourceIndexedComboTokenNonUnique that = (ResourceIndexedComboTokenNonUnique) theO; EqualsBuilder b = new EqualsBuilder(); - b.append(myIndexString, that.myIndexString); + b.append(getHashComplete(), that.getHashComplete()); return b.isEquals(); } @@ -157,7 +156,11 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex @Override public int hashCode() { - return new HashCodeBuilder(17, 37).append(myIndexString).toHashCode(); + calculateHashes(); + + HashCodeBuilder builder = new HashCodeBuilder(17, 37); + builder.append(getHashComplete()); + return builder.toHashCode(); } public PartitionSettings getPartitionSettings() { @@ -206,27 +209,6 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex public static long calculateHashComplete( PartitionSettings partitionSettings, PartitionablePartitionId thePartitionId, String queryString) { RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(thePartitionId); - return hashSearchParam(partitionSettings, requestPartitionId, queryString); - } - - public static long calculateHashComplete( - PartitionSettings partitionSettings, RequestPartitionId partitionId, String queryString) { - return hashSearchParam(partitionSettings, partitionId, queryString); - } - - /** - * Note: This field is not persisted, so it will only be populated for new indexes - */ - @Override - public void setSearchParameterId(IIdType theSearchParameterId) { - mySearchParameterId = theSearchParameterId; - } - - /** - * Note: This field is not persisted, so it will only be populated for new indexes - */ - @Override - public IIdType getSearchParameterId() { - return mySearchParameterId; + return SearchParamHash.hashSearchParam(partitionSettings, requestPartitionId, queryString); } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java index 343ca0d0c08..ea3ca5fba9d 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/ISearchParamHashIdentityRegistry.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR JPA Model + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.jpa.model.search; import ca.uhn.fhir.rest.server.util.IndexedSearchParam; diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java index 5ca532e1140..4c33765e00e 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/SearchParamHash.java @@ -29,6 +29,8 @@ import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** * Utility class for calculating hashes of SearchParam entity fields. @@ -51,12 +53,29 @@ public class SearchParamHash { * Applies a fast and consistent hashing algorithm to a set of strings */ public static long hashSearchParam( - PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String... theValues) { + @Nonnull PartitionSettings thePartitionSettings, + @Nonnull RequestPartitionId theRequestPartitionId, + @Nonnull String... theValues) { + return doHashSearchParam(thePartitionSettings, theRequestPartitionId, theValues); + } + + /** + * Applies a fast and consistent hashing algorithm to a set of strings + */ + public static long hashSearchParam(@Nonnull String... theValues) { + return doHashSearchParam(null, null, theValues); + } + + private static long doHashSearchParam( + @Nullable PartitionSettings thePartitionSettings, + @Nullable RequestPartitionId theRequestPartitionId, + @Nonnull String[] theValues) { Hasher hasher = HASH_FUNCTION.newHasher(); - if (thePartitionSettings.isPartitioningEnabled() - && thePartitionSettings.isIncludePartitionInSearchHashes() - && theRequestPartitionId != null) { + if (thePartitionSettings != null + && theRequestPartitionId != null + && thePartitionSettings.isPartitioningEnabled() + && thePartitionSettings.isIncludePartitionInSearchHashes()) { if (theRequestPartitionId.getPartitionIds().size() > 1) { throw new InternalErrorException(Msg.code(1527) + "Can not search multiple partitions when partitions are included in search hashes"); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 73bb07c878d..6ce98f6c290 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -48,9 +48,11 @@ import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.jpa.searchparam.util.RuntimeSearchParamHelper; import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; +import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.util.FhirTerser; @@ -563,6 +565,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor if (paramsListForCompositePart != null) { for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) { IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType(); + + if (nextParamAsClientParam instanceof DateParam) { + DateParam date = (DateParam) nextParamAsClientParam; + if (date.getPrecision() != TemporalPrecisionEnum.DAY) { + continue; + } + } + String value = nextParamAsClientParam.getValueAsQueryToken(myContext); RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceType, key); @@ -578,6 +588,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } } } + if (linksForCompositePart != null) { for (ResourceLink nextLink : linksForCompositePart) { if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) { @@ -942,7 +953,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor for (int i = 0; i < values.size(); i++) { IBase nextObject = values.get(i); if (nextObject instanceof IBaseExtension) { - IBaseExtension nextExtension = (IBaseExtension) nextObject; + IBaseExtension nextExtension = (IBaseExtension) nextObject; nextObject = nextExtension.getValue(); values.set(i, nextObject); } @@ -1740,7 +1751,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } } - @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) + @SuppressWarnings({"UnnecessaryLocalVariable"}) private void createStringIndexIfNotBlank( String theResourceType, Set theParams, diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java index d74faf0e656..0fae59e1e45 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCache.java @@ -89,8 +89,9 @@ public class JpaSearchParamCache { } public Optional getActiveComboSearchParamById(String theResourceName, IIdType theId) { + IIdType idToFind = theId.toUnqualifiedVersionless(); return getActiveComboSearchParams(theResourceName).stream() - .filter((param) -> Objects.equals(theId, param.getId())) + .filter((param) -> Objects.equals(idToFind, param.getIdUnqualifiedVersionless())) .findFirst(); } diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java index 5d924313a15..c3f260e8b4c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/JpaSearchParamCacheTest.java @@ -76,17 +76,17 @@ public class JpaSearchParamCacheTest { @Test public void testGetActiveComboParamByIdPresent(){ - IIdType id1 = new IdType(1); + IIdType id1 = new IdType("SearchParameter/1"); RuntimeSearchParam sp1 = createSearchParam(id1, ComboSearchParamType.NON_UNIQUE); - IIdType id2 = new IdType(2); + IIdType id2 = new IdType("SearchParameter/2"); RuntimeSearchParam sp2 = createSearchParam(id2, ComboSearchParamType.NON_UNIQUE); setActiveComboSearchParams(RESOURCE_TYPE, List.of(sp1, sp2)); Optional found = myJpaSearchParamCache.getActiveComboSearchParamById(RESOURCE_TYPE, id1); assertThat(found).isPresent(); - assertEquals(id1, found.get().getId()); + assertEquals(id1, found.get().getIdUnqualifiedVersionless()); } @Test @@ -143,7 +143,7 @@ public class JpaSearchParamCacheTest { private RuntimeSearchParam createSearchParam(IIdType theId, ComboSearchParamType theType){ RuntimeSearchParam sp = mock(RuntimeSearchParam.class); - when(sp.getId()).thenReturn(theId); + when(sp.getIdUnqualifiedVersionless()).thenReturn(theId); when(sp.getComboSearchParamType()).thenReturn(theType); return sp; } @@ -154,7 +154,7 @@ public class JpaSearchParamCacheTest { myJpaSearchParamCache.setActiveComboSearchParams(activeComboParams); } - private class TestableJpaSearchParamCache extends JpaSearchParamCache { + private static class TestableJpaSearchParamCache extends JpaSearchParamCache { public void setActiveComboSearchParams(Map> theActiveComboSearchParams){ myActiveComboSearchParams = theActiveComboSearchParams; } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java index 18be2b32b78..93710e4f63c 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java @@ -20,6 +20,7 @@ import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.SearchParameter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -45,6 +46,8 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { protected LocalDate myPartitionDate2; protected int myPartitionId; protected int myPartitionId2; + protected int myPartitionId3; + protected int myPartitionId4; private boolean myHaveDroppedForcedIdUniqueConstraint; @Autowired private IPartitionLookupSvc myPartitionConfigSvc; @@ -81,14 +84,16 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { myPartitionDate2 = LocalDate.of(2020, Month.FEBRUARY, 15); myPartitionId = 1; myPartitionId2 = 2; + myPartitionId3 = 3; + myPartitionId4 = 4; myPartitionInterceptor = new MyReadWriteInterceptor(); mySrdInterceptorService.registerInterceptor(myPartitionInterceptor); - myPartitionConfigSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1), null); - myPartitionConfigSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2), null); - myPartitionConfigSvc.createPartition(new PartitionEntity().setId(3).setName(PARTITION_3), null); - myPartitionConfigSvc.createPartition(new PartitionEntity().setId(4).setName(PARTITION_4), null); + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(myPartitionId).setName(PARTITION_1), null); + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(myPartitionId2).setName(PARTITION_2), null); + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(myPartitionId3).setName(PARTITION_3), null); + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(myPartitionId4).setName(PARTITION_4), null); myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.ENABLED); @@ -96,6 +101,10 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { myPartitionInterceptor.addReadPartition(RequestPartitionId.fromPartitionNames(JpaConstants.DEFAULT_PARTITION_NAME, PARTITION_1, PARTITION_2, PARTITION_3, PARTITION_4)); myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true), mySrd); + // Pre-fetch the partitions by ID + for (int i = 1; i <= 4; i++) { + myPartitionConfigSvc.getPartitionById(i); + } } @Override @@ -110,7 +119,8 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { }); } } - protected void createUniqueCompositeSp() { + + protected void createUniqueComboSp() { addCreateDefaultPartition(); addReadDefaultPartition(); // one for search param validation SearchParameter sp = new SearchParameter(); @@ -122,6 +132,17 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { sp.addBase("Patient"); mySearchParameterDao.update(sp, mySrd); + addCreateDefaultPartition(); + addReadDefaultPartition(); // one for search param validation + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name[0].family"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + addCreateDefaultPartition(); sp = new SearchParameter(); sp.setId("SearchParameter/patient-birthdate-unique"); @@ -131,6 +152,9 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { sp.addComponent() .setExpression("Patient") .setDefinition("SearchParameter/patient-birthdate"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); @@ -139,6 +163,49 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { mySearchParamRegistry.forceRefresh(); } + protected void createNonUniqueComboSp() { + addCreateDefaultPartition(); + addReadDefaultPartition(); // one for search param validation + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name.family"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + addCreateDefaultPartition(); + addReadDefaultPartition(); // one for search param validation + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-managingorg"); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode(Patient.SP_ORGANIZATION); + sp.setExpression("Patient.managingOrganization"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + addCreateDefaultPartition(); + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family-and-org"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-managingorg"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(false)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + } + protected void dropForcedIdUniqueConstraint() { runInTransaction(() -> { myEntityManager.createNativeQuery("alter table " + ResourceTable.HFJ_RESOURCE + " drop constraint " + IDX_RES_TYPE_FHIR_ID).executeUpdate(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java index 523c9deb914..2c00f66f3d2 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboNonUniqueParamTest.java @@ -1,6 +1,5 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.junit.jupiter.api.Assertions.assertNotNull; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -8,6 +7,7 @@ import ca.uhn.fhir.jpa.searchparam.submit.interceptor.SearchParamValidatingInter import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.util.HapiExtensions; @@ -16,24 +16,24 @@ import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.SearchParameter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4Test { - private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4ComboNonUniqueParamTest.class); + public static final String ORG_ID_UNQUALIFIED = "my-org"; + public static final String ORG_ID_QUALIFIED = "Organization/" + ORG_ID_UNQUALIFIED; @Autowired SearchParamValidatingInterceptor mySearchParamValidatingInterceptor; @@ -53,36 +53,28 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T } } - private void createNamesAndGenderSp() { + @Test + public void testTokenFromCodeableConcept_Create() { SearchParameter sp = new SearchParameter(); sp.setId("SearchParameter/patient-family"); sp.setType(Enumerations.SearchParamType.STRING); - sp.setCode("family"); - sp.setExpression("Patient.name.family + '|'"); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/patient-given"); - sp.setType(Enumerations.SearchParamType.STRING); sp.setCode("given"); - sp.setExpression("Patient.name.given"); + sp.setExpression("Patient.name.family"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); - sp.setId("SearchParameter/patient-gender"); + sp.setId("SearchParameter/patient-maritalstatus"); sp.setType(Enumerations.SearchParamType.TOKEN); sp.setCode("gender"); - sp.setExpression("Patient.gender"); + sp.setExpression("Patient.maritalStatus"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); - sp.setId("SearchParameter/patient-names-and-gender"); + sp.setId("SearchParameter/patient-names-and-maritalstatus"); sp.setType(Enumerations.SearchParamType.COMPOSITE); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); @@ -91,35 +83,58 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T .setDefinition("SearchParameter/patient-family"); sp.addComponent() .setExpression("Patient") - .setDefinition("SearchParameter/patient-given"); - sp.addComponent() - .setExpression("Patient") - .setDefinition("SearchParameter/patient-gender"); + .setDefinition("SearchParameter/patient-maritalstatus"); sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(false)); - mySearchParameterDao.update(sp); - + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); - myMessages.clear(); + + + Patient pt = new Patient(); + pt.addName().setFamily("FAMILY1"); + pt.addName().setFamily("FAMILY2"); + pt.getMaritalStatus().addCoding().setSystem("http://foo1").setCode("bar1"); + pt.getMaritalStatus().addCoding().setSystem("http://foo2").setCode("bar2"); + myPatientDao.create(pt, mySrd); + createPatient1(null); + + logAllNonUniqueIndexes(); + runInTransaction(() -> { + List indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll(); + indexedTokens.sort(Comparator.comparing(ResourceIndexedComboTokenNonUnique::getIndexString)); + assertEquals(4, indexedTokens.size()); + String expected; + expected = "Patient?gender=http%3A%2F%2Ffoo1%7Cbar1&given=FAMILY1"; + assertEquals(expected, indexedTokens.get(0).getIndexString()); + expected = "Patient?gender=http%3A%2F%2Ffoo1%7Cbar1&given=FAMILY2"; + assertEquals(expected, indexedTokens.get(1).getIndexString()); + expected = "Patient?gender=http%3A%2F%2Ffoo2%7Cbar2&given=FAMILY1"; + assertEquals(expected, indexedTokens.get(2).getIndexString()); + expected = "Patient?gender=http%3A%2F%2Ffoo2%7Cbar2&given=FAMILY2"; + assertEquals(expected, indexedTokens.get(3).getIndexString()); + }); } - @Test - public void testCreateAndUse() { - createNamesAndGenderSp(); - IIdType id1 = createPatient1(); + @Test + public void testStringAndToken_Create() { + createStringAndTokenCombo_NameAndGender(); + + IIdType id1 = createPatient1(null); assertNotNull(id1); - IIdType id2 = createPatient2(); + IIdType id2 = createPatient2(null); assertNotNull(id2); logAllNonUniqueIndexes(); runInTransaction(() -> { List indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll(); - indexedTokens.sort(Comparator.comparing(t -> t.getId())); + indexedTokens.sort(Comparator.comparing(ResourceIndexedComboTokenNonUnique::getId)); assertEquals(2, indexedTokens.size()); + String expected = "Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1"; + assertEquals(expected, indexedTokens.get(0).getIndexString()); assertEquals(-7504889232313729794L, indexedTokens.get(0).getHashComplete().longValue()); }); @@ -134,14 +149,9 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T myCaptureQueriesListener.logSelectQueries(); assertThat(actual).containsExactlyInAnyOrder(id1.toUnqualifiedVersionless().getValue()); - boolean found = false; - for (SqlQuery query : myCaptureQueriesListener.getSelectQueries()) { - String sql = query.getSql(true, false); - if ("SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 WHERE (t0.IDX_STRING = 'Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1')".equals(sql)) { - found = true; - } - } - assertThat(found).as("Found expected sql").isTrue(); + assertThat(myCaptureQueriesListener.getSelectQueries().stream().map(t->t.getSql(true, false)).toList()).contains( + "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 WHERE (t0.HASH_COMPLETE = '-7504889232313729794')" + ); logCapturedMessages(); assertThat(myMessages.toString()).contains("[INFO Using NON_UNIQUE index for query for search: Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1]"); @@ -149,9 +159,9 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T // Remove 1, add another - myPatientDao.delete(id1); + myPatientDao.delete(id1, mySrd); - IIdType id3 = createPatient1(); + IIdType id3 = createPatient1(null); assertNotNull(id3); params = SearchParameterMap.newSynchronous(); @@ -166,15 +176,15 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T } @Test - public void testCreateAndUpdateResource() { - createNamesAndGenderSp(); + public void testStringAndToken_CreateAndUpdate() { + createStringAndTokenCombo_NameAndGender(); // Create a resource patching the unique SP myCaptureQueriesListener.clear(); - IIdType id1 = createPatient1(); + IIdType id1 = createPatient1(null); assertNotNull(id1); - assertThat(myCaptureQueriesListener.countSelectQueries()).as(String.join(",", "\n" + myCaptureQueriesListener.getSelectQueries().stream().map(q -> q.getThreadName()).collect(Collectors.toList()))).isEqualTo(0); + assertThat(myCaptureQueriesListener.countSelectQueries()).as(String.join(",", "\n" + myCaptureQueriesListener.getSelectQueries().stream().map(SqlQuery::getThreadName).toList())).isEqualTo(0); assertEquals(12, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); @@ -219,19 +229,19 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T } @Test - public void testSearchWithExtraParameters() { - createNamesAndGenderSp(); + public void testStringAndToken_SearchWithExtraParameters() { + createStringAndTokenCombo_NameAndGender(); - IIdType id1 = createPatient1(); + IIdType id1 = createPatient1(null); assertNotNull(id1); - IIdType id2 = createPatient2(); + IIdType id2 = createPatient2(null); assertNotNull(id2); logAllNonUniqueIndexes(); runInTransaction(() -> { List indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll(); - indexedTokens.sort(Comparator.comparing(t -> t.getId())); + indexedTokens.sort(Comparator.comparing(ResourceIndexedComboTokenNonUnique::getId)); assertEquals(2, indexedTokens.size()); assertEquals(-7504889232313729794L, indexedTokens.get(0).getHashComplete().longValue()); }); @@ -249,7 +259,8 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T assertThat(actual).containsExactlyInAnyOrder(id1.toUnqualifiedVersionless().getValue()); String sql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false); - assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_IDX_CMB_TOK_NU t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_DATE t2 ON (t1.RES_ID = t2.RES_ID) WHERE ((t0.IDX_STRING = 'Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1') AND ((t2.HASH_IDENTITY = '5247847184787287691') AND ((t2.SP_VALUE_LOW_DATE_ORDINAL >= '20210202') AND (t2.SP_VALUE_HIGH_DATE_ORDINAL <= '20210202'))))", sql); + String expected = "SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_IDX_CMB_TOK_NU t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_DATE t2 ON (t1.RES_ID = t2.RES_ID) WHERE ((t0.HASH_COMPLETE = '-7504889232313729794') AND ((t2.HASH_IDENTITY = '5247847184787287691') AND ((t2.SP_VALUE_LOW_DATE_ORDINAL >= '20210202') AND (t2.SP_VALUE_HIGH_DATE_ORDINAL <= '20210202'))))"; + assertEquals(expected, sql); logCapturedMessages(); assertThat(myMessages.toString()).contains("[INFO Using NON_UNIQUE index for query for search: Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1]"); @@ -258,21 +269,296 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T } - private IIdType createPatient2() { - Patient pt2 = new Patient(); - pt2.getNameFirstRep().setFamily("Family2").addGiven("Given2"); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2021-02-02")); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualified(); - return id2; + @Test + public void testStringAndDate_Create() { + createStringAndTokenCombo_NameAndBirthdate(); + + IIdType id1 = createPatient1(null); + assertNotNull(id1); + + IIdType id2 = createPatient2(null); + assertNotNull(id2); + + logAllNonUniqueIndexes(); + runInTransaction(() -> { + List indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll(); + indexedTokens.sort(Comparator.comparing(ResourceIndexedComboTokenNonUnique::getId)); + assertEquals(2, indexedTokens.size()); + String expected = "Patient?birthdate=2021-02-02&family=FAMILY1"; + assertEquals(expected, indexedTokens.get(0).getIndexString()); + assertEquals(7196518367857292879L, indexedTokens.get(0).getHashComplete().longValue()); + }); + + myMessages.clear(); + SearchParameterMap params = SearchParameterMap.newSynchronous(); + params.add("family", new StringParam("family1")); + params.add("birthdate", new DateParam("2021-02-02")); + myCaptureQueriesListener.clear(); + IBundleProvider results = myPatientDao.search(params, mySrd); + List actual = toUnqualifiedVersionlessIdValues(results); + myCaptureQueriesListener.logSelectQueries(); + assertThat(actual).contains(id1.toUnqualifiedVersionless().getValue()); + + String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 WHERE (t0.HASH_COMPLETE = '7196518367857292879')"; + assertEquals(expected, myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false)); + + logCapturedMessages(); + assertThat(myMessages.toString()).contains("[INFO Using NON_UNIQUE index for query for search: Patient?birthdate=2021-02-02&family=FAMILY1]"); + myMessages.clear(); } - private IIdType createPatient1() { + /** + * Can't create or search for combo params with partial dates + */ + @Test + public void testStringAndDate_Create_PartialDate() { + createStringAndTokenCombo_NameAndBirthdate(); + + Patient pt1 = new Patient(); + pt1.getNameFirstRep().setFamily("Family1").addGiven("Given1"); + pt1.setBirthDateElement(new DateType("2021-02")); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualified(); + + logAllNonUniqueIndexes(); + runInTransaction(() -> { + List indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll(); + assertEquals(0, indexedTokens.size()); + }); + + myMessages.clear(); + SearchParameterMap params = SearchParameterMap.newSynchronous(); + params.add("family", new StringParam("family1")); + params.add("birthdate", new DateParam("2021-02")); + myCaptureQueriesListener.clear(); + IBundleProvider results = myPatientDao.search(params, mySrd); + List actual = toUnqualifiedVersionlessIdValues(results); + myCaptureQueriesListener.logSelectQueries(); + assertThat(actual).contains(id1.toUnqualifiedVersionless().getValue()); + + String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(sql).doesNotContain("HFJ_IDX_CMB_TOK_NU"); + + logCapturedMessages(); + assertThat(myMessages).isEmpty(); + } + + @Test + public void testStringAndReference_Create() { + createStringAndReferenceCombo_FamilyAndOrganization(); + + createOrg(); + + IIdType id1 = createPatient1(ORG_ID_QUALIFIED); + createPatient2(ORG_ID_QUALIFIED); + + logAllNonUniqueIndexes(); + runInTransaction(() -> { + List indexedTokens = myResourceIndexedComboTokensNonUniqueDao.findAll(); + indexedTokens.sort(Comparator.comparing(ResourceIndexedComboTokenNonUnique::getId)); + assertEquals(2, indexedTokens.size()); + assertEquals("Patient?family=FAMILY1%5C%7C&organization=Organization%2Fmy-org", indexedTokens.get(0).getIndexString()); + }); + + myMessages.clear(); + SearchParameterMap params = SearchParameterMap.newSynchronous(); + params.add("family", new StringParam("fAmIlY1|")); // weird casing to test normalization + params.add("organization", new ReferenceParam(ORG_ID_QUALIFIED)); + myCaptureQueriesListener.clear(); + IBundleProvider results = myPatientDao.search(params, mySrd); + List actual = toUnqualifiedVersionlessIdValues(results); + myCaptureQueriesListener.logSelectQueries(); + assertThat(actual).contains(id1.toUnqualifiedVersionless().getValue()); + + String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 WHERE (t0.HASH_COMPLETE = '2277801301223576208')"; + assertEquals(expected, myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false)); + } + + @Test + public void testStringAndReference_SearchByUnqualifiedReference() { + createStringAndReferenceCombo_FamilyAndOrganization(); + + createOrg(); + + IIdType id1 = createPatient1(ORG_ID_QUALIFIED); + createPatient2(ORG_ID_QUALIFIED); + + myMessages.clear(); + SearchParameterMap params = SearchParameterMap.newSynchronous(); + params.add("family", new StringParam("family1")); + // "orgid" instead of "Organization/orgid" + params.add("organization", new ReferenceParam(ORG_ID_UNQUALIFIED)); + myCaptureQueriesListener.clear(); + IBundleProvider results = myPatientDao.search(params, mySrd); + List actual = toUnqualifiedVersionlessIdValues(results); + assertThat(actual).contains(id1.toUnqualifiedVersionless().getValue()); + + myCaptureQueriesListener.logSelectQueries(); + String expected; + expected = "select rt1_0.RES_ID,rt1_0.RES_TYPE,rt1_0.FHIR_ID from HFJ_RESOURCE rt1_0 where rt1_0.FHIR_ID='my-org'"; + assertEquals(expected, myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false)); + String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false); + assertThat(sql).contains("SP_VALUE_NORMALIZED LIKE 'FAMILY1%'"); + assertThat(sql).contains("t1.TARGET_RESOURCE_ID"); + + assertThat(myMessages.get(0)).contains("This search uses an unqualified resource"); + } + + private void createOrg() { + Organization org = new Organization(); + org.setName("Some Org"); + org.setId(ORG_ID_QUALIFIED); + myOrganizationDao.update(org, mySrd); + } + + private void createStringAndTokenCombo_NameAndBirthdate() { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name.family"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-birthdate"); + sp.setType(Enumerations.SearchParamType.DATE); + sp.setCode("birthdate"); + sp.setExpression("Patient.birthDate"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-names-and-birthdate"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-birthdate"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(false)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + + myMessages.clear(); + } + + private void createStringAndTokenCombo_NameAndGender() { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name.family + '|'"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-given"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("given"); + sp.setExpression("Patient.name.given"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-gender"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.setCode("gender"); + sp.setExpression("Patient.gender"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-names-and-gender"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-given"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-gender"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(false)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + + myMessages.clear(); + } + + private void createStringAndReferenceCombo_FamilyAndOrganization() { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name.family + '|'"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-managingorg"); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode(Patient.SP_ORGANIZATION); + sp.setExpression("Patient.managingOrganization"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family-and-org"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-managingorg"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(false)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + + myMessages.clear(); + } + + + private IIdType createPatient1(String theOrgId) { Patient pt1 = new Patient(); pt1.getNameFirstRep().setFamily("Family1").addGiven("Given1"); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2021-02-02")); - return myPatientDao.create(pt1).getId().toUnqualified(); + pt1.getManagingOrganization().setReference(theOrgId); + return myPatientDao.create(pt1, mySrd).getId().toUnqualified(); + } + + private IIdType createPatient2(String theOrgId) { + Patient pt2 = new Patient(); + pt2.getNameFirstRep().setFamily("Family2").addGiven("Given2"); + pt2.setGender(Enumerations.AdministrativeGender.MALE); + pt2.setBirthDateElement(new DateType("2021-02-02")); + pt2.getManagingOrganization().setReference(theOrgId); + return myPatientDao.create(pt2, mySrd).getId().toUnqualified(); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamIT.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamIT.java index 1a8e7ab7d6d..507ac392a77 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamIT.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamIT.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertNotNull; import ca.uhn.fhir.batch2.api.IJobCoordinator; import ca.uhn.fhir.batch2.jobs.reindex.ReindexAppCtx; import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters; @@ -22,6 +20,7 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.HapiExtensions; +import jakarta.annotation.Nonnull; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; @@ -46,8 +45,8 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; -import jakarta.annotation.Nonnull; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -55,8 +54,9 @@ import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXING_FAILED; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -69,7 +69,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { @AfterEach public void purgeUniqueIndexes() { - myResourceIndexedCompositeStringUniqueDao.deleteAll(); + myResourceIndexedComboStringUniqueDao.deleteAll(); } private void createUniqueBirthdateAndGenderSps() { @@ -80,7 +80,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setExpression("Patient.gender"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/patient-birthdate"); @@ -89,7 +89,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setExpression("Patient.birthDate"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/patient-gender-birthdate"); @@ -105,7 +105,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); @@ -120,7 +120,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.REFERENCE); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Coverage"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/coverage-identifier"); @@ -129,7 +129,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.TOKEN); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Coverage"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/coverage-beneficiary-identifier"); @@ -147,7 +147,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); } @@ -160,7 +160,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.REFERENCE); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Observation"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/observation-uniq-subject"); @@ -175,7 +175,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); } @@ -188,7 +188,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.TOKEN); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/patient-uniq-identifier"); @@ -203,7 +203,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); } @@ -216,7 +216,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.TOKEN); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/patient-uniq-identifier"); @@ -231,7 +231,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); } @@ -243,7 +243,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setExpression("Patient.name"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/patient-organization"); @@ -252,7 +252,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setExpression("Patient.managingOrganization"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/patient-name-organization"); @@ -268,7 +268,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); } @@ -282,7 +282,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Observation"); sp.addTarget("Patient"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/obs-effective"); @@ -291,7 +291,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setExpression("Observation.effective"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Observation"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/obs-code"); @@ -300,7 +300,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setExpression("Observation.code"); sp.setStatus(PublicationStatus.ACTIVE); sp.addBase("Observation"); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); sp.setId("SearchParameter/observation-subject-date-code"); @@ -320,7 +320,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); + mySearchParameterDao.update(sp, mySrd); mySearchParamRegistry.forceRefresh(); } @@ -339,7 +339,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { .setValue(new BooleanType(true)); try { - mySearchParameterDao.create(sp); + mySearchParameterDao.create(sp, mySrd); fail(); } catch (UnprocessableEntityException e) { assertEquals(Msg.code(1115) + "SearchParameter is marked as unique but has no components", e.getMessage()); @@ -361,13 +361,60 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { .setValue(new BooleanType(true)); try { - mySearchParameterDao.create(sp); + mySearchParameterDao.create(sp, mySrd); fail(); } catch (UnprocessableEntityException e) { assertEquals(Msg.code(1116) + "SearchParameter is marked as unique but is missing component.definition", e.getMessage()); } } + @Test + public void testHashesCalculated() { + myStorageSettings.setAdvancedHSearchIndexing(false); + createUniqueIndexPatientIdentifier(); + + Patient pt = new Patient(); + pt.setActive(true); + pt.addIdentifier().setSystem("urn").setValue("111"); + pt.addIdentifier().setSystem("urn").setValue("222"); + + myCaptureQueriesListener.clear(); + IIdType id = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); + myCaptureQueriesListener.logInsertQueries(); + + List values = runInTransaction(()->{ + return myResourceIndexedComboStringUniqueDao.findAllForResourceIdForUnitTest(id.getIdPartAsLong()); + }); + assertEquals(2, values.size()); + values.sort(Comparator.comparing(ResourceIndexedComboStringUnique::getIndexString)); + assertEquals("Patient?identifier=urn%7C111", values.get(0).getIndexString()); + assertEquals(1719691123901055728L, values.get(0).getHashComplete()); + assertEquals(-8172870218136407824L, values.get(0).getHashComplete2()); + assertEquals("Patient?identifier=urn%7C222", values.get(1).getIndexString()); + assertEquals(7615522755370797441L, values.get(1).getHashComplete()); + assertEquals(1496354762750275623L, values.get(1).getHashComplete2()); + + // Update the values + + pt = new Patient(); + pt.setId(id.toUnqualifiedVersionless()); + pt.setActive(true); + pt.addIdentifier().setSystem("urn").setValue("111"); + pt.addIdentifier().setSystem("urn").setValue("333"); + myPatientDao.update(pt, mySrd); + + values = runInTransaction(()->{ + return myResourceIndexedComboStringUniqueDao.findAllForResourceIdForUnitTest(id.getIdPartAsLong()); + }); + assertEquals(2, values.size()); + values.sort(Comparator.comparing(ResourceIndexedComboStringUnique::getIndexString)); + assertEquals("Patient?identifier=urn%7C111", values.get(0).getIndexString()); + assertEquals(1719691123901055728L, values.get(0).getHashComplete()); + assertEquals(-8172870218136407824L, values.get(0).getHashComplete2()); + assertEquals("Patient?identifier=urn%7C333", values.get(1).getIndexString()); + assertEquals(3049342026616784675L, values.get(1).getHashComplete()); + assertEquals(-2327712097368275295L, values.get(1).getHashComplete2()); + } @Test public void testDoubleMatchingOnAnd_Search() { @@ -378,17 +425,17 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { pt.setActive(true); pt.addIdentifier().setSystem("urn").setValue("111"); pt.addIdentifier().setSystem("urn").setValue("222"); - String id1 = myPatientDao.create(pt).getId().toUnqualifiedVersionless().getValue(); + String id1 = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless().getValue(); pt = new Patient(); pt.setActive(true); pt.addIdentifier().setSystem("urn").setValue("333"); - String id2 = myPatientDao.create(pt).getId().toUnqualifiedVersionless().getValue(); + String id2 = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless().getValue(); pt = new Patient(); pt.setActive(false); pt.addIdentifier().setSystem("urn").setValue("444"); - myPatientDao.create(pt); + myPatientDao.create(pt, mySrd); String unformattedSql; @@ -401,7 +448,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { .addAnd(new TokenParam("urn", "111")) .addAnd(new TokenParam("urn", "222")) ); - IBundleProvider outcome = myPatientDao.search(sp); + IBundleProvider outcome = myPatientDao.search(sp, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); assertThat(unformattedSql).containsSubsequence( @@ -420,7 +467,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { new TokenAndListParam() .addAnd(new TokenParam("urn", "111"), new TokenParam("urn", "222")) ); - outcome = myPatientDao.search(sp); + outcome = myPatientDao.search(sp, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(id1); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); @@ -437,7 +484,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { new TokenAndListParam() .addAnd(new TokenParam(null, "true")) ); - outcome = myPatientDao.search(sp); + outcome = myPatientDao.search(sp, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(id1, id2); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); @@ -458,7 +505,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.REFERENCE); sp.addBase(ServiceRequest.class.getSimpleName()); sp.setExpression("ServiceRequest.subject.where(resolve() is Patient)"); - String patientParamId = mySearchParameterDao.create(sp).getId().toUnqualifiedVersionless().getValue(); + String patientParamId = mySearchParameterDao.create(sp, mySrd).getId().toUnqualifiedVersionless().getValue(); sp = new SearchParameter(); sp.setStatus(PublicationStatus.ACTIVE); @@ -467,7 +514,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.REFERENCE); sp.addBase(ServiceRequest.class.getSimpleName()); sp.setExpression("ServiceRequest.performer"); - String performerParamId = mySearchParameterDao.create(sp).getId().toUnqualifiedVersionless().getValue(); + String performerParamId = mySearchParameterDao.create(sp, mySrd).getId().toUnqualifiedVersionless().getValue(); sp = new SearchParameter(); sp.setStatus(PublicationStatus.ACTIVE); @@ -476,7 +523,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.setType(Enumerations.SearchParamType.TOKEN); sp.addBase(ServiceRequest.class.getSimpleName()); sp.setExpression("ServiceRequest.identifier"); - String identifierParamId = mySearchParameterDao.create(sp).getId().toUnqualifiedVersionless().getValue(); + String identifierParamId = mySearchParameterDao.create(sp, mySrd).getId().toUnqualifiedVersionless().getValue(); sp = new SearchParameter(); sp.setId("SearchParameter/patient-uniq-identifier"); @@ -497,32 +544,32 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { sp.addExtension() .setUrl(HapiExtensions.EXT_SP_UNIQUE) .setValue(new BooleanType(true)); - mySearchParameterDao.create(sp); + mySearchParameterDao.create(sp, mySrd); mySearchParamRegistry.forceRefresh(); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(sp)); // Now create matching/non-matching resources Patient pt = new Patient(); pt.setActive(true); - IIdType ptId = myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + IIdType ptId = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); Practitioner pract = new Practitioner(); pract.setActive(true); - IIdType practId = myPractitionerDao.create(pract).getId().toUnqualifiedVersionless(); + IIdType practId = myPractitionerDao.create(pract, mySrd).getId().toUnqualifiedVersionless(); ServiceRequest sr = new ServiceRequest(); sr.addIdentifier().setSystem("sys").setValue("111"); sr.addIdentifier().setSystem("sys").setValue("222"); sr.setSubject(new Reference(ptId)); sr.addPerformer(new Reference(practId)); - String srId = myServiceRequestDao.create(sr).getId().toUnqualifiedVersionless().getValue(); + String srId = myServiceRequestDao.create(sr, mySrd).getId().toUnqualifiedVersionless().getValue(); sr = new ServiceRequest(); sr.addIdentifier().setSystem("sys").setValue("888"); sr.addIdentifier().setSystem("sys").setValue("999"); sr.setSubject(new Reference(ptId)); sr.addPerformer(new Reference(practId)); - myServiceRequestDao.create(sr).getId().toUnqualifiedVersionless().getValue(); + myServiceRequestDao.create(sr, mySrd).getId().toUnqualifiedVersionless().getValue(); String unformattedSql; @@ -537,7 +584,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { ); map.add("patient", new ReferenceParam(ptId.getValue())); map.add("performer", new ReferenceParam(practId.getValue())); - IBundleProvider outcome = myServiceRequestDao.search(map); + IBundleProvider outcome = myServiceRequestDao.search(map, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(srId); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); @@ -559,7 +606,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { ); map.add("patient", new ReferenceParam(ptId.getIdPart())); map.add("performer", new ReferenceParam(practId.getIdPart())); - outcome = myServiceRequestDao.search(map); + outcome = myServiceRequestDao.search(map, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(srId); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); @@ -611,7 +658,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { // Make sure entries are saved runInTransaction(() -> { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + List all = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(2, all.size()); }); @@ -655,7 +702,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + List all = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(2, all.size()); } }); @@ -678,7 +725,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setActive(true); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); /* * Both of the following resources will match the unique index we'll @@ -690,13 +737,13 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { obs.getCode().addCoding().setSystem("foo").setCode("bar"); obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); obs.setEffective(new DateTimeType("2011-01-01")); - IIdType id2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + IIdType id2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); obs = new Observation(); obs.getCode().addCoding().setSystem("foo").setCode("bar"); obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); obs.setEffective(new DateTimeType("2011-01-01")); - IIdType id3 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + IIdType id3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); ourLog.info("ID1: {} - ID2: {} - ID3: {}", id1, id2, id3); @@ -709,14 +756,14 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { executeReindex(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size(), uniques.toString()); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()).isIn( "Observation/" + id2.getIdPart(), "Observation/" + id3.getIdPart()); assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); - myResourceIndexedCompositeStringUniqueDao.deleteAll(); + myResourceIndexedComboStringUniqueDao.deleteAll(); }); assertThat(mySearchParamRegistry.getActiveComboSearchParams("Observation")).hasSize(1); @@ -724,7 +771,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { executeReindex(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size(), uniques.toString()); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()).isIn( "Observation/" + id2.getIdPart(), @@ -743,10 +790,10 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); try { - myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); fail(); } catch (ResourceVersionConflictException e) { assertEquals(Msg.code(550) + Msg.code(824) + "The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.", e.getMessage()); @@ -760,10 +807,10 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd); try { - myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd); fail(); } catch (ResourceVersionConflictException e) { assertThat(e.getMessage()).contains("new unique index created by SearchParameter/patient-gender-birthdate"); @@ -777,13 +824,13 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt = new Patient(); pt.addIdentifier().setSystem("urn").setValue("111"); pt.addIdentifier().setSystem("urn").setValue("222"); - myPatientDao.create(pt); + myPatientDao.create(pt, mySrd); pt = new Patient(); pt.addIdentifier().setSystem("urn").setValue("111"); pt.addIdentifier().setSystem("urn").setValue("222"); try { - myPatientDao.create(pt); + myPatientDao.create(pt, mySrd); fail(); } catch (ResourceVersionConflictException e) { // good @@ -792,12 +839,12 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { pt = new Patient(); pt.addIdentifier().setSystem("urn").setValue("333"); pt.addIdentifier().setSystem("urn").setValue("222"); - myPatientDao.create(pt); + myPatientDao.create(pt, mySrd); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + List all = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(2, all.size()); } }); @@ -810,12 +857,12 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt = new Patient(); pt.addIdentifier().setSystem("urn").setValue("111"); - IIdType id = myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + IIdType id = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + List all = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, all.size()); } }); @@ -823,18 +870,18 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { pt = new Patient(); pt.addIdentifier().setSystem("urn").setValue("111"); pt.setActive(true); - String version = myPatientDao.update(pt, "Patient?first-identifier=urn|111").getId().getVersionIdPart(); + String version = myPatientDao.update(pt, "Patient?first-identifier=urn|111", mySrd).getId().getVersionIdPart(); assertEquals("2", version); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + List all = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, all.size()); } }); - pt = myPatientDao.read(id); + pt = myPatientDao.read(id, mySrd); assertTrue(pt.getActive()); } @@ -844,17 +891,17 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setBirthDateElement(new DateType("2011-01-02")); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); Coverage cov = new Coverage(); cov.getBeneficiary().setReference(id2.getValue()); cov.addIdentifier().setSystem("urn:foo:bar").setValue("123"); - IIdType id3 = myCoverageDao.create(cov).getId().toUnqualifiedVersionless(); + IIdType id3 = myCoverageDao.create(cov, mySrd).getId().toUnqualifiedVersionless(); createUniqueIndexCoverageBeneficiary(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); @@ -873,7 +920,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { }); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); ourLog.info("** Uniques: {}", uniques); assertEquals(1, uniques.size(), uniques.toString()); assertEquals("Coverage/" + id3.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); @@ -901,102 +948,103 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { public void testIndexTransactionWithMatchUrl2() { createUniqueIndexCoverageBeneficiary(); - String input = "{\n" + - " \"resourceType\": \"Bundle\",\n" + - " \"type\": \"transaction\",\n" + - " \"entry\": [\n" + - " {\n" + - " \"fullUrl\": \"urn:uuid:d2a46176-8e15-405d-bbda-baea1a9dc7f3\",\n" + - " \"resource\": {\n" + - " \"resourceType\": \"Patient\",\n" + - " \"identifier\": [\n" + - " {\n" + - " \"use\": \"official\",\n" + - " \"type\": {\n" + - " \"coding\": [\n" + - " {\n" + - " \"system\": \"http://hl7.org/fhir/v2/0203\",\n" + - " \"code\": \"MR\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"system\": \"FOOORG:FOOSITE:patientid:MR:R\",\n" + - " \"value\": \"007811959\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"request\": {\n" + - " \"method\": \"PUT\",\n" + - " \"url\": \"/Patient?identifier=FOOORG%3AFOOSITE%3Apatientid%3AMR%3AR%7C007811959%2CFOOORG%3AFOOSITE%3Apatientid%3AMR%3AB%7C000929990%2CFOOORG%3AFOOSITE%3Apatientid%3API%3APH%7C00589363%2Chttp%3A%2F%2Fhl7.org%2Ffhir%2Fsid%2Fus-ssn%7C657-01-8133\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"fullUrl\": \"urn:uuid:b58ff639-11d1-4dac-942f-abf4f9a625d7\",\n" + - " \"resource\": {\n" + - " \"resourceType\": \"Coverage\",\n" + - " \"identifier\": [\n" + - " {\n" + - " \"system\": \"FOOORG:FOOSITE:coverage:planId\",\n" + - " \"value\": \"0403-010101\"\n" + - " }\n" + - " ],\n" + - " \"beneficiary\": {\n" + - " \"reference\": \"urn:uuid:d2a46176-8e15-405d-bbda-baea1a9dc7f3\"\n" + - " }\n" + - " },\n" + - " \"request\": {\n" + - " \"method\": \"PUT\",\n" + - " \"url\": \"/Coverage?beneficiary=urn%3Auuid%3Ad2a46176-8e15-405d-bbda-baea1a9dc7f3&identifier=FOOORG%3AFOOSITE%3Acoverage%3AplanId%7C0403-010101\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"fullUrl\": \"urn:uuid:13f5da1a-6601-4c1a-82c9-41527be23fa0\",\n" + - " \"resource\": {\n" + - " \"resourceType\": \"Coverage\",\n" + - " \"contained\": [\n" + - " {\n" + - " \"resourceType\": \"RelatedPerson\",\n" + - " \"id\": \"1\",\n" + - " \"name\": [\n" + - " {\n" + - " \"family\": \"SMITH\",\n" + - " \"given\": [\n" + - " \"FAKER\"\n" + - " ]\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"resourceType\": \"Organization\",\n" + - " \"id\": \"2\",\n" + - " \"name\": \"MEDICAID\"\n" + - " }\n" + - " ],\n" + - " \"identifier\": [\n" + - " {\n" + - " \"system\": \"FOOORG:FOOSITE:coverage:planId\",\n" + - " \"value\": \"0404-010101\"\n" + - " }\n" + - " ],\n" + - " \"policyHolder\": {\n" + - " \"reference\": \"#1\"\n" + - " },\n" + - " \"beneficiary\": {\n" + - " \"reference\": \"urn:uuid:d2a46176-8e15-405d-bbda-baea1a9dc7f3\"\n" + - " },\n" + - " \"payor\": [\n" + - " {\n" + - " \"reference\": \"#2\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"request\": {\n" + - " \"method\": \"PUT\",\n" + - " \"url\": \"/Coverage?beneficiary=urn%3Auuid%3Ad2a46176-8e15-405d-bbda-baea1a9dc7f3&identifier=FOOORG%3AFOOSITE%3Acoverage%3AplanId%7C0404-010101\"\n" + - " }\n" + - " }\n" + - " ]\n" + - "}"; + String input = """ + { + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "urn:uuid:d2a46176-8e15-405d-bbda-baea1a9dc7f3", + "resource": { + "resourceType": "Patient", + "identifier": [ + { + "use": "official", + "type": { + "coding": [ + { + "system": "http://hl7.org/fhir/v2/0203", + "code": "MR" + } + ] + }, + "system": "FOOORG:FOOSITE:patientid:MR:R", + "value": "007811959" + } + ] + }, + "request": { + "method": "PUT", + "url": "/Patient?identifier=FOOORG%3AFOOSITE%3Apatientid%3AMR%3AR%7C007811959%2CFOOORG%3AFOOSITE%3Apatientid%3AMR%3AB%7C000929990%2CFOOORG%3AFOOSITE%3Apatientid%3API%3APH%7C00589363%2Chttp%3A%2F%2Fhl7.org%2Ffhir%2Fsid%2Fus-ssn%7C657-01-8133" + } + }, + { + "fullUrl": "urn:uuid:b58ff639-11d1-4dac-942f-abf4f9a625d7", + "resource": { + "resourceType": "Coverage", + "identifier": [ + { + "system": "FOOORG:FOOSITE:coverage:planId", + "value": "0403-010101" + } + ], + "beneficiary": { + "reference": "urn:uuid:d2a46176-8e15-405d-bbda-baea1a9dc7f3" + } + }, + "request": { + "method": "PUT", + "url": "/Coverage?beneficiary=urn%3Auuid%3Ad2a46176-8e15-405d-bbda-baea1a9dc7f3&identifier=FOOORG%3AFOOSITE%3Acoverage%3AplanId%7C0403-010101" + } + }, + { + "fullUrl": "urn:uuid:13f5da1a-6601-4c1a-82c9-41527be23fa0", + "resource": { + "resourceType": "Coverage", + "contained": [ + { + "resourceType": "RelatedPerson", + "id": "1", + "name": [ + { + "family": "SMITH", + "given": [ + "FAKER" + ] + } + ] + }, + { + "resourceType": "Organization", + "id": "2", + "name": "MEDICAID" + } + ], + "identifier": [ + { + "system": "FOOORG:FOOSITE:coverage:planId", + "value": "0404-010101" + } + ], + "policyHolder": { + "reference": "#1" + }, + "beneficiary": { + "reference": "urn:uuid:d2a46176-8e15-405d-bbda-baea1a9dc7f3" + }, + "payor": [ + { + "reference": "#2" + } + ] + }, + "request": { + "method": "PUT", + "url": "/Coverage?beneficiary=urn%3Auuid%3Ad2a46176-8e15-405d-bbda-baea1a9dc7f3&identifier=FOOORG%3AFOOSITE%3Acoverage%3AplanId%7C0404-010101" + } + } + ] + }"""; Bundle inputBundle = myFhirContext.newJsonParser().parseResource(Bundle.class, input); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(inputBundle)); @@ -1041,21 +1089,21 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt = new Patient(); pt.addIdentifier().setSystem("urn").setValue("111"); pt.addIdentifier().setSystem("urn").setValue("222"); - IIdType ptid = myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + IIdType ptid = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); Encounter enc = new Encounter(); enc.setSubject(new Reference(ptid)); - IIdType encid = myEncounterDao.create(enc).getId().toUnqualifiedVersionless(); + IIdType encid = myEncounterDao.create(enc, mySrd).getId().toUnqualifiedVersionless(); Observation obs = new Observation(); obs.setSubject(new Reference(ptid)); obs.setEncounter(new Reference(encid)); - myObservationDao.create(obs); + myObservationDao.create(obs, mySrd); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + List all = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, all.size(), all.toString()); } }); @@ -1070,12 +1118,12 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setBirthDateElement(new DateType("2011-01-02")); - myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myCaptureQueriesListener.clear(); myMessages.clear(); @@ -1101,12 +1149,12 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - String id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue(); + String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue(); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setBirthDateElement(new DateType("2011-01-02")); - myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myMessages.clear(); SearchParameterMap params = new SearchParameterMap(); @@ -1163,9 +1211,9 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertThat(uniques.size()).as(uniques.toString()).isEqualTo(1); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString()); @@ -1177,28 +1225,28 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { runInTransaction(() -> { List uniques; - uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); Patient pt1 = new Patient(); pt1.setActive(true); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); Observation obs = new Observation(); obs.getCode().addCoding().setSystem("foo").setCode("bar"); obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); obs.setEffective(new DateTimeType("2011-01-01")); - IIdType id2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + IIdType id2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.setActive(false); - IIdType id3 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id3 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); ourLog.info("ID1: {} - ID2: {} - ID3: {}", id1, id2, id3); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size(), uniques.toString()); assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); @@ -1213,7 +1261,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Organization org = new Organization(); org.setId("Organization/ORG"); org.setName("ORG"); - myOrganizationDao.update(org); + myOrganizationDao.update(org, mySrd); Patient pt1 = new Patient(); pt1.addName().setFamily("FAMILY1"); @@ -1226,7 +1274,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { myMessages.clear(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString()); @@ -1244,7 +1292,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { assertThat(myMessages.toString()).contains("Using UNIQUE index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"); myMessages.clear(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString()); @@ -1259,7 +1307,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Organization org = new Organization(); org.setId("Organization/ORG"); org.setName("ORG"); - myOrganizationDao.update(org); + myOrganizationDao.update(org, mySrd); Patient pt1 = new Patient(); pt1.addName() @@ -1268,9 +1316,9 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { .addGiven("GIVEN2") .addGiven("GIVEN2"); // GIVEN2 happens twice pt1.setManagingOrganization(new Reference("Organization/ORG")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); Collections.sort(uniques); assertThat(uniques).hasSize(3); @@ -1291,15 +1339,15 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Organization org = new Organization(); org.setId("Organization/ORG"); org.setName("ORG"); - myOrganizationDao.update(org); + myOrganizationDao.update(org, mySrd); Patient pt1 = new Patient(); pt1.addName().setFamily("FAMILY1"); pt1.setManagingOrganization(new Reference("Organization/ORG")); - IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization.name=ORG").getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization.name=ORG", mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString()); @@ -1310,10 +1358,10 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { pt1 = new Patient(); pt1.addName().setFamily("FAMILY1"); pt1.setManagingOrganization(new Reference("Organization/ORG")); - myPatientDao.update(pt1, "Patient?name=FAMILY1&organization.name=ORG").getId().toUnqualifiedVersionless(); + myPatientDao.update(pt1, "Patient?name=FAMILY1&organization.name=ORG", mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString()); @@ -1327,7 +1375,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Organization org = new Organization(); org.setId("Organization/ORG"); org.setName("ORG"); - myOrganizationDao.update(org); + myOrganizationDao.update(org, mySrd); Bundle bundle = new Bundle(); bundle.setType(Bundle.BundleType.TRANSACTION); @@ -1358,7 +1406,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { IIdType id1 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString()); @@ -1395,7 +1443,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { IdType id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString()); @@ -1410,26 +1458,26 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { pt = new Patient(); pt.setGender(Enumerations.AdministrativeGender.MALE); - myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); pt = new Patient(); - myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); pt = new Patient(); pt.setBirthDateElement(new DateType()); pt.setGender(Enumerations.AdministrativeGender.MALE); - myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); } @@ -1441,15 +1489,15 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Organization org = new Organization(); org.setId("Organization/ORG"); org.setName("ORG"); - myOrganizationDao.update(org); + myOrganizationDao.update(org, mySrd); Patient pt; pt = new Patient(); pt.setManagingOrganization(new Reference("Organization/ORG")); - myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); @@ -1459,17 +1507,17 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { .addGiven("GIVEN1") .addGiven("GIVEN2") .addGiven("GIVEN2"); // GIVEN2 happens twice - myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); pt = new Patient(); pt.setActive(true); - myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(0, uniques.size(), uniques.toString()); }); } @@ -1480,35 +1528,33 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setActive(true); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); Observation obs = new Observation(); obs.getCode().addCoding().setSystem("foo").setCode("bar"); obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); obs.setEffective(new DateTimeType("2011-01-01")); - IIdType id2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + IIdType id2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.setActive(false); - myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); executeReindex(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size(), uniques.toString()); assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); }); - runInTransaction(() -> { - myResourceIndexedCompositeStringUniqueDao.deleteAll(); - }); + runInTransaction(() -> myResourceIndexedComboStringUniqueDao.deleteAll()); executeReindex(); runInTransaction(() -> { - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + List uniques = myResourceIndexedComboStringUniqueDao.findAll(); assertEquals(1, uniques.size(), uniques.toString()); assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); @@ -1538,10 +1584,10 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd); try { - myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); fail(); } catch (ResourceVersionConflictException e) { assertThat(e.getMessage()).contains("new unique index created by SearchParameter/patient-gender-birthdate"); @@ -1549,14 +1595,14 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); pt2 = new Patient(); pt2.setId(id2); pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setBirthDateElement(new DateType("2011-01-01")); try { - myPatientDao.update(pt2); + myPatientDao.update(pt2, mySrd); fail(); } catch (ResourceVersionConflictException e) { assertThat(e.getMessage()).contains("new unique index created by SearchParameter/patient-gender-birthdate"); @@ -1572,7 +1618,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualified(); + IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualified(); assertNotNull(id1); ourLog.info("** Replacing"); @@ -1581,14 +1627,14 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { pt1.setId(id1); pt1.setGender(Enumerations.AdministrativeGender.FEMALE); pt1.setBirthDateElement(new DateType("2011-01-01")); - id1 = myPatientDao.update(pt1).getId().toUnqualified(); + id1 = myPatientDao.update(pt1, mySrd).getId().toUnqualified(); assertNotNull(id1); assertEquals("2", id1.getVersionIdPart()); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setBirthDateElement(new DateType("2011-01-01")); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myMessages.clear(); SearchParameterMap params = new SearchParameterMap(); @@ -1596,6 +1642,7 @@ public class FhirResourceDaoR4ComboUniqueParamIT extends BaseComboParamsR4Test { params.add("birthdate", new DateParam("2011-01-01")); IBundleProvider results = myPatientDao.search(params, mySrd); String searchId = results.getUuid(); + assertThat(searchId).isNotBlank(); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id2.getValue()); logCapturedMessages(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConcurrentWriteTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConcurrentWriteTest.java index 65e25eab52d..d021c6e14ce 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConcurrentWriteTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConcurrentWriteTest.java @@ -559,7 +559,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test { } runInTransaction(() -> { - ourLog.info("Uniques:\n * " + myResourceIndexedCompositeStringUniqueDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); + ourLog.info("Uniques:\n * " + myResourceIndexedComboStringUniqueDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); }); // Make sure we saved the object diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index 427b54ad690..c39d3994050 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -19,6 +19,7 @@ import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum; import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; +import ca.uhn.fhir.jpa.delete.job.ReindexTestHelper; import ca.uhn.fhir.jpa.entity.TermValueSet; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.interceptor.ForceOffsetSearchModeInterceptor; @@ -27,7 +28,6 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.submit.svc.ResourceModifiedSubmitterSvc; import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc; import ca.uhn.fhir.jpa.subscription.triggering.SubscriptionTriggeringSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcImpl; @@ -148,13 +148,12 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test @Autowired private ISubscriptionTriggeringSvc mySubscriptionTriggeringSvc; @Autowired - private ResourceModifiedSubmitterSvc myResourceModifiedSubmitterSvc; - @Autowired private ReindexStep myReindexStep; @Autowired private DeleteExpungeStep myDeleteExpungeStep; @Autowired protected SubscriptionTestUtil mySubscriptionTestUtil; + private ReindexTestHelper myReindexTestHelper; @AfterEach @@ -166,7 +165,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myStorageSettings.setDeleteEnabled(new JpaStorageSettings().isDeleteEnabled()); myStorageSettings.setHistoryCountMode(JpaStorageSettings.DEFAULT_HISTORY_COUNT_MODE); myStorageSettings.setIndexMissingFields(new JpaStorageSettings().getIndexMissingFields()); - myStorageSettings.setInlineResourceTextBelowSize(new JpaStorageSettings().getInlineResourceTextBelowSize()); myStorageSettings.setMassIngestionMode(new JpaStorageSettings().isMassIngestionMode()); myStorageSettings.setMatchUrlCacheEnabled(new JpaStorageSettings().isMatchUrlCacheEnabled()); myStorageSettings.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new JpaStorageSettings().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets()); @@ -175,6 +173,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myStorageSettings.setRespectVersionsForSearchIncludes(new JpaStorageSettings().isRespectVersionsForSearchIncludes()); myStorageSettings.setTagStorageMode(new JpaStorageSettings().getTagStorageMode()); myStorageSettings.setExpungeEnabled(false); + myStorageSettings.setUniqueIndexesEnabled(new JpaStorageSettings().isUniqueIndexesEnabled()); + myStorageSettings.setUniqueIndexesCheckedBeforeSave(new JpaStorageSettings().isUniqueIndexesCheckedBeforeSave()); myFhirContext.getParserOptions().setStripVersionsFromReferences(true); TermReadSvcImpl.setForceDisableHibernateSearchForUnitTest(false); @@ -190,6 +190,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test // Pre-cache all StructureDefinitions so that query doesn't affect other counts myValidationSupport.invalidateCaches(); myValidationSupport.fetchAllStructureDefinitions(); + + myReindexTestHelper = new ReindexTestHelper(myFhirContext, myDaoRegistry, mySearchParamRegistry); } /** @@ -1068,6 +1070,45 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + @Test + public void testReindexJob_ComboParamIndexesInUse() { + myStorageSettings.setUniqueIndexesEnabled(true); + myReindexTestHelper.createUniqueCodeSearchParameter(); + myReindexTestHelper.createNonUniqueStatusAndCodeSearchParameter(); + + Bundle inputBundle = myReindexTestHelper.createTransactionBundleWith20Observation(false); + Bundle transactionResonse = mySystemDao.transaction(mySrd, inputBundle); + ResourceIdListWorkChunkJson data = new ResourceIdListWorkChunkJson(); + transactionResonse + .getEntry() + .stream() + .map(t->new IdType(t.getResponse().getLocation())) + .forEach(t->data.addTypedPid("Observation", t.getIdPartAsLong())); + + runInTransaction(() -> { + assertEquals(24L, myResourceTableDao.count()); + assertEquals(20L, myResourceIndexedComboStringUniqueDao.count()); + assertEquals(20L, myResourceIndexedComboTokensNonUniqueDao.count()); + }); + + ReindexJobParameters params = new ReindexJobParameters() + .setOptimizeStorage(ReindexParameters.OptimizeStorageModeEnum.NONE) + .setReindexSearchParameters(ReindexParameters.ReindexSearchParametersEnum.ALL) + .setOptimisticLock(false); + + // execute + myCaptureQueriesListener.clear(); + RunOutcome outcome = myReindexStep.doReindex(data, mock(IJobDataSink.class), "123", "456", params); + assertEquals(20, outcome.getRecordsProcessed()); + + // validate + assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + } + public void assertNoPartitionSelectors() { List selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread(); @@ -3102,6 +3143,63 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test } + @Test + public void testTransaction_ComboParamIndexesInUse() { + myStorageSettings.setUniqueIndexesEnabled(true); + myReindexTestHelper.createUniqueCodeSearchParameter(); + myReindexTestHelper.createNonUniqueStatusAndCodeSearchParameter(); + + // Create resources for the first time + myCaptureQueriesListener.clear(); + Bundle inputBundle = myReindexTestHelper.createTransactionBundleWith20Observation(true); + mySystemDao.transaction(mySrd, inputBundle); + assertEquals(21, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(78, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + // Now run the transaction again - It should not need too many SELECTs + myCaptureQueriesListener.clear(); + inputBundle = myReindexTestHelper.createTransactionBundleWith20Observation(true); + mySystemDao.transaction(mySrd, inputBundle); + assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + + } + + @Test + public void testTransaction_ComboParamIndexesInUse_NoPreCheck() { + myStorageSettings.setUniqueIndexesEnabled(true); + myStorageSettings.setUniqueIndexesCheckedBeforeSave(false); + + myReindexTestHelper.createUniqueCodeSearchParameter(); + myReindexTestHelper.createNonUniqueStatusAndCodeSearchParameter(); + + // Create resources for the first time + myCaptureQueriesListener.clear(); + Bundle inputBundle = myReindexTestHelper.createTransactionBundleWith20Observation(true); + mySystemDao.transaction(mySrd, inputBundle); + myCaptureQueriesListener.logSelectQueries(); + assertEquals(1, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(7, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + // Now run the transaction again - It should not need too many SELECTs + myCaptureQueriesListener.clear(); + inputBundle = myReindexTestHelper.createTransactionBundleWith20Observation(true); + mySystemDao.transaction(mySrd, inputBundle); + assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + + } + /** * See the class javadoc before changing the counts in this test! */ diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index 899e2bcde2d..f68ed449a06 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -4487,7 +4487,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { ArgumentCaptor captor = ArgumentCaptor.forClass(HookParams.class); verify(interceptor, times(1)).invoke(ArgumentMatchers.eq(Pointcut.JPA_PERFTRACE_WARNING), captor.capture()); StorageProcessingMessage message = captor.getValue().get(StorageProcessingMessage.class); - assertEquals("This search uses an unqualified resource(a parameter in a chain without a resource type). This is less efficient than using a qualified type. If you know what you're looking for, try qualifying it using the form: 'entity:[resourceType]'", message.getMessage()); + assertEquals("This search uses an unqualified resource(a parameter in a chain without a resource type). This is less efficient than using a qualified type. If you know what you're looking for, try qualifying it using the form: 'entity:[resourceType]=[id] or entity=[resourceType]/[id]'", message.getMessage()); } @Test diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java index b1c0a5c9f88..69b55c35813 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java @@ -21,6 +21,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTag; @@ -54,6 +55,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Observation; @@ -68,6 +70,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -165,16 +169,15 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); List selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread(); - assertThat(selectQueries).hasSize(2); - // Look up the partition - assertThat(selectQueries.get(0).getSql(true, false).toLowerCase()).contains(" from hfj_partition "); + assertEquals(1, selectQueries.size()); // Look up the referenced subject/patient - assertThat(selectQueries.get(1).getSql(true, false).toLowerCase()).contains(" from hfj_resource "); - assertEquals(0, StringUtils.countMatches(selectQueries.get(1).getSql(true, false).toLowerCase(), "partition")); + String sql = selectQueries.get(0).getSql(true, false).toLowerCase(); + assertThat(sql).contains(" from hfj_resource "); + assertEquals(0, StringUtils.countMatches(selectQueries.get(0).getSql(true, false).toLowerCase(), "partition")); runInTransaction(() -> { List resLinks = myResourceLinkDao.findAll(); - ourLog.info("Resource links:\n{}", resLinks.toString()); + ourLog.info("Resource links:\n{}", resLinks); assertEquals(2, resLinks.size()); assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid()); @@ -223,7 +226,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { runInTransaction(() -> { List resLinks = myResourceLinkDao.findAll(); - ourLog.info("Resource links:\n{}", resLinks.toString()); + ourLog.info("Resource links:\n{}", resLinks); assertEquals(2, resLinks.size()); assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); assertEquals(patientId.getIdPart(), resLinks.get(0).getTargetResourceId()); @@ -270,7 +273,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { runInTransaction(() -> { List resLinks = myResourceLinkDao.findAll(); - ourLog.info("Resource links:\n{}", resLinks.toString()); + ourLog.info("Resource links:\n{}", resLinks); assertEquals(2, resLinks.size()); assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid()); @@ -294,7 +297,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { runInTransaction(() -> { List resLinks = myResourceLinkDao.findAll(); - ourLog.info("Resource links:\n{}", resLinks.toString()); + ourLog.info("Resource links:\n{}", resLinks); assertEquals(2, resLinks.size()); assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); assertEquals(patientId.getIdPart(), resLinks.get(0).getTargetResourceId()); @@ -396,7 +399,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { @Test public void testCreate_ServerId_WithPartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); createRequestId(); addCreatePartition(myPartitionId, myPartitionDate); @@ -409,7 +412,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { p.getMeta().addTag("http://system", "code", "diisplay"); p.addName().setFamily("FAM"); p.addIdentifier().setSystem("system").setValue("value"); - p.setBirthDate(new Date()); + p.setBirthDateElement(new DateType("2020-01-01")); p.getManagingOrganization().setReferenceElement(orgId); Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); @@ -470,17 +473,20 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertLocalDateFromDbMatches(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate()); // HFJ_IDX_CMP_STRING_UNIQ - List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId); + List uniques = myResourceIndexedComboStringUniqueDao.findAllForResourceIdForUnitTest(patientId); assertEquals(1, uniques.size()); assertEquals(myPartitionId, uniques.get(0).getPartitionId().getPartitionId().intValue()); assertLocalDateFromDbMatches(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate()); }); + myCaptureQueriesListener.clear(); + + } @Test public void testCreate_ServerId_DefaultPartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); createRequestId(); addCreateDefaultPartition(myPartitionDate); @@ -500,29 +506,29 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { runInTransaction(() -> { // HFJ_RESOURCE ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); - assertEquals(null, resourceTable.getPartitionId().getPartitionId()); + assertNull(resourceTable.getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); // HFJ_RES_TAG List tags = myResourceTagDao.findAll(); assertEquals(1, tags.size()); - assertEquals(null, tags.get(0).getPartitionId().getPartitionId()); + assertNull(tags.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, tags.get(0).getPartitionId().getPartitionDate()); // HFJ_RES_VER ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L); - assertEquals(null, version.getPartitionId().getPartitionId()); + assertNull(version.getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, version.getPartitionId().getPartitionDate()); // HFJ_HISTORY_TAG List historyTags = myResourceHistoryTagDao.findAll(); assertEquals(1, historyTags.size()); - assertEquals(null, historyTags.get(0).getPartitionId().getPartitionId()); + assertNull(historyTags.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, historyTags.get(0).getPartitionId().getPartitionDate()); // HFJ_RES_VER_PROV assertNotNull(version.getProvenance()); - assertEquals(null, version.getProvenance().getPartitionId().getPartitionId()); + assertNull(version.getProvenance().getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, version.getProvenance().getPartitionId().getPartitionDate()); // HFJ_SPIDX_STRING @@ -532,34 +538,34 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertThat(stringsDesc).doesNotContain("_text"); assertThat(stringsDesc).doesNotContain("_content"); assertEquals(9, strings.size(), stringsDesc); - assertEquals(null, strings.get(0).getPartitionId().getPartitionId()); + assertNull(strings.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, strings.get(0).getPartitionId().getPartitionDate()); // HFJ_SPIDX_DATE List dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId); ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * "))); assertEquals(2, dates.size()); - assertEquals(null, dates.get(0).getPartitionId().getPartitionId()); + assertNull(dates.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, dates.get(0).getPartitionId().getPartitionDate()); - assertEquals(null, dates.get(1).getPartitionId().getPartitionId()); + assertNull(dates.get(1).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, dates.get(1).getPartitionId().getPartitionDate()); // HFJ_RES_LINK List resourceLinks = myResourceLinkDao.findAllForSourceResourceId(patientId); assertEquals(1, resourceLinks.size()); - assertEquals(null, resourceLinks.get(0).getPartitionId().getPartitionId()); + assertNull(resourceLinks.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, resourceLinks.get(0).getPartitionId().getPartitionDate()); // HFJ_RES_PARAM_PRESENT List presents = mySearchParamPresentDao.findAllForResource(resourceTable); assertEquals(3, presents.size()); - assertEquals(null, presents.get(0).getPartitionId().getPartitionId()); + assertNull(presents.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate()); // HFJ_IDX_CMP_STRING_UNIQ - List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId); + List uniques = myResourceIndexedComboStringUniqueDao.findAllForResourceIdForUnitTest(patientId); assertEquals(1, uniques.size()); - assertEquals(null, uniques.get(0).getPartitionId().getPartitionId()); + assertNull(uniques.get(0).getPartitionId().getPartitionId()); assertLocalDateFromDbMatches(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate()); }); @@ -645,7 +651,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { @Test public void testCreateInTransaction_ServerId_WithPartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); createRequestId(); addCreatePartition(myPartitionId, myPartitionDate); @@ -716,7 +722,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { startRequest.setJobDefinitionId(DeleteExpungeAppCtx.JOB_DELETE_EXPUNGE); // execute - Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); // Validate JobInstance outcome = myBatch2JobHelper.awaitJobCompletion(startResponse); @@ -818,7 +824,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertLocalDateFromDbMatches(expected, actual); // HFJ_SPIDX_TOKEN - ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); + ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(ResourceIndexedSearchParamToken::toString).collect(Collectors.joining("\n * "))); assertEquals(3, myResourceIndexedSearchParamTokenDao.countForResourceId(patientId)); }); @@ -840,7 +846,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertLocalDateFromDbMatches(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); // HFJ_SPIDX_TOKEN - ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); + ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(ResourceIndexedSearchParamToken::toString).collect(Collectors.joining("\n * "))); assertEquals(3, myResourceIndexedSearchParamTokenDao.countForResourceId(patientId)); // HFJ_RES_VER @@ -1869,7 +1875,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { // Date param runInTransaction(() -> { - ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); + ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * "))); }); addReadPartition(1); myCaptureQueriesListener.clear(); @@ -2522,14 +2528,15 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { @Test public void testSearch_UniqueParam_SearchAllPartitions() { - createUniqueCompositeSp(); + createUniqueComboSp(); - IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01")); + IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM")); addReadAllPartitions(); myCaptureQueriesListener.clear(); SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAM")); map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); map.setLoadSynchronous(true); IBundleProvider results = myPatientDao.search(map, mySrd); @@ -2539,20 +2546,21 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("Search SQL:\n{}", searchSql); - assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); - assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING = 'Patient?birthdate=2020-01-01'")); + assertThat(searchSql).doesNotContain("PARTITION_ID"); + assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?birthdate=2020-01-01&family=FAM'"); } @Test public void testSearch_UniqueParam_SearchOnePartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); - IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01")); + IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM")); addReadPartition(1); myCaptureQueriesListener.clear(); SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAM")); map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); map.setLoadSynchronous(true); IBundleProvider results = myPatientDao.search(map, mySrd); @@ -2562,8 +2570,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("Search SQL:\n{}", searchSql); - assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); - assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING = 'Patient?birthdate=2020-01-01'")); + assertThat(searchSql).containsOnlyOnce( "PARTITION_ID = '1'"); + assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?birthdate=2020-01-01&family=FAM'"); // Same query, different partition addReadPartition(2); @@ -2578,9 +2586,69 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { } + + @ParameterizedTest + @ValueSource(strings = {"ALL", "ONE", "MANY"}) + public void testSearch_NonUniqueComboParam(String theReadPartitions) { + createNonUniqueComboSp(); + + IIdType orgId = createOrganization(withId("A"), withPartition(myPartitionId), withName("My Org")).toUnqualifiedVersionless(); + IIdType orgId2 = createOrganization(withId("B"), withPartition(myPartitionId2), withName("My Org")).toUnqualifiedVersionless(); + // Matching + IIdType patientId = createPatient(withPartition(myPartitionId), withFamily("FAMILY"), withOrganization(orgId)); + // Non matching + createPatient(withPartition(myPartitionId), withFamily("WRONG"), withOrganization(orgId)); + createPatient(withPartition(myPartitionId2), withFamily("FAMILY"), withOrganization(orgId2)); + + logAllNonUniqueIndexes(); + + switch (theReadPartitions) { + case "ALL": + addReadAllPartitions(); + break; + case "ONE": + addReadPartition(myPartitionId); + break; + case "MANY": + addReadPartition(myPartitionId, myPartitionId4); + break; + default: + throw new IllegalStateException(); + } + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.add(Patient.SP_ORGANIZATION, new ReferenceParam(orgId)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map, mySrd); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids).containsExactly(patientId); + + ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true)); + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + + switch (theReadPartitions) { + case "ALL": + assertThat(searchSql).doesNotContain("t0.PARTITION_ID"); + break; + case "ONE": + assertThat(searchSql).contains("t0.PARTITION_ID = '1'"); + break; + case "MANY": + assertThat(searchSql).contains("t0.PARTITION_ID IN ('1','4')"); + break; + default: + throw new IllegalStateException(); + } + + assertThat(searchSql).containsOnlyOnce("t0.HASH_COMPLETE = '-2879121558074554863'"); + } + + @Test public void testSearch_RefParam_TargetPid_SearchOnePartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); IIdType patientId = createPatient(withPartition(myPartitionId), withBirthdate("2020-01-01")); IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId)); @@ -2617,7 +2685,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { @Test public void testSearch_RefParam_TargetPid_SearchDefaultPartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); IIdType patientId = createPatient(withPartition(null), withBirthdate("2020-01-01")); IIdType observationId = createObservation(withPartition(null), withSubject(patientId)); @@ -2654,7 +2722,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { @Test public void testSearch_RefParam_TargetForcedId_SearchOnePartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); IIdType patientId = createPatient(withPartition(myPartitionId), withId("ONE"), withBirthdate("2020-01-01")); IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId)); @@ -2724,7 +2792,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { @Test public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() { - createUniqueCompositeSp(); + createUniqueComboSp(); IIdType patientId = createPatient(withPartition(null), withId("ONE"), withBirthdate("2020-01-01")); IIdType observationId = createObservation(withPartition(null), withSubject(patientId)); @@ -2890,7 +2958,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { Bundle output = mySystemDao.transaction(requestDetails, input); myCaptureQueriesListener.logSelectQueries(); - assertEquals(18, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(17, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); assertEquals(6189, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); assertEquals(418, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexJobTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexJobTest.java index 8b692cb69c3..4cbdcdd56c4 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexJobTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexJobTest.java @@ -15,6 +15,8 @@ import ca.uhn.fhir.jpa.api.dao.ReindexParameters; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.test.BaseJpaR4Test; @@ -44,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +@SuppressWarnings("SqlDialectInspection") public class ReindexJobTest extends BaseJpaR4Test { @Autowired @@ -65,7 +68,6 @@ public class ReindexJobTest extends BaseJpaR4Test { @AfterEach public void after() { myInterceptorRegistry.unregisterAllAnonymousInterceptors(); - myStorageSettings.setInlineResourceTextBelowSize(new JpaStorageSettings().getInlineResourceTextBelowSize()); myStorageSettings.setStoreMetaSourceInformation(new JpaStorageSettings().getStoreMetaSourceInformation()); myStorageSettings.setPreserveRequestIdInResourceBody(new JpaStorageSettings().isPreserveRequestIdInResourceBody()); } @@ -98,8 +100,6 @@ public class ReindexJobTest extends BaseJpaR4Test { } }); - myStorageSettings.setInlineResourceTextBelowSize(10000); - // execute JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); @@ -108,7 +108,7 @@ public class ReindexJobTest extends BaseJpaR4Test { .setOptimizeStorage(ReindexParameters.OptimizeStorageModeEnum.CURRENT_VERSION) .setReindexSearchParameters(ReindexParameters.ReindexSearchParametersEnum.NONE) ); - Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); myBatch2JobHelper.awaitJobCompletion(startResponse); // validate @@ -165,7 +165,7 @@ public class ReindexJobTest extends BaseJpaR4Test { .setOptimizeStorage(ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS) .setReindexSearchParameters(ReindexParameters.ReindexSearchParametersEnum.NONE) ); - Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); myBatch2JobHelper.awaitJobCompletion(startResponse); // validate @@ -224,7 +224,7 @@ public class ReindexJobTest extends BaseJpaR4Test { .setOptimizeStorage(ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS) .setReindexSearchParameters(ReindexParameters.ReindexSearchParametersEnum.NONE) ); - Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); myBatch2JobHelper.awaitJobCompletion(startResponse); // validate @@ -251,8 +251,6 @@ public class ReindexJobTest extends BaseJpaR4Test { myPatientDao.delete(nextId, mySrd); } - myStorageSettings.setInlineResourceTextBelowSize(10000); - // execute JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); @@ -299,7 +297,7 @@ public class ReindexJobTest extends BaseJpaR4Test { JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); startRequest.setParameters(parameters); - Batch2JobStartResponse res = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse res = myJobCoordinator.startInstance(mySrd, startRequest); myBatch2JobHelper.awaitJobCompletion(res); // validate @@ -338,7 +336,7 @@ public class ReindexJobTest extends BaseJpaR4Test { JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); startRequest.setParameters(parameters); - Batch2JobStartResponse res = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse res = myJobCoordinator.startInstance(mySrd, startRequest); myBatch2JobHelper.awaitJobCompletion(res); // then @@ -369,7 +367,7 @@ public class ReindexJobTest extends BaseJpaR4Test { JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); startRequest.setParameters(new ReindexJobParameters()); - Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); myBatch2JobHelper.awaitJobCompletion(startResponse); // validate @@ -380,8 +378,8 @@ public class ReindexJobTest extends BaseJpaR4Test { @Test public void testReindex_DuplicateResourceBeforeEnforceUniqueShouldSaveWarning() { - myReindexTestHelper.createObservationWithCode(); - myReindexTestHelper.createObservationWithCode(); + myReindexTestHelper.createObservationWithStatusAndCode(); + myReindexTestHelper.createObservationWithStatusAndCode(); DaoMethodOutcome searchParameter = myReindexTestHelper.createUniqueCodeSearchParameter(); @@ -396,6 +394,73 @@ public class ReindexJobTest extends BaseJpaR4Test { assertThat(myJob.getWarningMessages()).contains("Failed to reindex resource because unique search parameter " + searchParameter.getEntity().getIdDt().toVersionless().toString()); } + /** + * This test will fail and can be deleted if we make the hash columns on + * the unique index table non-nullable. + */ + @Test + public void testReindex_ComboUnique_HashesShouldBePopulated() { + myReindexTestHelper.createUniqueCodeSearchParameter(); + myReindexTestHelper.createObservationWithStatusAndCode(); + logAllUniqueIndexes(); + + // Clear hashes + runInTransaction(()->{ + assertEquals(1, myEntityManager.createNativeQuery("UPDATE HFJ_IDX_CMP_STRING_UNIQ SET HASH_COMPLETE = null WHERE HASH_COMPLETE IS NOT NULL").executeUpdate()); + assertEquals(0, myEntityManager.createNativeQuery("UPDATE HFJ_IDX_CMP_STRING_UNIQ SET HASH_COMPLETE = null WHERE HASH_COMPLETE IS NOT NULL").executeUpdate()); + assertEquals(1, myEntityManager.createNativeQuery("UPDATE HFJ_IDX_CMP_STRING_UNIQ SET HASH_COMPLETE_2 = null WHERE HASH_COMPLETE_2 IS NOT NULL").executeUpdate()); + assertEquals(0, myEntityManager.createNativeQuery("UPDATE HFJ_IDX_CMP_STRING_UNIQ SET HASH_COMPLETE_2 = null WHERE HASH_COMPLETE_2 IS NOT NULL").executeUpdate()); + }); + + // Run a reindex + JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); + startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); + startRequest.setParameters(new ReindexJobParameters()); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(new SystemRequestDetails(), startRequest); + JobInstance myJob = myBatch2JobHelper.awaitJobCompletion(startResponse.getInstanceId(), 999); + assertEquals(StatusEnum.COMPLETED, myJob.getStatus()); + + // Verify that hashes are repopulated + runInTransaction(()->{ + List indexes = myResourceIndexedComboStringUniqueDao.findAll(); + assertEquals(1, indexes.size()); + assertThat(indexes.get(0).getHashComplete()).isNotNull().isNotZero(); + assertThat(indexes.get(0).getHashComplete2()).isNotNull().isNotZero(); + }); + } + + /** + * This test will fail and can be deleted if we make the hash columns on + * the unique index table non-nullable. + */ + @Test + public void testReindex_ComboNonUnique_HashesShouldBePopulated() { + myReindexTestHelper.createNonUniqueStatusAndCodeSearchParameter(); + myReindexTestHelper.createObservationWithStatusAndCode(); + logAllNonUniqueIndexes(); + + // Set hash wrong + runInTransaction(()->{ + assertEquals(1, myEntityManager.createNativeQuery("UPDATE HFJ_IDX_CMB_TOK_NU SET HASH_COMPLETE = 0 WHERE HASH_COMPLETE != 0").executeUpdate()); + assertEquals(0, myEntityManager.createNativeQuery("UPDATE HFJ_IDX_CMB_TOK_NU SET HASH_COMPLETE = 0 WHERE HASH_COMPLETE != 0").executeUpdate()); + }); + + // Run a reindex + JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); + startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); + startRequest.setParameters(new ReindexJobParameters()); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(new SystemRequestDetails(), startRequest); + JobInstance myJob = myBatch2JobHelper.awaitJobCompletion(startResponse.getInstanceId(), 999); + assertEquals(StatusEnum.COMPLETED, myJob.getStatus()); + + // Verify that hashes are repopulated + runInTransaction(()->{ + List indexes = myResourceIndexedComboTokensNonUniqueDao.findAll(); + assertEquals(1, indexes.size()); + assertEquals(-4763890811650597657L, indexes.get(0).getHashComplete()); + }); + } + @Test public void testReindex_ExceptionThrownDuringWrite() { // setup @@ -415,7 +480,7 @@ public class ReindexJobTest extends BaseJpaR4Test { JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); startRequest.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); startRequest.setParameters(new ReindexJobParameters()); - Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(startRequest); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); JobInstance outcome = myBatch2JobHelper.awaitJobCompletion(startResponse); // Verify @@ -461,7 +526,7 @@ public class ReindexJobTest extends BaseJpaR4Test { myStorageSettings.setMarkResourcesForReindexingUponSearchParameterChange(true); // create an Observation resource and SearchParameter for it to trigger re-indexing - myReindexTestHelper.createObservationWithCode(); + myReindexTestHelper.createObservationWithStatusAndCode(); myReindexTestHelper.createCodeSearchParameter(); // check that reindex job was created diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexTestHelper.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexTestHelper.java index 9ed3490580f..f5597a0142d 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexTestHelper.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/delete/job/ReindexTestHelper.java @@ -11,11 +11,13 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; +import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.BundleUtil; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations; @@ -124,6 +126,25 @@ public class ReindexTestHelper { return daoMethodOutcome; } + public void createNonUniqueStatusAndCodeSearchParameter() { + createCodeSearchParameter(); + createStatusSearchParameter(); + SearchParameter uniqueCodeSp = new SearchParameter(); + uniqueCodeSp.setId("SearchParameter/nonunique-status-code"); + uniqueCodeSp.addExtension(new Extension().setUrl("http://hapifhir.io/fhir/StructureDefinition/sp-unique").setValue(new BooleanType(false))); + uniqueCodeSp.setStatus(Enumerations.PublicationStatus.ACTIVE); + uniqueCodeSp.setCode("observation-status-and-code"); + uniqueCodeSp.addBase("Observation"); + uniqueCodeSp.setType(Enumerations.SearchParamType.COMPOSITE); + uniqueCodeSp.setExpression("Observation"); + uniqueCodeSp.addComponent(new SearchParameter.SearchParameterComponentComponent().setDefinition("SearchParameter/clinical-code").setExpression("Observation")); + uniqueCodeSp.addComponent(new SearchParameter.SearchParameterComponentComponent().setDefinition("SearchParameter/clinical-status").setExpression("Observation")); + + mySearchParameterDao.update(uniqueCodeSp); + mySearchParamRegistry.forceRefresh(); + } + + public DaoMethodOutcome createCodeSearchParameter() { SearchParameter codeSp = new SearchParameter(); codeSp.setId("SearchParameter/clinical-code"); @@ -138,6 +159,20 @@ public class ReindexTestHelper { return daoMethodOutcome; } + public DaoMethodOutcome createStatusSearchParameter() { + SearchParameter codeSp = new SearchParameter(); + codeSp.setId("SearchParameter/clinical-status"); + codeSp.setStatus(Enumerations.PublicationStatus.ACTIVE); + codeSp.setCode("status"); + codeSp.addBase("Observation"); + codeSp.setType(Enumerations.SearchParamType.TOKEN); + codeSp.setExpression("Observation.status"); + + DaoMethodOutcome daoMethodOutcome = mySearchParameterDao.update(codeSp); + mySearchParamRegistry.forceRefresh(); + return daoMethodOutcome; + } + public IIdType createObservationWithAlleleExtension(Observation.ObservationStatus theStatus) { Observation observation = buildObservationWithAlleleExtension(theStatus); return myObservationDao.create(observation).getId(); @@ -151,13 +186,14 @@ public class ReindexTestHelper { return observation; } - public IIdType createObservationWithCode() { - Observation observation = buildObservationWithCode(); + public IIdType createObservationWithStatusAndCode() { + Observation observation = buildObservationWithStatusAndCode(); return myObservationDao.create(observation).getId(); } - public Observation buildObservationWithCode() { + public Observation buildObservationWithStatusAndCode() { Observation observation = new Observation(); + observation.setStatus(Observation.ObservationStatus.FINAL); CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding(new Coding().setCode("29463-7").setSystem("http://loinc.org").setDisplay("Body Weight")); observation.setCode(codeableConcept); @@ -206,4 +242,28 @@ public class ReindexTestHelper { .execute(); return BundleUtil.toListOfResourceIds(myFhirContext, result); } + + /** + * Creates a transaction bundle with 20 Observations which will create rows for indexes + * created by {@link #createNonUniqueStatusAndCodeSearchParameter()} and + * {@link #createUniqueCodeSearchParameter()}. + */ + public Bundle createTransactionBundleWith20Observation(boolean theUseClientAssignedIds) { + BundleBuilder bb = new BundleBuilder(myFhirContext); + for (int i = 0; i < 20; i++) { + Observation observation = new Observation(); + if (theUseClientAssignedIds) { + observation.setId("OBS" + i); + } + observation.addIdentifier().setSystem("http://foo").setValue("ident" + i); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.getCode().addCoding().setSystem("http://foo").setCode("" + i); + if (theUseClientAssignedIds) { + bb.addTransactionUpdateEntry(observation); + } else { + bb.addTransactionCreateEntry(observation); + } + } + return bb.getBundleTyped(); + } } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java index df20dea6542..f5cc4817110 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java @@ -246,14 +246,12 @@ public class GiantTransactionPerfTest { myDaoSearchParamSynchronizer = new DaoSearchParamSynchronizer(); myDaoSearchParamSynchronizer.setEntityManager(myEntityManager); + myDaoSearchParamSynchronizer.setStorageSettings(myStorageSettings); mySearchParamWithInlineReferencesExtractor = new SearchParamWithInlineReferencesExtractor(); mySearchParamWithInlineReferencesExtractor.setStorageSettings(myStorageSettings); mySearchParamWithInlineReferencesExtractor.setContext(ourFhirContext); - mySearchParamWithInlineReferencesExtractor.setPartitionSettings(this.myPartitionSettings); mySearchParamWithInlineReferencesExtractor.setSearchParamExtractorService(mySearchParamExtractorSvc); - mySearchParamWithInlineReferencesExtractor.setSearchParamRegistry(mySearchParamRegistry); - mySearchParamWithInlineReferencesExtractor.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer); myEobDao = new JpaResourceDao<>(); myEobDao.setContext(ourFhirContext); diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/DuplicateIndexR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/DuplicateIndexR5Test.java index 692cf339deb..d88d136d089 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/DuplicateIndexR5Test.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/DuplicateIndexR5Test.java @@ -13,16 +13,18 @@ import ca.uhn.fhir.util.HapiExtensions; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.Enumerations; -import org.hl7.fhir.r5.model.SearchParameter; +import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Reference; +import org.hl7.fhir.r5.model.SearchParameter; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; public class DuplicateIndexR5Test extends BaseJpaR5Test { + public static final String SEARCH_PARAMETER_PATIENT_NAMES_AND_GENDER = "SearchParameter/patient-names-and-gender"; + @Test public void testDuplicateTokensClearedOnUpdate() { // Setup @@ -168,7 +170,7 @@ public class DuplicateIndexR5Test extends BaseJpaR5Test { dupe0.setResource(param.getResource()); dupe0.setHashComplete(param.getHashComplete()); dupe0.setIndexString(param.getIndexString()); - dupe0.setSearchParameterId(param.getSearchParameterId()); + dupe0.setSearchParameterId(new IdType(SEARCH_PARAMETER_PATIENT_NAMES_AND_GENDER)); dupe0.calculateHashes(); myResourceIndexedComboTokensNonUniqueDao.save(dupe0); @@ -178,7 +180,7 @@ public class DuplicateIndexR5Test extends BaseJpaR5Test { dupe1.setResource(param.getResource()); dupe1.setHashComplete(param.getHashComplete()); dupe1.setIndexString(param.getIndexString()); - dupe1.setSearchParameterId(param.getSearchParameterId()); + dupe1.setSearchParameterId(new IdType(SEARCH_PARAMETER_PATIENT_NAMES_AND_GENDER)); dupe1.calculateHashes(); myResourceIndexedComboTokensNonUniqueDao.save(dupe1); }); @@ -289,7 +291,7 @@ public class DuplicateIndexR5Test extends BaseJpaR5Test { mySearchParameterDao.update(sp, mySrd); sp = new SearchParameter(); - sp.setId("SearchParameter/patient-names-and-gender"); + sp.setId(SEARCH_PARAMETER_PATIENT_NAMES_AND_GENDER); sp.setType(Enumerations.SearchParamType.COMPOSITE); sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.addBase(Enumerations.VersionIndependentResourceTypesAll.PATIENT); diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java index 3d2cdc6511a..9d535aa67c5 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java @@ -50,7 +50,6 @@ import ca.uhn.fhir.jpa.dao.data.IPartitionDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao; -import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao; @@ -193,7 +192,6 @@ import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.BindingKind; -import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -277,8 +275,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil @Autowired protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao; @Autowired - protected IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao; - @Autowired protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao; @Autowired @Qualifier("myAllergyIntoleranceDaoR4") diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java index 2a7e57146d4..2ff5ddb261a 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java @@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao; @@ -61,6 +62,7 @@ import ca.uhn.fhir.jpa.entity.TermValueSet; import ca.uhn.fhir.jpa.entity.TermValueSetConcept; import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; @@ -232,6 +234,8 @@ public abstract class BaseJpaTest extends BaseTest { protected IResourceIndexedSearchParamCoordsDao myResourceIndexedSearchParamCoordsDao; @Autowired protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao; + @Autowired + protected IResourceIndexedComboStringUniqueDao myResourceIndexedComboStringUniqueDao; @Autowired(required = false) protected IFulltextSearchSvc myFulltestSearchSvc; @Autowired(required = false) @@ -535,6 +539,12 @@ public abstract class BaseJpaTest extends BaseTest { }); } + protected void logAllUniqueIndexes() { + runInTransaction(() -> { + ourLog.info("Unique indexes:\n * {}", myResourceIndexedComboStringUniqueDao.findAll().stream().map(ResourceIndexedComboStringUnique::toString).collect(Collectors.joining("\n * "))); + }); + } + protected void logAllTokenIndexes() { runInTransaction(() -> { ourLog.info("Token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(ResourceIndexedSearchParamToken::toString).collect(Collectors.joining("\n * "))); diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java index 6f6921844cc..17315fa3e22 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasksTest.java @@ -1,13 +1,184 @@ package ca.uhn.fhir.jpa.migrate.tasks; +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.fhir.jpa.migrate.HapiMigrator; +import ca.uhn.fhir.jpa.migrate.MigrationResult; +import ca.uhn.fhir.jpa.migrate.MigrationTaskList; +import ca.uhn.fhir.jpa.migrate.taskdef.InitializeSchemaTask; +import ca.uhn.fhir.util.VersionEnum; +import jakarta.annotation.Nonnull; +import org.apache.commons.dbcp2.BasicDataSource; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.ColumnMapRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.jdbc.support.lob.LobCreator; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Duration; import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; public class HapiFhirJpaMigrationTasksTest { + private static final Logger ourLog = LoggerFactory.getLogger(HapiFhirJpaMigrationTasksTest.class); + private static final String MIGRATION_TABLE_NAME = "HFJ_FLY_MIGRATOR"; + private final BasicDataSource myDataSource = newDataSource(); + private final JdbcTemplate myJdbcTemplate = new JdbcTemplate(myDataSource); + @Test public void testCreate() { new HapiFhirJpaMigrationTasks(Collections.emptySet()); } + + /** + * Verify migration task 20240617.4 which creates hashes on the unique combo + * search param table if they aren't already present. Hash columns were only + * added in 7.4.0 so this backfills them. + */ + @Test + public void testCreateUniqueComboParamHashes() { + /* + * Setup + */ + + // Create migrator and initialize schema using a static version + // of the schema from the 7.2.0 release + HapiFhirJpaMigrationTasks tasks = new HapiFhirJpaMigrationTasks(Set.of()); + HapiMigrator migrator = new HapiMigrator(MIGRATION_TABLE_NAME, myDataSource, DriverTypeEnum.H2_EMBEDDED); + migrator.addTask(new InitializeSchemaTask("7.2.0", "20180115.0", + new SchemaInitializationProvider( + "HAPI FHIR", "/jpa_h2_schema_720", "HFJ_RESOURCE", true))); + + migrator.createMigrationTableIfRequired(); + migrator.migrate(); + + // Run a second time to run the 7.4.0 migrations + MigrationTaskList allTasks = tasks.getAllTasks(VersionEnum.V7_3_0, VersionEnum.V7_4_0); + migrator.addTasks(allTasks); + migrator.migrate(); + + // Create a unique index row with no hashes populated + insertRow_ResourceTable(); + insertRow_ResourceIndexedComboStringUnique(); + + /* + * Execute + */ + + // Remove the task we're testing from the migrator history, so it runs again + assertEquals(1, myJdbcTemplate.update("DELETE FROM " + MIGRATION_TABLE_NAME + " WHERE version = ?", "7.4.0.20240625.40")); + + // Run the migrator + ourLog.info("About to run the migrator a second time"); + MigrationResult migrationResult = migrator.migrate(); + assertEquals(1, migrationResult.succeededTasks.size()); + assertEquals(0, migrationResult.failedTasks.size()); + + /* + * Verify + */ + + List> rows = myJdbcTemplate.query("SELECT * FROM HFJ_IDX_CMP_STRING_UNIQ", new ColumnMapRowMapper()); + assertEquals(1, rows.size()); + Map row = rows.get(0); + assertThat(row.get("HASH_COMPLETE")).as(row::toString).isEqualTo(-5443017569618195896L); + assertThat(row.get("HASH_COMPLETE_2")).as(row::toString).isEqualTo(-1513800680307323438L); + } + + private void insertRow_ResourceIndexedComboStringUnique() { + myJdbcTemplate.execute( + """ + insert into + HFJ_IDX_CMP_STRING_UNIQ ( + PID, + RES_ID, + IDX_STRING) + values (1, 1, 'Patient?foo=bar') + """); + } + + private void insertRow_ResourceTable() { + myJdbcTemplate.execute( + """ + insert into + HFJ_RESOURCE ( + RES_DELETED_AT, + RES_VERSION, + FHIR_ID, + HAS_TAGS, + RES_PUBLISHED, + RES_UPDATED, + SP_HAS_LINKS, + HASH_SHA256, + SP_INDEX_STATUS, + RES_LANGUAGE, + SP_CMPSTR_UNIQ_PRESENT, + SP_COORDS_PRESENT, + SP_DATE_PRESENT, + SP_NUMBER_PRESENT, + SP_QUANTITY_PRESENT, + SP_STRING_PRESENT, + SP_TOKEN_PRESENT, + SP_URI_PRESENT, + SP_QUANTITY_NRML_PRESENT, + RES_TYPE, + RES_VER, + RES_ID) + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + new AbstractLobCreatingPreparedStatementCallback(new DefaultLobHandler()) { + @Override + protected void setValues(@Nonnull PreparedStatement thePs, @Nonnull LobCreator theLobCreator) throws SQLException { + int i = 1; + thePs.setNull(i++, Types.TIMESTAMP); + thePs.setString(i++, "R4"); + thePs.setString(i++, "ABC"); // FHIR_ID + thePs.setBoolean(i++, false); + thePs.setTimestamp(i++, new Timestamp(System.currentTimeMillis())); + thePs.setTimestamp(i++, new Timestamp(System.currentTimeMillis())); + thePs.setBoolean(i++, false); + thePs.setNull(i++, Types.VARCHAR); + thePs.setLong(i++, 1L); + thePs.setNull(i++, Types.VARCHAR); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); + thePs.setBoolean(i++, false); // SP_QUANTITY_NRML_PRESENT + thePs.setString(i++, "Patient"); + thePs.setLong(i++, 1L); + thePs.setLong(i, 1L); // RES_ID + } + }); + } + + static BasicDataSource newDataSource() { + BasicDataSource retVal = new BasicDataSource(); + retVal.setDriver(new org.h2.Driver()); + retVal.setUrl("jdbc:h2:mem:test_migration-" + UUID.randomUUID() + ";CASE_INSENSITIVE_IDENTIFIERS=TRUE;"); + retVal.setMaxWait(Duration.ofMillis(30000)); + retVal.setUsername(""); + retVal.setPassword(""); + retVal.setMaxTotal(5); + + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/resources/jpa_h2_schema_720/h2.sql b/hapi-fhir-jpaserver-test-utilities/src/test/resources/jpa_h2_schema_720/h2.sql new file mode 100644 index 00000000000..bbdb29e8be7 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/test/resources/jpa_h2_schema_720/h2.sql @@ -0,0 +1,1383 @@ + + create sequence SEQ_BLKEXCOL_PID start with 1 increment by 50; + + create sequence SEQ_BLKEXCOLFILE_PID start with 1 increment by 50; + + create sequence SEQ_BLKEXJOB_PID start with 1 increment by 50; + + create sequence SEQ_BLKIMJOB_PID start with 1 increment by 50; + + create sequence SEQ_BLKIMJOBFILE_PID start with 1 increment by 50; + + create sequence SEQ_CNCPT_MAP_GRP_ELM_TGT_PID start with 1 increment by 50; + + create sequence SEQ_CODESYSTEM_PID start with 1 increment by 50; + + create sequence SEQ_CODESYSTEMVER_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_DESIG_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_MAP_GROUP_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_MAP_GRP_ELM_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_MAP_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_PC_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_PID start with 1 increment by 50; + + create sequence SEQ_CONCEPT_PROP_PID start with 1 increment by 50; + + create sequence SEQ_EMPI_LINK_ID start with 1 increment by 50; + + create sequence SEQ_FORCEDID_ID start with 1 increment by 50; + + create sequence SEQ_HFJ_REVINFO start with 1 increment by 50; + + create sequence SEQ_HISTORYTAG_ID start with 1 increment by 50; + + create sequence SEQ_IDXCMBTOKNU_ID start with 1 increment by 50; + + create sequence SEQ_IDXCMPSTRUNIQ_ID start with 1 increment by 50; + + create sequence SEQ_NPM_PACK start with 1 increment by 50; + + create sequence SEQ_NPM_PACKVER start with 1 increment by 50; + + create sequence SEQ_NPM_PACKVERRES start with 1 increment by 50; + + create sequence SEQ_RES_REINDEX_JOB start with 1 increment by 50; + + create sequence SEQ_RESLINK_ID start with 1 increment by 50; + + create sequence SEQ_RESOURCE_HISTORY_ID start with 1 increment by 50; + + create sequence SEQ_RESOURCE_ID start with 1 increment by 50; + + create sequence SEQ_RESPARMPRESENT_ID start with 1 increment by 50; + + create sequence SEQ_RESTAG_ID start with 1 increment by 50; + + create sequence SEQ_SEARCH start with 1 increment by 50; + + create sequence SEQ_SEARCH_INC start with 1 increment by 50; + + create sequence SEQ_SEARCH_RES start with 1 increment by 50; + + create sequence SEQ_SPIDX_COORDS start with 1 increment by 50; + + create sequence SEQ_SPIDX_DATE start with 1 increment by 50; + + create sequence SEQ_SPIDX_NUMBER start with 1 increment by 50; + + create sequence SEQ_SPIDX_QUANTITY start with 1 increment by 50; + + create sequence SEQ_SPIDX_QUANTITY_NRML start with 1 increment by 50; + + create sequence SEQ_SPIDX_STRING start with 1 increment by 50; + + create sequence SEQ_SPIDX_TOKEN start with 1 increment by 50; + + create sequence SEQ_SPIDX_URI start with 1 increment by 50; + + create sequence SEQ_SUBSCRIPTION_ID start with 1 increment by 50; + + create sequence SEQ_TAGDEF_ID start with 1 increment by 50; + + create sequence SEQ_VALUESET_C_DSGNTN_PID start with 1 increment by 50; + + create sequence SEQ_VALUESET_CONCEPT_PID start with 1 increment by 50; + + create sequence SEQ_VALUESET_PID start with 1 increment by 50; + + create table BT2_JOB_INSTANCE ( + ID varchar(100) not null, + JOB_CANCELLED boolean not null, + CMB_RECS_PROCESSED integer, + CMB_RECS_PER_SEC float(53), + CREATE_TIME timestamp(6) not null, + CUR_GATED_STEP_ID varchar(100), + DEFINITION_ID varchar(100) not null, + DEFINITION_VER integer not null, + END_TIME timestamp(6), + ERROR_COUNT integer, + ERROR_MSG varchar(500), + EST_REMAINING varchar(100), + FAST_TRACKING boolean, + PARAMS_JSON varchar(2000), + PARAMS_JSON_LOB clob, + PARAMS_JSON_VC clob, + PROGRESS_PCT float(53), + REPORT clob, + REPORT_VC clob, + START_TIME timestamp(6), + STAT varchar(20) not null, + TOT_ELAPSED_MILLIS integer, + CLIENT_ID varchar(200), + USER_NAME varchar(200), + UPDATE_TIME timestamp(6), + WARNING_MSG varchar(4000), + WORK_CHUNKS_PURGED boolean not null, + primary key (ID) + ); + + create table BT2_WORK_CHUNK ( + ID varchar(100) not null, + CREATE_TIME timestamp(6) not null, + END_TIME timestamp(6), + ERROR_COUNT integer not null, + ERROR_MSG varchar(500), + INSTANCE_ID varchar(100) not null, + DEFINITION_ID varchar(100) not null, + DEFINITION_VER integer not null, + RECORDS_PROCESSED integer, + SEQ integer not null, + CHUNK_DATA clob, + CHUNK_DATA_VC clob, + START_TIME timestamp(6), + STAT varchar(20) not null, + TGT_STEP_ID varchar(100) not null, + UPDATE_TIME timestamp(6), + WARNING_MSG varchar(4000), + primary key (ID) + ); + + create table HFJ_BINARY_STORAGE ( + CONTENT_ID varchar(200) not null, + BLOB_DATA blob, + CONTENT_TYPE varchar(100) not null, + CONTENT_HASH varchar(128), + PUBLISHED_DATE timestamp(6) not null, + RESOURCE_ID varchar(100) not null, + CONTENT_SIZE bigint, + STORAGE_CONTENT_BIN blob, + primary key (CONTENT_ID) + ); + + create table HFJ_BLK_EXPORT_COLFILE ( + PID bigint not null, + RES_ID varchar(100) not null, + COLLECTION_PID bigint not null, + primary key (PID) + ); + + create table HFJ_BLK_EXPORT_COLLECTION ( + PID bigint not null, + TYPE_FILTER varchar(1000), + RES_TYPE varchar(40) not null, + OPTLOCK integer not null, + JOB_PID bigint not null, + primary key (PID) + ); + + create table HFJ_BLK_EXPORT_JOB ( + PID bigint not null, + CREATED_TIME timestamp(6) not null, + EXP_TIME timestamp(6), + JOB_ID varchar(36) not null, + REQUEST varchar(1024) not null, + EXP_SINCE timestamp(6), + JOB_STATUS varchar(10) not null, + STATUS_MESSAGE varchar(500), + STATUS_TIME timestamp(6) not null, + OPTLOCK integer not null, + primary key (PID), + constraint IDX_BLKEX_JOB_ID unique (JOB_ID) + ); + + create table HFJ_BLK_IMPORT_JOB ( + PID bigint not null, + BATCH_SIZE integer not null, + FILE_COUNT integer not null, + JOB_DESC varchar(500), + JOB_ID varchar(36) not null, + ROW_PROCESSING_MODE varchar(20) not null, + JOB_STATUS varchar(10) not null, + STATUS_MESSAGE varchar(500), + STATUS_TIME timestamp(6) not null, + OPTLOCK integer not null, + primary key (PID), + constraint IDX_BLKIM_JOB_ID unique (JOB_ID) + ); + + create table HFJ_BLK_IMPORT_JOBFILE ( + PID bigint not null, + JOB_CONTENTS blob, + JOB_CONTENTS_VC clob, + FILE_DESCRIPTION varchar(500), + FILE_SEQ integer not null, + TENANT_NAME varchar(200), + JOB_PID bigint not null, + primary key (PID) + ); + + create table HFJ_FORCED_ID ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + FORCED_ID varchar(100) not null, + RESOURCE_PID bigint not null, + RESOURCE_TYPE varchar(100) default '', + primary key (PID) + ); + + create table HFJ_HISTORY_TAG ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + TAG_ID bigint, + RES_VER_PID bigint not null, + RES_ID bigint not null, + RES_TYPE varchar(40) not null, + primary key (PID), + constraint IDX_RESHISTTAG_TAGID unique (RES_VER_PID, TAG_ID) + ); + + create table HFJ_IDX_CMB_TOK_NU ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + HASH_COMPLETE bigint not null, + IDX_STRING varchar(500) not null, + RES_ID bigint, + primary key (PID) + ); + + create table HFJ_IDX_CMP_STRING_UNIQ ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + IDX_STRING varchar(500) not null, + RES_ID bigint, + primary key (PID), + constraint IDX_IDXCMPSTRUNIQ_STRING unique (IDX_STRING) + ); + + create table HFJ_PARTITION ( + PART_ID integer not null, + PART_DESC varchar(200), + PART_NAME varchar(200) not null, + primary key (PART_ID), + constraint IDX_PART_NAME unique (PART_NAME) + ); + + create table HFJ_RES_LINK ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SRC_PATH varchar(500) not null, + SRC_RESOURCE_ID bigint not null, + SOURCE_RESOURCE_TYPE varchar(40) not null, + TARGET_RESOURCE_ID bigint, + TARGET_RESOURCE_TYPE varchar(40) not null, + TARGET_RESOURCE_URL varchar(200), + TARGET_RESOURCE_VERSION bigint, + SP_UPDATED timestamp(6), + primary key (PID) + ); + + create table HFJ_RES_PARAM_PRESENT ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + HASH_PRESENCE bigint, + SP_PRESENT boolean not null, + RES_ID bigint not null, + primary key (PID) + ); + + create table HFJ_RES_REINDEX_JOB ( + PID bigint not null, + JOB_DELETED boolean not null, + REINDEX_COUNT integer, + RES_TYPE varchar(100), + SUSPENDED_UNTIL timestamp(6), + UPDATE_THRESHOLD_HIGH timestamp(6) not null, + UPDATE_THRESHOLD_LOW timestamp(6), + primary key (PID) + ); + + create table HFJ_RES_SEARCH_URL ( + RES_SEARCH_URL varchar(768) not null, + CREATED_TIME timestamp(6) not null, + RES_ID bigint not null, + primary key (RES_SEARCH_URL) + ); + + create table HFJ_RES_TAG ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + TAG_ID bigint, + RES_ID bigint, + RES_TYPE varchar(40) not null, + primary key (PID), + constraint IDX_RESTAG_TAGID unique (RES_ID, TAG_ID) + ); + + create table HFJ_RES_VER ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + RES_DELETED_AT timestamp(6), + RES_VERSION varchar(7), + HAS_TAGS boolean not null, + RES_PUBLISHED timestamp(6) not null, + RES_UPDATED timestamp(6) not null, + RES_ENCODING varchar(5) not null, + REQUEST_ID varchar(16), + RES_TEXT blob, + RES_ID bigint not null, + RES_TEXT_VC clob, + RES_TYPE varchar(40) not null, + RES_VER bigint not null, + SOURCE_URI varchar(100), + primary key (PID), + constraint IDX_RESVER_ID_VER unique (RES_ID, RES_VER) + ); + + create table HFJ_RES_VER_PROV ( + RES_VER_PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + REQUEST_ID varchar(16), + SOURCE_URI varchar(100), + RES_PID bigint not null, + primary key (RES_VER_PID) + ); + + create table HFJ_RESOURCE ( + RES_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + RES_DELETED_AT timestamp(6), + RES_VERSION varchar(7), + HAS_TAGS boolean not null, + RES_PUBLISHED timestamp(6) not null, + RES_UPDATED timestamp(6) not null, + FHIR_ID varchar(64), + SP_HAS_LINKS boolean, + HASH_SHA256 varchar(64), + SP_INDEX_STATUS bigint, + RES_LANGUAGE varchar(20), + SP_CMPSTR_UNIQ_PRESENT boolean, + SP_CMPTOKS_PRESENT boolean, + SP_COORDS_PRESENT boolean, + SP_DATE_PRESENT boolean, + SP_NUMBER_PRESENT boolean, + SP_QUANTITY_NRML_PRESENT boolean, + SP_QUANTITY_PRESENT boolean, + SP_STRING_PRESENT boolean, + SP_TOKEN_PRESENT boolean, + SP_URI_PRESENT boolean, + RES_TYPE varchar(40) not null, + SEARCH_URL_PRESENT boolean, + RES_VER bigint, + primary key (RES_ID), + constraint IDX_RES_TYPE_FHIR_ID unique (RES_TYPE, FHIR_ID) + ); + + create table HFJ_RESOURCE_MODIFIED ( + RES_ID varchar(256) not null, + RES_VER varchar(8) not null, + CREATED_TIME timestamp(6) not null, + RESOURCE_TYPE varchar(40) not null, + SUMMARY_MESSAGE varchar(4000) not null, + primary key (RES_ID, RES_VER) + ); + + create table HFJ_REVINFO ( + REV bigint not null, + REVTSTMP timestamp(6), + primary key (REV) + ); + + create table HFJ_SEARCH ( + PID bigint not null, + CREATED timestamp(6) not null, + SEARCH_DELETED boolean, + EXPIRY_OR_NULL timestamp(6), + FAILURE_CODE integer, + FAILURE_MESSAGE varchar(500), + LAST_UPDATED_HIGH timestamp(6), + LAST_UPDATED_LOW timestamp(6), + NUM_BLOCKED integer, + NUM_FOUND integer not null, + PREFERRED_PAGE_SIZE integer, + RESOURCE_ID bigint, + RESOURCE_TYPE varchar(200), + SEARCH_PARAM_MAP blob, + SEARCH_PARAM_MAP_BIN blob, + SEARCH_QUERY_STRING clob, + SEARCH_QUERY_STRING_HASH integer, + SEARCH_QUERY_STRING_VC clob, + SEARCH_TYPE integer not null, + SEARCH_STATUS varchar(10) not null, + TOTAL_COUNT integer, + SEARCH_UUID varchar(48) not null, + OPTLOCK_VERSION integer, + primary key (PID), + constraint IDX_SEARCH_UUID unique (SEARCH_UUID) + ); + + create table HFJ_SEARCH_INCLUDE ( + PID bigint not null, + SEARCH_INCLUDE varchar(200) not null, + INC_RECURSE boolean not null, + REVINCLUDE boolean not null, + SEARCH_PID bigint not null, + primary key (PID) + ); + + create table HFJ_SEARCH_RESULT ( + PID bigint not null, + SEARCH_ORDER integer not null, + RESOURCE_PID bigint not null, + SEARCH_PID bigint not null, + primary key (PID), + constraint IDX_SEARCHRES_ORDER unique (SEARCH_PID, SEARCH_ORDER) + ); + + create table HFJ_SPIDX_COORDS ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + SP_LATITUDE float(53), + SP_LONGITUDE float(53), + primary key (SP_ID) + ); + + create table HFJ_SPIDX_DATE ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + SP_VALUE_HIGH timestamp(6), + SP_VALUE_HIGH_DATE_ORDINAL integer, + SP_VALUE_LOW timestamp(6), + SP_VALUE_LOW_DATE_ORDINAL integer, + primary key (SP_ID) + ); + + create table HFJ_SPIDX_NUMBER ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + SP_VALUE decimal(19,2), + primary key (SP_ID) + ); + + create table HFJ_SPIDX_QUANTITY ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + HASH_IDENTITY_AND_UNITS bigint, + HASH_IDENTITY_SYS_UNITS bigint, + SP_SYSTEM varchar(200), + SP_UNITS varchar(200), + SP_VALUE float(53), + primary key (SP_ID) + ); + + create table HFJ_SPIDX_QUANTITY_NRML ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + HASH_IDENTITY_AND_UNITS bigint, + HASH_IDENTITY_SYS_UNITS bigint, + SP_SYSTEM varchar(200), + SP_UNITS varchar(200), + SP_VALUE float(53), + primary key (SP_ID) + ); + + create table HFJ_SPIDX_STRING ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_EXACT bigint, + HASH_IDENTITY bigint, + HASH_NORM_PREFIX bigint, + SP_VALUE_EXACT varchar(200), + SP_VALUE_NORMALIZED varchar(200), + primary key (SP_ID) + ); + + create table HFJ_SPIDX_TOKEN ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + HASH_SYS bigint, + HASH_SYS_AND_VALUE bigint, + HASH_VALUE bigint, + SP_SYSTEM varchar(200), + SP_VALUE varchar(200), + primary key (SP_ID) + ); + + create table HFJ_SPIDX_URI ( + SP_ID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + SP_MISSING boolean not null, + SP_NAME varchar(100) not null, + RES_ID bigint not null, + RES_TYPE varchar(100) not null, + SP_UPDATED timestamp(6), + HASH_IDENTITY bigint, + HASH_URI bigint, + SP_URI varchar(500), + primary key (SP_ID), + constraint IDX_SP_URI_HASH_URI_V2 unique (HASH_URI, RES_ID, PARTITION_ID), + constraint IDX_SP_URI_HASH_IDENTITY_V2 unique (HASH_IDENTITY, SP_URI, RES_ID, PARTITION_ID) + ); + + create table HFJ_SUBSCRIPTION_STATS ( + PID bigint not null, + CREATED_TIME timestamp(6) not null, + RES_ID bigint, + primary key (PID), + constraint IDX_SUBSC_RESID unique (RES_ID) + ); + + create table HFJ_TAG_DEF ( + TAG_ID bigint not null, + TAG_CODE varchar(200), + TAG_DISPLAY varchar(200), + TAG_SYSTEM varchar(200), + TAG_TYPE integer not null, + TAG_USER_SELECTED boolean, + TAG_VERSION varchar(30), + primary key (TAG_ID) + ); + + create table MPI_LINK ( + PID bigint not null, + PARTITION_DATE date, + PARTITION_ID integer, + CREATED timestamp(6) not null, + EID_MATCH boolean, + GOLDEN_RESOURCE_PID bigint not null, + NEW_PERSON boolean, + LINK_SOURCE integer not null, + MATCH_RESULT integer not null, + TARGET_TYPE varchar(40), + PERSON_PID bigint not null, + RULE_COUNT bigint, + SCORE float(53), + TARGET_PID bigint not null, + UPDATED timestamp(6) not null, + VECTOR bigint, + VERSION varchar(16) not null, + primary key (PID), + constraint IDX_EMPI_PERSON_TGT unique (PERSON_PID, TARGET_PID) + ); + + create table MPI_LINK_AUD ( + PID bigint not null, + REV bigint not null, + REVTYPE tinyint, + PARTITION_DATE date, + PARTITION_ID integer, + CREATED timestamp(6), + EID_MATCH boolean, + GOLDEN_RESOURCE_PID bigint, + NEW_PERSON boolean, + LINK_SOURCE integer, + MATCH_RESULT integer, + TARGET_TYPE varchar(40), + PERSON_PID bigint, + RULE_COUNT bigint, + SCORE float(53), + TARGET_PID bigint, + UPDATED timestamp(6), + VECTOR bigint, + VERSION varchar(16), + primary key (REV, PID) + ); + + create table NPM_PACKAGE ( + PID bigint not null, + CUR_VERSION_ID varchar(200), + PACKAGE_DESC varchar(200), + PACKAGE_ID varchar(200) not null, + UPDATED_TIME timestamp(6) not null, + primary key (PID), + constraint IDX_PACK_ID unique (PACKAGE_ID) + ); + + create table NPM_PACKAGE_VER ( + PID bigint not null, + CURRENT_VERSION boolean not null, + PKG_DESC varchar(200), + DESC_UPPER varchar(200), + FHIR_VERSION varchar(10) not null, + FHIR_VERSION_ID varchar(20) not null, + PACKAGE_ID varchar(200) not null, + PACKAGE_SIZE_BYTES bigint not null, + SAVED_TIME timestamp(6) not null, + UPDATED_TIME timestamp(6) not null, + VERSION_ID varchar(200) not null, + PACKAGE_PID bigint not null, + BINARY_RES_ID bigint not null, + primary key (PID), + constraint IDX_PACKVER unique (PACKAGE_ID, VERSION_ID) + ); + + create table NPM_PACKAGE_VER_RES ( + PID bigint not null, + CANONICAL_URL varchar(200), + CANONICAL_VERSION varchar(200), + FILE_DIR varchar(200), + FHIR_VERSION varchar(10) not null, + FHIR_VERSION_ID varchar(20) not null, + FILE_NAME varchar(200), + RES_SIZE_BYTES bigint not null, + RES_TYPE varchar(40) not null, + UPDATED_TIME timestamp(6) not null, + PACKVER_PID bigint not null, + BINARY_RES_ID bigint not null, + primary key (PID) + ); + + create table TRM_CODESYSTEM ( + PID bigint not null, + CODE_SYSTEM_URI varchar(200) not null, + CURRENT_VERSION_PID bigint, + CS_NAME varchar(200), + RES_ID bigint, + primary key (PID), + constraint IDX_CS_CODESYSTEM unique (CODE_SYSTEM_URI) + ); + + create table TRM_CODESYSTEM_VER ( + PID bigint not null, + CS_DISPLAY varchar(200), + CODESYSTEM_PID bigint, + CS_VERSION_ID varchar(200), + RES_ID bigint not null, + primary key (PID), + constraint IDX_CODESYSTEM_AND_VER unique (CODESYSTEM_PID, CS_VERSION_ID) + ); + + create table TRM_CONCEPT ( + PID bigint not null, + CODEVAL varchar(500) not null, + CODESYSTEM_PID bigint, + DISPLAY varchar(400), + INDEX_STATUS bigint, + PARENT_PIDS clob, + PARENT_PIDS_VC clob, + CODE_SEQUENCE integer, + CONCEPT_UPDATED timestamp(6), + primary key (PID), + constraint IDX_CONCEPT_CS_CODE unique (CODESYSTEM_PID, CODEVAL) + ); + + create table TRM_CONCEPT_DESIG ( + PID bigint not null, + LANG varchar(500), + USE_CODE varchar(500), + USE_DISPLAY varchar(500), + USE_SYSTEM varchar(500), + VAL varchar(2000) not null, + CS_VER_PID bigint, + CONCEPT_PID bigint, + primary key (PID) + ); + + create table TRM_CONCEPT_MAP ( + PID bigint not null, + RES_ID bigint, + SOURCE_URL varchar(200), + TARGET_URL varchar(200), + URL varchar(200) not null, + VER varchar(200), + primary key (PID), + constraint IDX_CONCEPT_MAP_URL unique (URL, VER) + ); + + create table TRM_CONCEPT_MAP_GROUP ( + PID bigint not null, + CONCEPT_MAP_URL varchar(200), + SOURCE_URL varchar(200) not null, + SOURCE_VS varchar(200), + SOURCE_VERSION varchar(200), + TARGET_URL varchar(200) not null, + TARGET_VS varchar(200), + TARGET_VERSION varchar(200), + CONCEPT_MAP_PID bigint not null, + primary key (PID) + ); + + create table TRM_CONCEPT_MAP_GRP_ELEMENT ( + PID bigint not null, + SOURCE_CODE varchar(500) not null, + CONCEPT_MAP_URL varchar(200), + SOURCE_DISPLAY varchar(500), + SYSTEM_URL varchar(200), + SYSTEM_VERSION varchar(200), + VALUESET_URL varchar(200), + CONCEPT_MAP_GROUP_PID bigint not null, + primary key (PID) + ); + + create table TRM_CONCEPT_MAP_GRP_ELM_TGT ( + PID bigint not null, + TARGET_CODE varchar(500), + CONCEPT_MAP_URL varchar(200), + TARGET_DISPLAY varchar(500), + TARGET_EQUIVALENCE varchar(50), + SYSTEM_URL varchar(200), + SYSTEM_VERSION varchar(200), + VALUESET_URL varchar(200), + CONCEPT_MAP_GRP_ELM_PID bigint not null, + primary key (PID) + ); + + create table TRM_CONCEPT_PC_LINK ( + PID bigint not null, + CHILD_PID bigint, + CODESYSTEM_PID bigint not null, + PARENT_PID bigint, + REL_TYPE integer, + primary key (PID) + ); + + create table TRM_CONCEPT_PROPERTY ( + PID bigint not null, + PROP_CODESYSTEM varchar(500), + PROP_DISPLAY varchar(500), + PROP_KEY varchar(500) not null, + PROP_TYPE integer not null, + PROP_VAL varchar(500), + PROP_VAL_BIN blob, + PROP_VAL_LOB blob, + CS_VER_PID bigint, + CONCEPT_PID bigint, + primary key (PID) + ); + + create table TRM_VALUESET ( + PID bigint not null, + EXPANSION_STATUS varchar(50) not null, + EXPANDED_AT timestamp(6), + VSNAME varchar(200), + RES_ID bigint, + TOTAL_CONCEPT_DESIGNATIONS bigint default 0 not null, + TOTAL_CONCEPTS bigint default 0 not null, + URL varchar(200) not null, + VER varchar(200), + primary key (PID), + constraint IDX_VALUESET_URL unique (URL, VER) + ); + + create table TRM_VALUESET_C_DESIGNATION ( + PID bigint not null, + VALUESET_CONCEPT_PID bigint not null, + LANG varchar(500), + USE_CODE varchar(500), + USE_DISPLAY varchar(500), + USE_SYSTEM varchar(500), + VAL varchar(2000) not null, + VALUESET_PID bigint not null, + primary key (PID) + ); + + create table TRM_VALUESET_CONCEPT ( + PID bigint not null, + CODEVAL varchar(500) not null, + DISPLAY varchar(400), + INDEX_STATUS bigint, + VALUESET_ORDER integer not null, + SOURCE_DIRECT_PARENT_PIDS clob, + SOURCE_DIRECT_PARENT_PIDS_VC clob, + SOURCE_PID bigint, + SYSTEM_URL varchar(200) not null, + SYSTEM_VER varchar(200), + VALUESET_PID bigint not null, + primary key (PID), + constraint IDX_VS_CONCEPT_CSCD unique (VALUESET_PID, SYSTEM_URL, CODEVAL), + constraint IDX_VS_CONCEPT_ORDER unique (VALUESET_PID, VALUESET_ORDER) + ); + + create index IDX_BT2JI_CT + on BT2_JOB_INSTANCE (CREATE_TIME); + + create index IDX_BT2WC_II_SEQ + on BT2_WORK_CHUNK (INSTANCE_ID, SEQ); + + create index IDX_BLKEX_EXPTIME + on HFJ_BLK_EXPORT_JOB (EXP_TIME); + + create index IDX_BLKIM_JOBFILE_JOBID + on HFJ_BLK_IMPORT_JOBFILE (JOB_PID); + + create index IDX_RESHISTTAG_RESID + on HFJ_HISTORY_TAG (RES_ID); + + create index IDX_IDXCMBTOKNU_STR + on HFJ_IDX_CMB_TOK_NU (IDX_STRING); + + create index IDX_IDXCMBTOKNU_RES + on HFJ_IDX_CMB_TOK_NU (RES_ID); + + create index IDX_IDXCMPSTRUNIQ_RESOURCE + on HFJ_IDX_CMP_STRING_UNIQ (RES_ID); + + create index IDX_RL_SRC + on HFJ_RES_LINK (SRC_RESOURCE_ID); + + create index IDX_RL_TGT_v2 + on HFJ_RES_LINK (TARGET_RESOURCE_ID, SRC_PATH, SRC_RESOURCE_ID, TARGET_RESOURCE_TYPE, PARTITION_ID); + + create index IDX_RESPARMPRESENT_RESID + on HFJ_RES_PARAM_PRESENT (RES_ID); + + create index IDX_RESPARMPRESENT_HASHPRES + on HFJ_RES_PARAM_PRESENT (HASH_PRESENCE); + + create index IDX_RESSEARCHURL_RES + on HFJ_RES_SEARCH_URL (RES_ID); + + create index IDX_RESSEARCHURL_TIME + on HFJ_RES_SEARCH_URL (CREATED_TIME); + + create index IDX_RES_TAG_RES_TAG + on HFJ_RES_TAG (RES_ID, TAG_ID, PARTITION_ID); + + create index IDX_RES_TAG_TAG_RES + on HFJ_RES_TAG (TAG_ID, RES_ID, PARTITION_ID); + + create index IDX_RESVER_TYPE_DATE + on HFJ_RES_VER (RES_TYPE, RES_UPDATED); + + create index IDX_RESVER_ID_DATE + on HFJ_RES_VER (RES_ID, RES_UPDATED); + + create index IDX_RESVER_DATE + on HFJ_RES_VER (RES_UPDATED); + + create index IDX_RESVERPROV_SOURCEURI + on HFJ_RES_VER_PROV (SOURCE_URI); + + create index IDX_RESVERPROV_REQUESTID + on HFJ_RES_VER_PROV (REQUEST_ID); + + create index IDX_RESVERPROV_RES_PID + on HFJ_RES_VER_PROV (RES_PID); + + create index IDX_RES_DATE + on HFJ_RESOURCE (RES_UPDATED); + + create index IDX_RES_FHIR_ID + on HFJ_RESOURCE (FHIR_ID); + + create index IDX_RES_TYPE_DEL_UPDATED + on HFJ_RESOURCE (RES_TYPE, RES_DELETED_AT, RES_UPDATED, PARTITION_ID, RES_ID); + + create index IDX_RES_RESID_UPDATED + on HFJ_RESOURCE (RES_ID, RES_UPDATED, PARTITION_ID); + + create index IDX_SEARCH_RESTYPE_HASHS + on HFJ_SEARCH (RESOURCE_TYPE, SEARCH_QUERY_STRING_HASH, CREATED); + + create index IDX_SEARCH_CREATED + on HFJ_SEARCH (CREATED); + + create index FK_SEARCHINC_SEARCH + on HFJ_SEARCH_INCLUDE (SEARCH_PID); + + create index IDX_SP_COORDS_HASH_V2 + on HFJ_SPIDX_COORDS (HASH_IDENTITY, SP_LATITUDE, SP_LONGITUDE, RES_ID, PARTITION_ID); + + create index IDX_SP_COORDS_UPDATED + on HFJ_SPIDX_COORDS (SP_UPDATED); + + create index IDX_SP_COORDS_RESID + on HFJ_SPIDX_COORDS (RES_ID); + + create index IDX_SP_DATE_HASH_V2 + on HFJ_SPIDX_DATE (HASH_IDENTITY, SP_VALUE_LOW, SP_VALUE_HIGH, RES_ID, PARTITION_ID); + + create index IDX_SP_DATE_HASH_HIGH_V2 + on HFJ_SPIDX_DATE (HASH_IDENTITY, SP_VALUE_HIGH, RES_ID, PARTITION_ID); + + create index IDX_SP_DATE_ORD_HASH_V2 + on HFJ_SPIDX_DATE (HASH_IDENTITY, SP_VALUE_LOW_DATE_ORDINAL, SP_VALUE_HIGH_DATE_ORDINAL, RES_ID, PARTITION_ID); + + create index IDX_SP_DATE_ORD_HASH_HIGH_V2 + on HFJ_SPIDX_DATE (HASH_IDENTITY, SP_VALUE_HIGH_DATE_ORDINAL, RES_ID, PARTITION_ID); + + create index IDX_SP_DATE_RESID_V2 + on HFJ_SPIDX_DATE (RES_ID, HASH_IDENTITY, SP_VALUE_LOW, SP_VALUE_HIGH, SP_VALUE_LOW_DATE_ORDINAL, SP_VALUE_HIGH_DATE_ORDINAL, PARTITION_ID); + + create index IDX_SP_NUMBER_HASH_VAL_V2 + on HFJ_SPIDX_NUMBER (HASH_IDENTITY, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_NUMBER_RESID_V2 + on HFJ_SPIDX_NUMBER (RES_ID, HASH_IDENTITY, SP_VALUE, PARTITION_ID); + + create index IDX_SP_QUANTITY_HASH_V2 + on HFJ_SPIDX_QUANTITY (HASH_IDENTITY, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_QUANTITY_HASH_UN_V2 + on HFJ_SPIDX_QUANTITY (HASH_IDENTITY_AND_UNITS, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_QUANTITY_HASH_SYSUN_V2 + on HFJ_SPIDX_QUANTITY (HASH_IDENTITY_SYS_UNITS, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_QUANTITY_RESID_V2 + on HFJ_SPIDX_QUANTITY (RES_ID, HASH_IDENTITY, HASH_IDENTITY_SYS_UNITS, HASH_IDENTITY_AND_UNITS, SP_VALUE, PARTITION_ID); + + create index IDX_SP_QNTY_NRML_HASH_V2 + on HFJ_SPIDX_QUANTITY_NRML (HASH_IDENTITY, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_QNTY_NRML_HASH_UN_V2 + on HFJ_SPIDX_QUANTITY_NRML (HASH_IDENTITY_AND_UNITS, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_QNTY_NRML_HASH_SYSUN_V2 + on HFJ_SPIDX_QUANTITY_NRML (HASH_IDENTITY_SYS_UNITS, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_QNTY_NRML_RESID_V2 + on HFJ_SPIDX_QUANTITY_NRML (RES_ID, HASH_IDENTITY, HASH_IDENTITY_SYS_UNITS, HASH_IDENTITY_AND_UNITS, SP_VALUE, PARTITION_ID); + + create index IDX_SP_STRING_HASH_IDENT_V2 + on HFJ_SPIDX_STRING (HASH_IDENTITY, RES_ID, PARTITION_ID); + + create index IDX_SP_STRING_HASH_NRM_V2 + on HFJ_SPIDX_STRING (HASH_NORM_PREFIX, SP_VALUE_NORMALIZED, RES_ID, PARTITION_ID); + + create index IDX_SP_STRING_HASH_EXCT_V2 + on HFJ_SPIDX_STRING (HASH_EXACT, RES_ID, PARTITION_ID); + + create index IDX_SP_STRING_RESID_V2 + on HFJ_SPIDX_STRING (RES_ID, HASH_NORM_PREFIX, PARTITION_ID); + + create index IDX_SP_TOKEN_HASH_V2 + on HFJ_SPIDX_TOKEN (HASH_IDENTITY, SP_SYSTEM, SP_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_TOKEN_HASH_S_V2 + on HFJ_SPIDX_TOKEN (HASH_SYS, RES_ID, PARTITION_ID); + + create index IDX_SP_TOKEN_HASH_SV_V2 + on HFJ_SPIDX_TOKEN (HASH_SYS_AND_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_TOKEN_HASH_V_V2 + on HFJ_SPIDX_TOKEN (HASH_VALUE, RES_ID, PARTITION_ID); + + create index IDX_SP_TOKEN_RESID_V2 + on HFJ_SPIDX_TOKEN (RES_ID, HASH_SYS_AND_VALUE, HASH_VALUE, HASH_SYS, HASH_IDENTITY, PARTITION_ID); + + create index IDX_SP_URI_COORDS + on HFJ_SPIDX_URI (RES_ID); + + create index IDX_TAG_DEF_TP_CD_SYS + on HFJ_TAG_DEF (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_ID, TAG_VERSION, TAG_USER_SELECTED); + + create index IDX_EMPI_MATCH_TGT_VER + on MPI_LINK (MATCH_RESULT, TARGET_PID, VERSION); + + create index IDX_EMPI_GR_TGT + on MPI_LINK (GOLDEN_RESOURCE_PID, TARGET_PID); + + create index FK_EMPI_LINK_TARGET + on MPI_LINK (TARGET_PID); + + create index IDX_EMPI_TGT_MR_LS + on MPI_LINK (TARGET_TYPE, MATCH_RESULT, LINK_SOURCE); + + create index IDX_EMPI_TGT_MR_SCORE + on MPI_LINK (TARGET_TYPE, MATCH_RESULT, SCORE); + + create index FK_NPM_PKV_PKG + on NPM_PACKAGE_VER (PACKAGE_PID); + + create index FK_NPM_PKV_RESID + on NPM_PACKAGE_VER (BINARY_RES_ID); + + create index IDX_PACKVERRES_URL + on NPM_PACKAGE_VER_RES (CANONICAL_URL); + + create index FK_NPM_PACKVERRES_PACKVER + on NPM_PACKAGE_VER_RES (PACKVER_PID); + + create index FK_NPM_PKVR_RESID + on NPM_PACKAGE_VER_RES (BINARY_RES_ID); + + create index FK_TRMCODESYSTEM_RES + on TRM_CODESYSTEM (RES_ID); + + create index FK_TRMCODESYSTEM_CURVER + on TRM_CODESYSTEM (CURRENT_VERSION_PID); + + create index FK_CODESYSVER_RES_ID + on TRM_CODESYSTEM_VER (RES_ID); + + create index FK_CODESYSVER_CS_ID + on TRM_CODESYSTEM_VER (CODESYSTEM_PID); + + create index IDX_CONCEPT_INDEXSTATUS + on TRM_CONCEPT (INDEX_STATUS); + + create index IDX_CONCEPT_UPDATED + on TRM_CONCEPT (CONCEPT_UPDATED); + + create index FK_CONCEPTDESIG_CONCEPT + on TRM_CONCEPT_DESIG (CONCEPT_PID); + + create index FK_CONCEPTDESIG_CSV + on TRM_CONCEPT_DESIG (CS_VER_PID); + + create index FK_TRMCONCEPTMAP_RES + on TRM_CONCEPT_MAP (RES_ID); + + create index FK_TCMGROUP_CONCEPTMAP + on TRM_CONCEPT_MAP_GROUP (CONCEPT_MAP_PID); + + create index IDX_CNCPT_MAP_GRP_CD + on TRM_CONCEPT_MAP_GRP_ELEMENT (SOURCE_CODE); + + create index FK_TCMGELEMENT_GROUP + on TRM_CONCEPT_MAP_GRP_ELEMENT (CONCEPT_MAP_GROUP_PID); + + create index IDX_CNCPT_MP_GRP_ELM_TGT_CD + on TRM_CONCEPT_MAP_GRP_ELM_TGT (TARGET_CODE); + + create index FK_TCMGETARGET_ELEMENT + on TRM_CONCEPT_MAP_GRP_ELM_TGT (CONCEPT_MAP_GRP_ELM_PID); + + create index FK_TERM_CONCEPTPC_CHILD + on TRM_CONCEPT_PC_LINK (CHILD_PID); + + create index FK_TERM_CONCEPTPC_PARENT + on TRM_CONCEPT_PC_LINK (PARENT_PID); + + create index FK_TERM_CONCEPTPC_CS + on TRM_CONCEPT_PC_LINK (CODESYSTEM_PID); + + create index FK_CONCEPTPROP_CONCEPT + on TRM_CONCEPT_PROPERTY (CONCEPT_PID); + + create index FK_CONCEPTPROP_CSV + on TRM_CONCEPT_PROPERTY (CS_VER_PID); + + create index FK_TRMVALUESET_RES + on TRM_VALUESET (RES_ID); + + create index FK_TRM_VALUESET_CONCEPT_PID + on TRM_VALUESET_C_DESIGNATION (VALUESET_CONCEPT_PID); + + create index FK_TRM_VSCD_VS_PID + on TRM_VALUESET_C_DESIGNATION (VALUESET_PID); + + alter table if exists BT2_WORK_CHUNK + add constraint FK_BT2WC_INSTANCE + foreign key (INSTANCE_ID) + references BT2_JOB_INSTANCE; + + alter table if exists HFJ_BLK_EXPORT_COLFILE + add constraint FK_BLKEXCOLFILE_COLLECT + foreign key (COLLECTION_PID) + references HFJ_BLK_EXPORT_COLLECTION; + + alter table if exists HFJ_BLK_EXPORT_COLLECTION + add constraint FK_BLKEXCOL_JOB + foreign key (JOB_PID) + references HFJ_BLK_EXPORT_JOB; + + alter table if exists HFJ_BLK_IMPORT_JOBFILE + add constraint FK_BLKIMJOBFILE_JOB + foreign key (JOB_PID) + references HFJ_BLK_IMPORT_JOB; + + alter table if exists HFJ_HISTORY_TAG + add constraint FKtderym7awj6q8iq5c51xv4ndw + foreign key (TAG_ID) + references HFJ_TAG_DEF; + + alter table if exists HFJ_HISTORY_TAG + add constraint FK_HISTORYTAG_HISTORY + foreign key (RES_VER_PID) + references HFJ_RES_VER; + + alter table if exists HFJ_IDX_CMB_TOK_NU + add constraint FK_IDXCMBTOKNU_RES_ID + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_IDX_CMP_STRING_UNIQ + add constraint FK_IDXCMPSTRUNIQ_RES_ID + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_LINK + add constraint FK_RESLINK_SOURCE + foreign key (SRC_RESOURCE_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_LINK + add constraint FK_RESLINK_TARGET + foreign key (TARGET_RESOURCE_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_PARAM_PRESENT + add constraint FK_RESPARMPRES_RESID + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_TAG + add constraint FKbfcjbaftmiwr3rxkwsy23vneo + foreign key (TAG_ID) + references HFJ_TAG_DEF; + + alter table if exists HFJ_RES_TAG + add constraint FK_RESTAG_RESOURCE + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_VER + add constraint FK_RESOURCE_HISTORY_RESOURCE + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_VER_PROV + add constraint FK_RESVERPROV_RES_PID + foreign key (RES_PID) + references HFJ_RESOURCE; + + alter table if exists HFJ_RES_VER_PROV + add constraint FK_RESVERPROV_RESVER_PID + foreign key (RES_VER_PID) + references HFJ_RES_VER; + + alter table if exists HFJ_SEARCH_INCLUDE + add constraint FK_SEARCHINC_SEARCH + foreign key (SEARCH_PID) + references HFJ_SEARCH; + + alter table if exists HFJ_SPIDX_COORDS + add constraint FKC97MPK37OKWU8QVTCEG2NH9VN + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_DATE + add constraint FK_SP_DATE_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_NUMBER + add constraint FK_SP_NUMBER_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_QUANTITY + add constraint FK_SP_QUANTITY_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_QUANTITY_NRML + add constraint FK_SP_QUANTITYNM_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_STRING + add constraint FK_SPIDXSTR_RESOURCE + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_TOKEN + add constraint FK_SP_TOKEN_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SPIDX_URI + add constraint FKGXSREUTYMMFJUWDSWV3Y887DO + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists HFJ_SUBSCRIPTION_STATS + add constraint FK_SUBSC_RESOURCE_ID + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists MPI_LINK + add constraint FK_EMPI_LINK_GOLDEN_RESOURCE + foreign key (GOLDEN_RESOURCE_PID) + references HFJ_RESOURCE; + + alter table if exists MPI_LINK + add constraint FK_EMPI_LINK_PERSON + foreign key (PERSON_PID) + references HFJ_RESOURCE; + + alter table if exists MPI_LINK + add constraint FK_EMPI_LINK_TARGET + foreign key (TARGET_PID) + references HFJ_RESOURCE; + + alter table if exists MPI_LINK_AUD + add constraint FKaow7nxncloec419ars0fpp58m + foreign key (REV) + references HFJ_REVINFO; + + alter table if exists NPM_PACKAGE_VER + add constraint FK_NPM_PKV_PKG + foreign key (PACKAGE_PID) + references NPM_PACKAGE; + + alter table if exists NPM_PACKAGE_VER + add constraint FK_NPM_PKV_RESID + foreign key (BINARY_RES_ID) + references HFJ_RESOURCE; + + alter table if exists NPM_PACKAGE_VER_RES + add constraint FK_NPM_PACKVERRES_PACKVER + foreign key (PACKVER_PID) + references NPM_PACKAGE_VER; + + alter table if exists NPM_PACKAGE_VER_RES + add constraint FK_NPM_PKVR_RESID + foreign key (BINARY_RES_ID) + references HFJ_RESOURCE; + + alter table if exists TRM_CODESYSTEM + add constraint FK_TRMCODESYSTEM_CURVER + foreign key (CURRENT_VERSION_PID) + references TRM_CODESYSTEM_VER; + + alter table if exists TRM_CODESYSTEM + add constraint FK_TRMCODESYSTEM_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists TRM_CODESYSTEM_VER + add constraint FK_CODESYSVER_CS_ID + foreign key (CODESYSTEM_PID) + references TRM_CODESYSTEM; + + alter table if exists TRM_CODESYSTEM_VER + add constraint FK_CODESYSVER_RES_ID + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists TRM_CONCEPT + add constraint FK_CONCEPT_PID_CS_PID + foreign key (CODESYSTEM_PID) + references TRM_CODESYSTEM_VER; + + alter table if exists TRM_CONCEPT_DESIG + add constraint FK_CONCEPTDESIG_CSV + foreign key (CS_VER_PID) + references TRM_CODESYSTEM_VER; + + alter table if exists TRM_CONCEPT_DESIG + add constraint FK_CONCEPTDESIG_CONCEPT + foreign key (CONCEPT_PID) + references TRM_CONCEPT; + + alter table if exists TRM_CONCEPT_MAP + add constraint FK_TRMCONCEPTMAP_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists TRM_CONCEPT_MAP_GROUP + add constraint FK_TCMGROUP_CONCEPTMAP + foreign key (CONCEPT_MAP_PID) + references TRM_CONCEPT_MAP; + + alter table if exists TRM_CONCEPT_MAP_GRP_ELEMENT + add constraint FK_TCMGELEMENT_GROUP + foreign key (CONCEPT_MAP_GROUP_PID) + references TRM_CONCEPT_MAP_GROUP; + + alter table if exists TRM_CONCEPT_MAP_GRP_ELM_TGT + add constraint FK_TCMGETARGET_ELEMENT + foreign key (CONCEPT_MAP_GRP_ELM_PID) + references TRM_CONCEPT_MAP_GRP_ELEMENT; + + alter table if exists TRM_CONCEPT_PC_LINK + add constraint FK_TERM_CONCEPTPC_CHILD + foreign key (CHILD_PID) + references TRM_CONCEPT; + + alter table if exists TRM_CONCEPT_PC_LINK + add constraint FK_TERM_CONCEPTPC_CS + foreign key (CODESYSTEM_PID) + references TRM_CODESYSTEM_VER; + + alter table if exists TRM_CONCEPT_PC_LINK + add constraint FK_TERM_CONCEPTPC_PARENT + foreign key (PARENT_PID) + references TRM_CONCEPT; + + alter table if exists TRM_CONCEPT_PROPERTY + add constraint FK_CONCEPTPROP_CSV + foreign key (CS_VER_PID) + references TRM_CODESYSTEM_VER; + + alter table if exists TRM_CONCEPT_PROPERTY + add constraint FK_CONCEPTPROP_CONCEPT + foreign key (CONCEPT_PID) + references TRM_CONCEPT; + + alter table if exists TRM_VALUESET + add constraint FK_TRMVALUESET_RES + foreign key (RES_ID) + references HFJ_RESOURCE; + + alter table if exists TRM_VALUESET_C_DESIGNATION + add constraint FK_TRM_VALUESET_CONCEPT_PID + foreign key (VALUESET_CONCEPT_PID) + references TRM_VALUESET_CONCEPT; + + alter table if exists TRM_VALUESET_C_DESIGNATION + add constraint FK_TRM_VSCD_VS_PID + foreign key (VALUESET_PID) + references TRM_VALUESET; + + alter table if exists TRM_VALUESET_CONCEPT + add constraint FK_TRM_VALUESET_PID + foreign key (VALUESET_PID) + references TRM_VALUESET; diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java index af14206d65f..a4077b208b8 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java @@ -118,7 +118,7 @@ public abstract class BaseColumnCalculatorTask extends BaseTableColumnTask { try { next.get(); } catch (Exception e) { - throw new SQLException(Msg.code(69) + e); + throw new SQLException(Msg.code(69) + e, e); } } } @@ -168,8 +168,9 @@ public abstract class BaseColumnCalculatorTask extends BaseTableColumnTask { rejectedExecutionHandler); } - public void setPidColumnName(String thePidColumnName) { + public BaseColumnCalculatorTask setPidColumnName(String thePidColumnName) { myPidColumnName = thePidColumnName; + return this; } private Future updateRows(List> theRows) { diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/ColumnAndNullable.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/ColumnAndNullable.java index ea89dccfd0e..bc7635926b7 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/ColumnAndNullable.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/ColumnAndNullable.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR Server - SQL Migration + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.jpa.migrate.tasks.api; /** diff --git a/pom.xml b/pom.xml index 60eabdcfcaf..1b8ad85d2ee 100644 --- a/pom.xml +++ b/pom.xml @@ -973,7 +973,7 @@ 2.23.0 5.8.0 0.7.9 - 32.1.1-jre + 33.2.1-jre 2.8.9 2.2.11_1 2.3.1