Handle Versioned Canonical Refs (#6003)
* Work on versioned canonidal refs * Merge * Tests working * Working without version support * CLean refactor * Targets * Remove redundant parameter * Fix up tests * Remove fixme * Add changelog * Improve changelog * Update version * Test fixes
This commit is contained in:
parent
72b50a4e6d
commit
8947706af3
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-bom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<name>HAPI FHIR BOM</name>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
type: add
|
||||
issue: 6003
|
||||
title: "When performing a search for a resource which uses versioned canonical
|
||||
references to another resource (e.g. a QuestionnaireResponse with a
|
||||
questionnaire reference of `http://example.org/my-questionnaire|1.0`),
|
||||
the server failed to process `_include` directives which should bring in
|
||||
these references. Includes will now fetch all versions of a canonical
|
||||
reference, which is still not perfect but is an improvement over the
|
||||
current behaviour. A future update may add more targeted behaviour to
|
||||
address including versioned reference targets, but this will require a
|
||||
significant re-architecture of the loading module."
|
|
@ -11,7 +11,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -42,6 +42,7 @@ public class IResourceTableDaoImpl implements IForcedIdQueries {
|
|||
* is an object array, where the order matters (the array represents columns returned by the query).
|
||||
* Deleted resources are not filtered.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeIncludeDeleted(
|
||||
String theResourceType, Collection<String> theForcedIds) {
|
||||
return findAndResolveByForcedIdWithNoType(theResourceType, theForcedIds, false);
|
||||
|
@ -52,6 +53,7 @@ public class IResourceTableDaoImpl implements IForcedIdQueries {
|
|||
* is an object array, where the order matters (the array represents columns returned by the query).
|
||||
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoType(
|
||||
String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted) {
|
||||
String query = "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted "
|
||||
|
@ -74,6 +76,7 @@ public class IResourceTableDaoImpl implements IForcedIdQueries {
|
|||
* is an object array, where the order matters (the array represents columns returned by the query).
|
||||
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(
|
||||
String theResourceType,
|
||||
Collection<String> theForcedIds,
|
||||
|
@ -100,6 +103,7 @@ public class IResourceTableDaoImpl implements IForcedIdQueries {
|
|||
* is an object array, where the order matters (the array represents columns returned by the query).
|
||||
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(
|
||||
String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted) {
|
||||
String query = "SELECT t.myResourceType, t.myId, t.myFhirId, t.myDeleted "
|
||||
|
@ -122,6 +126,7 @@ public class IResourceTableDaoImpl implements IForcedIdQueries {
|
|||
* is an object array, where the order matters (the array represents columns returned by the query).
|
||||
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
|
||||
String theResourceType,
|
||||
Collection<String> theForcedIds,
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.search.builder;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.ComboSearchParamType;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
|
@ -163,7 +165,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
public static boolean myUseMaxPageSize50ForTest = false;
|
||||
protected final IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
protected final IResourceTagDao myResourceTagDao;
|
||||
private final String myResourceName;
|
||||
String myResourceName;
|
||||
private final Class<? extends IBaseResource> myResourceType;
|
||||
private final HapiFhirLocalContainerEntityManagerFactoryBean myEntityManagerFactory;
|
||||
private final SqlObjectFactory mySqlBuilderFactory;
|
||||
|
@ -195,6 +197,9 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
@Autowired(required = false)
|
||||
private IElasticsearchSvc myIElasticsearchSvc;
|
||||
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
|
||||
@Autowired
|
||||
private IJpaStorageResourceParser myJpaStorageResourceParser;
|
||||
|
||||
|
@ -1309,7 +1314,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, theParameters.getRequestDetails())) {
|
||||
CurrentThreadCaptureQueriesListener.startCapturing();
|
||||
}
|
||||
if (matches.size() == 0) {
|
||||
if (matches.isEmpty()) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
if (currentIncludes == null || currentIncludes.isEmpty()) {
|
||||
|
@ -1356,196 +1361,32 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
|
||||
if (matchAll) {
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append("SELECT r.").append(findPidFieldName);
|
||||
sqlBuilder.append(", r.").append(findResourceTypeFieldName);
|
||||
if (findVersionFieldName != null) {
|
||||
sqlBuilder.append(", r.").append(findVersionFieldName);
|
||||
}
|
||||
sqlBuilder.append(" FROM ResourceLink r WHERE ");
|
||||
|
||||
sqlBuilder.append("r.");
|
||||
sqlBuilder.append(searchPidFieldName); // (rev mode) target_resource_id | source_resource_id
|
||||
sqlBuilder.append(" IN (:target_pids)");
|
||||
|
||||
/*
|
||||
* We need to set the resource type in 2 cases only:
|
||||
* 1) we are in $everything mode
|
||||
* (where we only want to fetch specific resource types, regardless of what is
|
||||
* available to fetch)
|
||||
* 2) we are doing revincludes
|
||||
*
|
||||
* Technically if the request is a qualified star (e.g. _include=Observation:*) we
|
||||
* should always be checking the source resource type on the resource link. We don't
|
||||
* actually index that column though by default, so in order to try and be efficient
|
||||
* we don't actually include it for includes (but we do for revincludes). This is
|
||||
* because for an include, it doesn't really make sense to include a different
|
||||
* resource type than the one you are searching on.
|
||||
*/
|
||||
if (wantResourceType != null
|
||||
&& (reverseMode || (myParams != null && myParams.getEverythingMode() != null))) {
|
||||
// because mySourceResourceType is not part of the HFJ_RES_LINK
|
||||
// index, this might not be the most optimal performance.
|
||||
// but it is for an $everything operation (and maybe we should update the index)
|
||||
sqlBuilder.append(" AND r.mySourceResourceType = :want_resource_type");
|
||||
loadIncludesMatchAll(
|
||||
findPidFieldName,
|
||||
findResourceTypeFieldName,
|
||||
findVersionFieldName,
|
||||
searchPidFieldName,
|
||||
wantResourceType,
|
||||
reverseMode,
|
||||
hasDesiredResourceTypes,
|
||||
nextRoundMatches,
|
||||
entityManager,
|
||||
maxCount,
|
||||
desiredResourceTypes,
|
||||
pidsToInclude,
|
||||
request);
|
||||
} else {
|
||||
wantResourceType = null;
|
||||
}
|
||||
|
||||
// When calling $everything on a Patient instance, we don't want to recurse into new Patient
|
||||
// resources
|
||||
// (e.g. via Provenance, List, or Group) when in an $everything operation
|
||||
if (myParams != null
|
||||
&& myParams.getEverythingMode() == SearchParameterMap.EverythingModeEnum.PATIENT_INSTANCE) {
|
||||
sqlBuilder.append(" AND r.myTargetResourceType != 'Patient'");
|
||||
sqlBuilder.append(UNDESIRED_RESOURCE_LINKAGES_FOR_EVERYTHING_ON_PATIENT_INSTANCE.stream()
|
||||
.collect(Collectors.joining("', '", " AND r.mySourceResourceType NOT IN ('", "')")));
|
||||
}
|
||||
if (hasDesiredResourceTypes) {
|
||||
sqlBuilder.append(" AND r.myTargetResourceType IN (:desired_target_resource_types)");
|
||||
}
|
||||
|
||||
String sql = sqlBuilder.toString();
|
||||
List<Collection<JpaPid>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||
for (Collection<JpaPid> nextPartition : partitions) {
|
||||
TypedQuery<?> q = entityManager.createQuery(sql, Object[].class);
|
||||
q.setParameter("target_pids", JpaPid.toLongList(nextPartition));
|
||||
if (wantResourceType != null) {
|
||||
q.setParameter("want_resource_type", wantResourceType);
|
||||
}
|
||||
if (maxCount != null) {
|
||||
q.setMaxResults(maxCount);
|
||||
}
|
||||
if (hasDesiredResourceTypes) {
|
||||
q.setParameter("desired_target_resource_types", desiredResourceTypes);
|
||||
}
|
||||
List<?> results = q.getResultList();
|
||||
for (Object nextRow : results) {
|
||||
if (nextRow == null) {
|
||||
// This can happen if there are outgoing references which are canonical or point to
|
||||
// other servers
|
||||
continue;
|
||||
}
|
||||
|
||||
Long version = null;
|
||||
Long resourceLink = (Long) ((Object[]) nextRow)[0];
|
||||
String resourceType = (String) ((Object[]) nextRow)[1];
|
||||
if (findVersionFieldName != null) {
|
||||
version = (Long) ((Object[]) nextRow)[2];
|
||||
}
|
||||
|
||||
if (resourceLink != null) {
|
||||
JpaPid pid =
|
||||
JpaPid.fromIdAndVersionAndResourceType(resourceLink, version, resourceType);
|
||||
pidsToInclude.add(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> paths;
|
||||
|
||||
// Start replace
|
||||
RuntimeSearchParam param;
|
||||
String resType = nextInclude.getParamType();
|
||||
if (isBlank(resType)) {
|
||||
continue;
|
||||
}
|
||||
RuntimeResourceDefinition def = fhirContext.getResourceDefinition(resType);
|
||||
if (def == null) {
|
||||
ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
String paramName = nextInclude.getParamName();
|
||||
if (isNotBlank(paramName)) {
|
||||
param = mySearchParamRegistry.getActiveSearchParam(resType, paramName);
|
||||
} else {
|
||||
param = null;
|
||||
}
|
||||
if (param == null) {
|
||||
ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
paths = param.getPathsSplitForResourceType(resType);
|
||||
// end replace
|
||||
|
||||
Set<String> targetResourceTypes = computeTargetResourceTypes(nextInclude, param);
|
||||
|
||||
for (String nextPath : paths) {
|
||||
String findPidFieldSqlColumn = findPidFieldName.equals(MY_SOURCE_RESOURCE_PID)
|
||||
? "src_resource_id"
|
||||
: "target_resource_id";
|
||||
String fieldsToLoad = "r." + findPidFieldSqlColumn + " AS " + RESOURCE_ID_ALIAS;
|
||||
if (findVersionFieldName != null) {
|
||||
fieldsToLoad += ", r.target_resource_version AS " + RESOURCE_VERSION_ALIAS;
|
||||
}
|
||||
|
||||
// Query for includes lookup has 2 cases
|
||||
// Case 1: Where target_resource_id is available in hfj_res_link table for local references
|
||||
// Case 2: Where target_resource_id is null in hfj_res_link table and referred by a canonical
|
||||
// url in target_resource_url
|
||||
|
||||
// Case 1:
|
||||
Map<String, Object> localReferenceQueryParams = new HashMap<>();
|
||||
|
||||
String searchPidFieldSqlColumn = searchPidFieldName.equals(MY_TARGET_RESOURCE_PID)
|
||||
? "target_resource_id"
|
||||
: "src_resource_id";
|
||||
StringBuilder localReferenceQuery =
|
||||
new StringBuilder("SELECT " + fieldsToLoad + " FROM hfj_res_link r "
|
||||
+ " WHERE r.src_path = :src_path AND "
|
||||
+ " r.target_resource_id IS NOT NULL AND "
|
||||
+ " r."
|
||||
+ searchPidFieldSqlColumn + " IN (:target_pids) ");
|
||||
localReferenceQueryParams.put("src_path", nextPath);
|
||||
// we loop over target_pids later.
|
||||
if (targetResourceTypes != null) {
|
||||
if (targetResourceTypes.size() == 1) {
|
||||
localReferenceQuery.append(" AND r.target_resource_type = :target_resource_type ");
|
||||
localReferenceQueryParams.put(
|
||||
"target_resource_type",
|
||||
targetResourceTypes.iterator().next());
|
||||
} else {
|
||||
localReferenceQuery.append(" AND r.target_resource_type in (:target_resource_types) ");
|
||||
localReferenceQueryParams.put("target_resource_types", targetResourceTypes);
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2:
|
||||
Pair<String, Map<String, Object>> canonicalQuery = buildCanonicalUrlQuery(
|
||||
findVersionFieldName, searchPidFieldSqlColumn, targetResourceTypes);
|
||||
|
||||
// @formatter:on
|
||||
|
||||
String sql = localReferenceQuery + " UNION " + canonicalQuery.getLeft();
|
||||
|
||||
List<Collection<JpaPid>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||
for (Collection<JpaPid> nextPartition : partitions) {
|
||||
Query q = entityManager.createNativeQuery(sql, Tuple.class);
|
||||
q.setParameter("target_pids", JpaPid.toLongList(nextPartition));
|
||||
localReferenceQueryParams.forEach(q::setParameter);
|
||||
canonicalQuery.getRight().forEach(q::setParameter);
|
||||
|
||||
if (maxCount != null) {
|
||||
q.setMaxResults(maxCount);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Tuple> results = q.getResultList();
|
||||
for (Tuple result : results) {
|
||||
if (result != null) {
|
||||
Long resourceId =
|
||||
NumberUtils.createLong(String.valueOf(result.get(RESOURCE_ID_ALIAS)));
|
||||
Long resourceVersion = null;
|
||||
if (findVersionFieldName != null && result.get(RESOURCE_VERSION_ALIAS) != null) {
|
||||
resourceVersion = NumberUtils.createLong(
|
||||
String.valueOf(result.get(RESOURCE_VERSION_ALIAS)));
|
||||
}
|
||||
pidsToInclude.add(JpaPid.fromIdAndVersion(resourceId, resourceVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadIncludesMatchSpecific(
|
||||
nextInclude,
|
||||
fhirContext,
|
||||
findPidFieldName,
|
||||
findVersionFieldName,
|
||||
searchPidFieldName,
|
||||
reverseMode,
|
||||
nextRoundMatches,
|
||||
entityManager,
|
||||
maxCount,
|
||||
pidsToInclude);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1609,9 +1450,264 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
return allAdded;
|
||||
}
|
||||
|
||||
private void loadIncludesMatchSpecific(
|
||||
Include nextInclude,
|
||||
FhirContext fhirContext,
|
||||
String findPidFieldName,
|
||||
String findVersionFieldName,
|
||||
String searchPidFieldName,
|
||||
boolean reverseMode,
|
||||
List<JpaPid> nextRoundMatches,
|
||||
EntityManager entityManager,
|
||||
Integer maxCount,
|
||||
HashSet<JpaPid> pidsToInclude) {
|
||||
List<String> paths;
|
||||
|
||||
// Start replace
|
||||
RuntimeSearchParam param;
|
||||
String resType = nextInclude.getParamType();
|
||||
if (isBlank(resType)) {
|
||||
return;
|
||||
}
|
||||
RuntimeResourceDefinition def = fhirContext.getResourceDefinition(resType);
|
||||
if (def == null) {
|
||||
ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue());
|
||||
return;
|
||||
}
|
||||
|
||||
String paramName = nextInclude.getParamName();
|
||||
if (isNotBlank(paramName)) {
|
||||
param = mySearchParamRegistry.getActiveSearchParam(resType, paramName);
|
||||
} else {
|
||||
param = null;
|
||||
}
|
||||
if (param == null) {
|
||||
ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue());
|
||||
return;
|
||||
}
|
||||
|
||||
paths = param.getPathsSplitForResourceType(resType);
|
||||
// end replace
|
||||
|
||||
Set<String> targetResourceTypes = computeTargetResourceTypes(nextInclude, param);
|
||||
|
||||
for (String nextPath : paths) {
|
||||
String findPidFieldSqlColumn =
|
||||
findPidFieldName.equals(MY_SOURCE_RESOURCE_PID) ? "src_resource_id" : "target_resource_id";
|
||||
String fieldsToLoad = "r." + findPidFieldSqlColumn + " AS " + RESOURCE_ID_ALIAS;
|
||||
if (findVersionFieldName != null) {
|
||||
fieldsToLoad += ", r.target_resource_version AS " + RESOURCE_VERSION_ALIAS;
|
||||
}
|
||||
|
||||
// Query for includes lookup has 2 cases
|
||||
// Case 1: Where target_resource_id is available in hfj_res_link table for local references
|
||||
// Case 2: Where target_resource_id is null in hfj_res_link table and referred by a canonical
|
||||
// url in target_resource_url
|
||||
|
||||
// Case 1:
|
||||
Map<String, Object> localReferenceQueryParams = new HashMap<>();
|
||||
|
||||
String searchPidFieldSqlColumn =
|
||||
searchPidFieldName.equals(MY_TARGET_RESOURCE_PID) ? "target_resource_id" : "src_resource_id";
|
||||
StringBuilder localReferenceQuery = new StringBuilder("SELECT " + fieldsToLoad + " FROM hfj_res_link r "
|
||||
+ " WHERE r.src_path = :src_path AND "
|
||||
+ " r.target_resource_id IS NOT NULL AND "
|
||||
+ " r."
|
||||
+ searchPidFieldSqlColumn + " IN (:target_pids) ");
|
||||
localReferenceQueryParams.put("src_path", nextPath);
|
||||
// we loop over target_pids later.
|
||||
if (targetResourceTypes != null) {
|
||||
if (targetResourceTypes.size() == 1) {
|
||||
localReferenceQuery.append(" AND r.target_resource_type = :target_resource_type ");
|
||||
localReferenceQueryParams.put(
|
||||
"target_resource_type",
|
||||
targetResourceTypes.iterator().next());
|
||||
} else {
|
||||
localReferenceQuery.append(" AND r.target_resource_type in (:target_resource_types) ");
|
||||
localReferenceQueryParams.put("target_resource_types", targetResourceTypes);
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2:
|
||||
Pair<String, Map<String, Object>> canonicalQuery =
|
||||
buildCanonicalUrlQuery(findVersionFieldName, targetResourceTypes, reverseMode);
|
||||
|
||||
String sql = localReferenceQuery + " UNION " + canonicalQuery.getLeft();
|
||||
|
||||
List<Collection<JpaPid>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||
for (Collection<JpaPid> nextPartition : partitions) {
|
||||
Query q = entityManager.createNativeQuery(sql, Tuple.class);
|
||||
q.setParameter("target_pids", JpaPid.toLongList(nextPartition));
|
||||
localReferenceQueryParams.forEach(q::setParameter);
|
||||
canonicalQuery.getRight().forEach(q::setParameter);
|
||||
|
||||
if (maxCount != null) {
|
||||
q.setMaxResults(maxCount);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Tuple> results = q.getResultList();
|
||||
for (Tuple result : results) {
|
||||
if (result != null) {
|
||||
Long resourceId = NumberUtils.createLong(String.valueOf(result.get(RESOURCE_ID_ALIAS)));
|
||||
Long resourceVersion = null;
|
||||
if (findVersionFieldName != null && result.get(RESOURCE_VERSION_ALIAS) != null) {
|
||||
resourceVersion =
|
||||
NumberUtils.createLong(String.valueOf(result.get(RESOURCE_VERSION_ALIAS)));
|
||||
}
|
||||
pidsToInclude.add(JpaPid.fromIdAndVersion(resourceId, resourceVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadIncludesMatchAll(
|
||||
String findPidFieldName,
|
||||
String findResourceTypeFieldName,
|
||||
String findVersionFieldName,
|
||||
String searchPidFieldName,
|
||||
String wantResourceType,
|
||||
boolean reverseMode,
|
||||
boolean hasDesiredResourceTypes,
|
||||
List<JpaPid> nextRoundMatches,
|
||||
EntityManager entityManager,
|
||||
Integer maxCount,
|
||||
List<String> desiredResourceTypes,
|
||||
HashSet<JpaPid> pidsToInclude,
|
||||
RequestDetails request) {
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append("SELECT r.").append(findPidFieldName);
|
||||
sqlBuilder.append(", r.").append(findResourceTypeFieldName);
|
||||
sqlBuilder.append(", r.myTargetResourceUrl");
|
||||
if (findVersionFieldName != null) {
|
||||
sqlBuilder.append(", r.").append(findVersionFieldName);
|
||||
}
|
||||
sqlBuilder.append(" FROM ResourceLink r WHERE ");
|
||||
|
||||
sqlBuilder.append("r.");
|
||||
sqlBuilder.append(searchPidFieldName); // (rev mode) target_resource_id | source_resource_id
|
||||
sqlBuilder.append(" IN (:target_pids)");
|
||||
|
||||
/*
|
||||
* We need to set the resource type in 2 cases only:
|
||||
* 1) we are in $everything mode
|
||||
* (where we only want to fetch specific resource types, regardless of what is
|
||||
* available to fetch)
|
||||
* 2) we are doing revincludes
|
||||
*
|
||||
* Technically if the request is a qualified star (e.g. _include=Observation:*) we
|
||||
* should always be checking the source resource type on the resource link. We don't
|
||||
* actually index that column though by default, so in order to try and be efficient
|
||||
* we don't actually include it for includes (but we do for revincludes). This is
|
||||
* because for an include, it doesn't really make sense to include a different
|
||||
* resource type than the one you are searching on.
|
||||
*/
|
||||
if (wantResourceType != null && (reverseMode || (myParams != null && myParams.getEverythingMode() != null))) {
|
||||
// because mySourceResourceType is not part of the HFJ_RES_LINK
|
||||
// index, this might not be the most optimal performance.
|
||||
// but it is for an $everything operation (and maybe we should update the index)
|
||||
sqlBuilder.append(" AND r.mySourceResourceType = :want_resource_type");
|
||||
} else {
|
||||
wantResourceType = null;
|
||||
}
|
||||
|
||||
// When calling $everything on a Patient instance, we don't want to recurse into new Patient
|
||||
// resources
|
||||
// (e.g. via Provenance, List, or Group) when in an $everything operation
|
||||
if (myParams != null
|
||||
&& myParams.getEverythingMode() == SearchParameterMap.EverythingModeEnum.PATIENT_INSTANCE) {
|
||||
sqlBuilder.append(" AND r.myTargetResourceType != 'Patient'");
|
||||
sqlBuilder.append(UNDESIRED_RESOURCE_LINKAGES_FOR_EVERYTHING_ON_PATIENT_INSTANCE.stream()
|
||||
.collect(Collectors.joining("', '", " AND r.mySourceResourceType NOT IN ('", "')")));
|
||||
}
|
||||
if (hasDesiredResourceTypes) {
|
||||
sqlBuilder.append(" AND r.myTargetResourceType IN (:desired_target_resource_types)");
|
||||
}
|
||||
|
||||
String sql = sqlBuilder.toString();
|
||||
List<Collection<JpaPid>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||
for (Collection<JpaPid> nextPartition : partitions) {
|
||||
TypedQuery<?> q = entityManager.createQuery(sql, Object[].class);
|
||||
q.setParameter("target_pids", JpaPid.toLongList(nextPartition));
|
||||
if (wantResourceType != null) {
|
||||
q.setParameter("want_resource_type", wantResourceType);
|
||||
}
|
||||
if (maxCount != null) {
|
||||
q.setMaxResults(maxCount);
|
||||
}
|
||||
if (hasDesiredResourceTypes) {
|
||||
q.setParameter("desired_target_resource_types", desiredResourceTypes);
|
||||
}
|
||||
List<?> results = q.getResultList();
|
||||
Set<String> canonicalUrls = null;
|
||||
for (Object nextRow : results) {
|
||||
if (nextRow == null) {
|
||||
// This can happen if there are outgoing references which are canonical or point to
|
||||
// other servers
|
||||
continue;
|
||||
}
|
||||
|
||||
Long version = null;
|
||||
Long resourceId = (Long) ((Object[]) nextRow)[0];
|
||||
String resourceType = (String) ((Object[]) nextRow)[1];
|
||||
String resourceCanonicalUrl = (String) ((Object[]) nextRow)[2];
|
||||
if (findVersionFieldName != null) {
|
||||
version = (Long) ((Object[]) nextRow)[3];
|
||||
}
|
||||
|
||||
if (resourceId != null) {
|
||||
JpaPid pid = JpaPid.fromIdAndVersionAndResourceType(resourceId, version, resourceType);
|
||||
pidsToInclude.add(pid);
|
||||
} else if (resourceCanonicalUrl != null) {
|
||||
if (canonicalUrls == null) {
|
||||
canonicalUrls = new HashSet<>();
|
||||
}
|
||||
canonicalUrls.add(resourceCanonicalUrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (canonicalUrls != null) {
|
||||
String message =
|
||||
"Search with _include=* can be inefficient when references using canonical URLs are detected. Use more specific _include values instead.";
|
||||
firePerformanceWarning(request, message);
|
||||
loadCanonicalUrls(canonicalUrls, entityManager, pidsToInclude, reverseMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCanonicalUrls(
|
||||
Set<String> theCanonicalUrls,
|
||||
EntityManager theEntityManager,
|
||||
HashSet<JpaPid> thePidsToInclude,
|
||||
boolean theReverse) {
|
||||
StringBuilder sqlBuilder;
|
||||
Set<Long> identityHashesForTypes = calculateIndexUriIdentityHashesForResourceTypes(null, theReverse);
|
||||
List<Collection<String>> canonicalUrlPartitions =
|
||||
partition(theCanonicalUrls, getMaximumPageSize() - identityHashesForTypes.size());
|
||||
|
||||
sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append("SELECT i.myResourcePid ");
|
||||
sqlBuilder.append("FROM ResourceIndexedSearchParamUri i ");
|
||||
sqlBuilder.append("WHERE i.myHashIdentity IN (:hash_identity) ");
|
||||
sqlBuilder.append("AND i.myUri IN (:uris)");
|
||||
|
||||
String canonicalResSql = sqlBuilder.toString();
|
||||
|
||||
for (Collection<String> nextCanonicalUrlList : canonicalUrlPartitions) {
|
||||
TypedQuery<Long> canonicalResIdQuery = theEntityManager.createQuery(canonicalResSql, Long.class);
|
||||
canonicalResIdQuery.setParameter("hash_identity", identityHashesForTypes);
|
||||
canonicalResIdQuery.setParameter("uris", nextCanonicalUrlList);
|
||||
List<Long> resIds = canonicalResIdQuery.getResultList();
|
||||
for (var next : resIds) {
|
||||
if (next != null) {
|
||||
thePidsToInclude.add(JpaPid.fromId(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a
|
||||
* @param request
|
||||
* Sends a raw SQL query to the Pointcut for raw SQL queries.
|
||||
*/
|
||||
private void callRawSqlHookWithCurrentThreadQueries(RequestDetails request) {
|
||||
SqlQueryList capturedQueries = CurrentThreadCaptureQueriesListener.getCurrentQueueAndStopCapturing();
|
||||
|
@ -1641,30 +1737,22 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
|
||||
@Nonnull
|
||||
private Pair<String, Map<String, Object>> buildCanonicalUrlQuery(
|
||||
String theVersionFieldName, String thePidFieldSqlColumn, Set<String> theTargetResourceTypes) {
|
||||
String fieldsToLoadFromSpidxUriTable = "rUri.res_id";
|
||||
String theVersionFieldName, Set<String> theTargetResourceTypes, boolean theReverse) {
|
||||
String fieldsToLoadFromSpidxUriTable = theReverse ? "r.src_resource_id" : "rUri.res_id";
|
||||
if (theVersionFieldName != null) {
|
||||
// canonical-uri references aren't versioned, but we need to match the column count for the UNION
|
||||
fieldsToLoadFromSpidxUriTable += ", NULL";
|
||||
}
|
||||
// The logical join will be by hfj_spidx_uri on sp_name='uri' and sp_uri=target_resource_url.
|
||||
// But sp_name isn't indexed, so we use hash_identity instead.
|
||||
if (theTargetResourceTypes == null) {
|
||||
// hash_identity includes the resource type. So a null wildcard must be replaced with a list of all types.
|
||||
theTargetResourceTypes = myDaoRegistry.getRegisteredDaoTypes();
|
||||
}
|
||||
assert !theTargetResourceTypes.isEmpty();
|
||||
|
||||
Set<Long> identityHashesForTypes = theTargetResourceTypes.stream()
|
||||
.map(type -> BaseResourceIndexedSearchParam.calculateHashIdentity(
|
||||
myPartitionSettings, myRequestPartitionId, type, "url"))
|
||||
.collect(Collectors.toSet());
|
||||
Set<Long> identityHashesForTypes =
|
||||
calculateIndexUriIdentityHashesForResourceTypes(theTargetResourceTypes, theReverse);
|
||||
|
||||
Map<String, Object> canonicalUriQueryParams = new HashMap<>();
|
||||
StringBuilder canonicalUrlQuery = new StringBuilder(
|
||||
"SELECT " + fieldsToLoadFromSpidxUriTable + " FROM hfj_res_link r " + " JOIN hfj_spidx_uri rUri ON ( ");
|
||||
// join on hash_identity and sp_uri - indexed in IDX_SP_URI_HASH_IDENTITY_V2
|
||||
if (theTargetResourceTypes.size() == 1) {
|
||||
if (theTargetResourceTypes != null && theTargetResourceTypes.size() == 1) {
|
||||
canonicalUrlQuery.append(" rUri.hash_identity = :uri_identity_hash ");
|
||||
canonicalUriQueryParams.put(
|
||||
"uri_identity_hash", identityHashesForTypes.iterator().next());
|
||||
|
@ -1673,21 +1761,102 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
canonicalUriQueryParams.put("uri_identity_hashes", identityHashesForTypes);
|
||||
}
|
||||
|
||||
canonicalUrlQuery.append(" AND r.target_resource_url = rUri.sp_uri )" + " WHERE r.src_path = :src_path AND "
|
||||
+ " r.target_resource_id IS NULL AND "
|
||||
+ " r."
|
||||
+ thePidFieldSqlColumn + " IN (:target_pids) ");
|
||||
canonicalUrlQuery.append(" AND r.target_resource_url = rUri.sp_uri )");
|
||||
canonicalUrlQuery.append(" WHERE r.src_path = :src_path AND ");
|
||||
canonicalUrlQuery.append(" r.target_resource_id IS NULL ");
|
||||
canonicalUrlQuery.append(" AND ");
|
||||
if (theReverse) {
|
||||
canonicalUrlQuery.append("rUri.res_id");
|
||||
} else {
|
||||
canonicalUrlQuery.append("r.src_resource_id");
|
||||
}
|
||||
canonicalUrlQuery.append(" IN (:target_pids) ");
|
||||
|
||||
return Pair.of(canonicalUrlQuery.toString(), canonicalUriQueryParams);
|
||||
}
|
||||
|
||||
private List<Collection<JpaPid>> partition(Collection<JpaPid> theNextRoundMatches, int theMaxLoad) {
|
||||
@Nonnull
|
||||
Set<Long> calculateIndexUriIdentityHashesForResourceTypes(Set<String> theTargetResourceTypes, boolean theReverse) {
|
||||
Set<String> targetResourceTypes = theTargetResourceTypes;
|
||||
if (targetResourceTypes == null) {
|
||||
/*
|
||||
* If we don't have a list of valid target types, we need to figure out a list of all
|
||||
* possible target types in order to perform the search of the URI index table. This is
|
||||
* because the hash_identity column encodes the resource type, so we'll need a hash
|
||||
* value for each possible target type.
|
||||
*/
|
||||
targetResourceTypes = new HashSet<>();
|
||||
Set<String> possibleTypes = myDaoRegistry.getRegisteredDaoTypes();
|
||||
if (theReverse) {
|
||||
// For reverse includes, it is really hard to figure out what types
|
||||
// are actually potentially pointing to the type we're searching for
|
||||
// in this context, so let's just assume it could be anything.
|
||||
targetResourceTypes = possibleTypes;
|
||||
} else {
|
||||
for (var next : mySearchParamRegistry.getActiveSearchParams(myResourceName).values().stream()
|
||||
.filter(t -> t.getParamType().equals(RestSearchParameterTypeEnum.REFERENCE))
|
||||
.collect(Collectors.toList())) {
|
||||
|
||||
// If the reference points to a Reference (ie not a canonical or CanonicalReference)
|
||||
// then it doesn't matter here anyhow. The logic here only works for elements at the
|
||||
// root level of the document (e.g. QuestionnaireResponse.subject or
|
||||
// QuestionnaireResponse.subject.where(...)) but this is just an optimization
|
||||
// anyhow.
|
||||
if (next.getPath().startsWith(myResourceName + ".")) {
|
||||
String elementName =
|
||||
next.getPath().substring(next.getPath().indexOf('.') + 1);
|
||||
int secondDotIndex = elementName.indexOf('.');
|
||||
if (secondDotIndex != -1) {
|
||||
elementName = elementName.substring(0, secondDotIndex);
|
||||
}
|
||||
BaseRuntimeChildDefinition child =
|
||||
myContext.getResourceDefinition(myResourceName).getChildByName(elementName);
|
||||
if (child != null) {
|
||||
BaseRuntimeElementDefinition<?> childDef = child.getChildByName(elementName);
|
||||
if (childDef != null) {
|
||||
if (childDef.getName().equals("Reference")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!next.getTargets().isEmpty()) {
|
||||
// For each reference parameter on the resource type we're searching for,
|
||||
// add all the potential target types to the list of possible target
|
||||
// resource types we can look up.
|
||||
for (var nextTarget : next.getTargets()) {
|
||||
if (possibleTypes.contains(nextTarget)) {
|
||||
targetResourceTypes.add(nextTarget);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we have any references that don't define any target types, then
|
||||
// we need to assume that all enabled resource types are possible target
|
||||
// types
|
||||
targetResourceTypes.addAll(possibleTypes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert !targetResourceTypes.isEmpty();
|
||||
|
||||
Set<Long> identityHashesForTypes = targetResourceTypes.stream()
|
||||
.map(type -> BaseResourceIndexedSearchParam.calculateHashIdentity(
|
||||
myPartitionSettings, myRequestPartitionId, type, "url"))
|
||||
.collect(Collectors.toSet());
|
||||
return identityHashesForTypes;
|
||||
}
|
||||
|
||||
private <T> List<Collection<T>> partition(Collection<T> theNextRoundMatches, int theMaxLoad) {
|
||||
if (theNextRoundMatches.size() <= theMaxLoad) {
|
||||
return Collections.singletonList(theNextRoundMatches);
|
||||
} else {
|
||||
|
||||
List<Collection<JpaPid>> retVal = new ArrayList<>();
|
||||
Collection<JpaPid> current = null;
|
||||
for (JpaPid next : theNextRoundMatches) {
|
||||
List<Collection<T>> retVal = new ArrayList<>();
|
||||
Collection<T> current = null;
|
||||
for (T next : theNextRoundMatches) {
|
||||
if (current == null) {
|
||||
current = new ArrayList<>(theMaxLoad);
|
||||
retVal.add(current);
|
||||
|
@ -2121,19 +2290,11 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
|
||||
private void sendProcessingMsgAndFirePerformanceHook() {
|
||||
StorageProcessingMessage message = new StorageProcessingMessage();
|
||||
String msg = "Pass completed with no matching results seeking rows "
|
||||
+ myPidSet.size() + "-" + mySkipCount
|
||||
+ ". This indicates an inefficient query! Retrying with new max count of "
|
||||
+ myMaxResultsToFetch;
|
||||
ourLog.warn(msg);
|
||||
message.setMessage(msg);
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, myRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, myRequest)
|
||||
.add(StorageProcessingMessage.class, message);
|
||||
CompositeInterceptorBroadcaster.doCallHooks(
|
||||
myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
|
||||
firePerformanceWarning(myRequest, msg);
|
||||
}
|
||||
|
||||
private void initializeIteratorQuery(Integer theOffset, Integer theMaxResultsToFetch) {
|
||||
|
@ -2208,6 +2369,18 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
}
|
||||
|
||||
private void firePerformanceWarning(RequestDetails theRequest, String theMessage) {
|
||||
ourLog.warn(theMessage);
|
||||
StorageProcessingMessage message = new StorageProcessingMessage();
|
||||
message.setMessage(theMessage);
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(StorageProcessingMessage.class, message);
|
||||
CompositeInterceptorBroadcaster.doCallHooks(
|
||||
myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
|
||||
}
|
||||
|
||||
public static int getMaximumPageSize() {
|
||||
if (myUseMaxPageSize50ForTest) {
|
||||
return MAXIMUM_PAGE_SIZE_FOR_TESTING;
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package ca.uhn.fhir.jpa.search.builder;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.rest.server.util.FhirContextSearchParamRegistry;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SearchBuilderTest {
|
||||
|
||||
public static final FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
@Spy
|
||||
private FhirContext myFhirContext = ourCtx;
|
||||
|
||||
@Spy
|
||||
private ISearchParamRegistry mySearchParamRegistry = new FhirContextSearchParamRegistry(myFhirContext);
|
||||
|
||||
@Spy
|
||||
private PartitionSettings myPartitionSettings = new PartitionSettings();
|
||||
|
||||
@Mock(strictness = Mock.Strictness.LENIENT)
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@InjectMocks
|
||||
private SearchBuilder mySearchBuilder;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
mySearchBuilder.myResourceName = "QuestionnaireResponse";
|
||||
when(myDaoRegistry.getRegisteredDaoTypes()).thenReturn(ourCtx.getResourceTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCalculateIndexUriIdentityHashesForResourceTypes_Include_Null() {
|
||||
Set<Long> types = mySearchBuilder.calculateIndexUriIdentityHashesForResourceTypes(null, false);
|
||||
// There are only 12 resource types that actually can be linked to by the QuestionnaireResponse
|
||||
// resource via canonical references in any parameters
|
||||
assertThat(types).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCalculateIndexUriIdentityHashesForResourceTypes_Include_Nonnull() {
|
||||
Set<String> inputTypes = Set.of("Questionnaire");
|
||||
Set<Long> types = mySearchBuilder.calculateIndexUriIdentityHashesForResourceTypes(inputTypes, false);
|
||||
// Just the one that we actually specified
|
||||
assertThat(types).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCalculateIndexUriIdentityHashesForResourceTypes_RevInclude_Null() {
|
||||
Set<Long> types = mySearchBuilder.calculateIndexUriIdentityHashesForResourceTypes(null, true);
|
||||
// Revincludes are really hard to figure out the potential resource types for, so we just need to
|
||||
// use all active resource types
|
||||
assertThat(types).hasSize(146);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -228,6 +228,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
|
|||
b.append("paramName", getParamName());
|
||||
b.append("uri", myUri);
|
||||
b.append("hashUri", myHashUri);
|
||||
b.append("hashIdentity", myHashIdentity);
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -304,11 +304,11 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
StringBuilder b = new StringBuilder();
|
||||
b.append("ResourceLink[");
|
||||
b.append("path=").append(mySourcePath);
|
||||
b.append(", src=").append(mySourceResourcePid);
|
||||
b.append(", target=").append(myTargetResourcePid);
|
||||
b.append(", targetType=").append(myTargetResourceType);
|
||||
b.append(", targetVersion=").append(myTargetResourceVersion);
|
||||
b.append(", targetUrl=").append(myTargetResourceUrl);
|
||||
b.append(", srcResId=").append(mySourceResourcePid);
|
||||
b.append(", targetResId=").append(myTargetResourcePid);
|
||||
b.append(", targetResType=").append(myTargetResourceType);
|
||||
b.append(", targetResVersion=").append(myTargetResourceVersion);
|
||||
b.append(", targetResUrl=").append(myTargetResourceUrl);
|
||||
|
||||
b.append("]");
|
||||
return b.toString();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -2161,6 +2161,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
|
||||
if (parsed.isAbsolute()) {
|
||||
String refValue =
|
||||
fakeReference.getReferenceElement().getValue();
|
||||
if (refValue.contains("|")) {
|
||||
fakeReference.setReference(refValue.substring(0, refValue.indexOf('|')));
|
||||
}
|
||||
|
||||
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true);
|
||||
theParams.add(myPathAndRef);
|
||||
break;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -13,6 +13,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.api.dao.ReindexParameters;
|
||||
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||
|
@ -72,6 +73,8 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.Provenance;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.Questionnaire;
|
||||
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
|
@ -1505,6 +1508,40 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchWithRevInclude() {
|
||||
Questionnaire q = new Questionnaire();
|
||||
q.setId("q");
|
||||
q.setUrl("http://foo");
|
||||
q.setVersion("1.0");
|
||||
myQuestionnaireDao.update(q, mySrd);
|
||||
|
||||
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||
qr.setId("qr");
|
||||
qr.setQuestionnaire("http://foo");
|
||||
myQuestionnaireResponseDao.update(qr, mySrd);
|
||||
|
||||
logAllResourceLinks();
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add("_id", new ReferenceParam("Questionnaire/q"));
|
||||
map.addRevInclude(QuestionnaireResponse.INCLUDE_QUESTIONNAIRE);
|
||||
IFhirResourceDao<?> dao = myQuestionnaireDao;
|
||||
dao.search(map, mySrd);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
IBundleProvider outcome = dao.search(map, mySrd);
|
||||
toUnqualifiedVersionlessIdValues(outcome);
|
||||
assertEquals(4, myCaptureQueriesListener.countSelectQueries());
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = dao.search(map, mySrd);
|
||||
toUnqualifiedVersionlessIdValues(outcome);
|
||||
assertEquals(4, myCaptureQueriesListener.countSelectQueries());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.util.SqlQuery;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
|
@ -12,9 +17,7 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
|
|||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.BodyStructure;
|
||||
import org.hl7.fhir.r4.model.CarePlan;
|
||||
|
@ -23,34 +26,169 @@ import org.hl7.fhir.r4.model.EpisodeOfCare;
|
|||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Procedure;
|
||||
import org.hl7.fhir.r4.model.Questionnaire;
|
||||
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.SearchParameter;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
|
||||
@SuppressWarnings({"unchecked", "Duplicates"})
|
||||
@SuppressWarnings({"Duplicates"})
|
||||
public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchIncludeTest.class);
|
||||
|
||||
@Mock
|
||||
private IAnonymousInterceptor myAnonymousInterceptor;
|
||||
@Captor
|
||||
private ArgumentCaptor<HookParams> myParamsCaptor;
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() {
|
||||
myStorageSettings.setMaximumIncludesToLoadPerPage(JpaStorageSettings.DEFAULT_MAXIMUM_INCLUDES_TO_LOAD_PER_PAGE);
|
||||
myInterceptorRegistry.unregisterInterceptor(myAnonymousInterceptor);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
// theQuestionnaireRespId, theReverse, theMatchAll
|
||||
"QuestionnaireResponse/qr , false, false",
|
||||
"QuestionnaireResponse/qr2 , false, false",
|
||||
"QuestionnaireResponse/qr , true, false",
|
||||
"QuestionnaireResponse/qr2 , true, false",
|
||||
"QuestionnaireResponse/qr , false, true",
|
||||
"QuestionnaireResponse/qr2 , false, true",
|
||||
// "QuestionnaireResponse/qr , true, true", // Not yet supported
|
||||
// "QuestionnaireResponse/qr2 , true, true", // Not yet supported
|
||||
})
|
||||
public void testIncludeCanonicalReference(String theQuestionnaireRespId, boolean theReverse, boolean theMatchAll) {
|
||||
Questionnaire qWrongVersion = new Questionnaire();
|
||||
qWrongVersion.setId("qWrongVersion");
|
||||
qWrongVersion.setUrl("http://foo");
|
||||
qWrongVersion.setVersion("99.0");
|
||||
myQuestionnaireDao.update(qWrongVersion, mySrd);
|
||||
|
||||
Questionnaire q = new Questionnaire();
|
||||
q.setId("q");
|
||||
q.setUrl("http://foo");
|
||||
q.setVersion("1.0");
|
||||
myQuestionnaireDao.update(q, mySrd);
|
||||
|
||||
if (theQuestionnaireRespId.equals("QuestionnaireResponse/qr")) {
|
||||
QuestionnaireResponse qr = new QuestionnaireResponse();
|
||||
qr.setId("qr");
|
||||
qr.setQuestionnaire("http://foo");
|
||||
myQuestionnaireResponseDao.update(qr, mySrd);
|
||||
} else {
|
||||
QuestionnaireResponse qr2 = new QuestionnaireResponse();
|
||||
qr2.setId("qr2");
|
||||
qr2.setQuestionnaire("http://foo|1.0");
|
||||
myQuestionnaireResponseDao.update(qr2, mySrd);
|
||||
}
|
||||
|
||||
logAllUriIndexes();
|
||||
logAllResourceLinks();
|
||||
|
||||
// Create a QR and Q that have other URLs and shouldn't be turned up in searches here
|
||||
Questionnaire qIrrelevant = new Questionnaire();
|
||||
qIrrelevant.setId("qIrrelevant");
|
||||
qIrrelevant.setUrl("http://fooIrrelevant");
|
||||
qIrrelevant.setVersion("1.0");
|
||||
myQuestionnaireDao.update(qIrrelevant, mySrd);
|
||||
|
||||
QuestionnaireResponse qrIrrelevant = new QuestionnaireResponse();
|
||||
qrIrrelevant.setId("qrIrrelevant");
|
||||
qrIrrelevant.setQuestionnaire("http://fooIrrelevant");
|
||||
myQuestionnaireResponseDao.update(qrIrrelevant, mySrd);
|
||||
|
||||
|
||||
IBundleProvider outcome;
|
||||
IFhirResourceDao<?> dao;
|
||||
SearchParameterMap map;
|
||||
String expectWarning = null;
|
||||
if (theReverse) {
|
||||
map = new SearchParameterMap();
|
||||
map.add("_id", new TokenParam("Questionnaire/q"));
|
||||
if (theMatchAll) {
|
||||
map.addRevInclude(IBaseResource.INCLUDE_ALL);
|
||||
} else {
|
||||
map.addRevInclude(QuestionnaireResponse.INCLUDE_QUESTIONNAIRE);
|
||||
}
|
||||
dao = myQuestionnaireDao;
|
||||
} else {
|
||||
map = new SearchParameterMap();
|
||||
map.add("_id", new TokenParam(theQuestionnaireRespId));
|
||||
if (theMatchAll) {
|
||||
map.addInclude(IBaseResource.INCLUDE_ALL);
|
||||
} else {
|
||||
map.addInclude(QuestionnaireResponse.INCLUDE_QUESTIONNAIRE);
|
||||
}
|
||||
dao = myQuestionnaireResponseDao;
|
||||
}
|
||||
|
||||
if (theMatchAll) {
|
||||
expectWarning = "Search with _include=* can be inefficient";
|
||||
}
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_WARNING, myAnonymousInterceptor);
|
||||
|
||||
map.setLoadSynchronous(true);
|
||||
outcome = dao.search(map, mySrd);
|
||||
List<String> outcomeValues = toUnqualifiedVersionlessIdValues(outcome);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
||||
if (theReverse) {
|
||||
assertThat(outcomeValues).as(outcomeValues.toString()).containsExactlyInAnyOrder(
|
||||
theQuestionnaireRespId, "Questionnaire/q"
|
||||
);
|
||||
} else {
|
||||
assertThat(outcomeValues).as(outcomeValues.toString()).containsExactlyInAnyOrder(
|
||||
theQuestionnaireRespId, "Questionnaire/q", "Questionnaire/qWrongVersion"
|
||||
);
|
||||
}
|
||||
|
||||
if (expectWarning == null) {
|
||||
verify(myAnonymousInterceptor, never()).invoke(eq(Pointcut.JPA_PERFTRACE_WARNING), myParamsCaptor.capture());
|
||||
} else {
|
||||
verify(myAnonymousInterceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_WARNING), myParamsCaptor.capture());
|
||||
HookParams params = myParamsCaptor.getValue();
|
||||
assertThat(params.get(StorageProcessingMessage.class).getMessage()).contains(expectWarning);
|
||||
}
|
||||
|
||||
if (!theReverse && theMatchAll) {
|
||||
SqlQuery searchForCanonicalReferencesQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(3);
|
||||
// Make sure we have the right query - If this ever fails, maybe we have optimized the queries
|
||||
// (or somehow made things worse) and the search for the canonical target is no longer the 4th
|
||||
// SQL query
|
||||
assertThat(searchForCanonicalReferencesQuery.getSql(true, false)).contains("rispu1_0.HASH_IDENTITY in ('-600769180185160063')");
|
||||
assertThat(searchForCanonicalReferencesQuery.getSql(true, false)).contains("rispu1_0.SP_URI in ('http://foo')");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testIncludesNotAppliedToIncludedResources() {
|
||||
createOrganizationWithReferencingEpisodesOfCare(10);
|
||||
|
@ -59,7 +197,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
.add("_id", new TokenParam("EOC-0"))
|
||||
.addInclude(new Include("*"))
|
||||
.addRevInclude(new Include("*").setRecurse(true));
|
||||
IBundleProvider results = myEpisodeOfCareDao.search(map);
|
||||
IBundleProvider results = myEpisodeOfCareDao.search(map, mySrd);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
assertThat(ids).as(ids.toString()).containsExactlyInAnyOrder("EpisodeOfCare/EOC-0", "Organization/ORG-0");
|
||||
}
|
||||
|
@ -72,7 +210,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
.setCount(10)
|
||||
.addInclude(new Include("*"))
|
||||
.addRevInclude(new Include("*").setRecurse(true));
|
||||
IBundleProvider results = myOrganizationDao.search(map);
|
||||
IBundleProvider results = myOrganizationDao.search(map, mySrd);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
|
||||
List<String> expected = IntStream.range(0, 10)
|
||||
|
@ -97,7 +235,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
.add("_id", new TokenParam("ORG-0"))
|
||||
.addInclude(new Include("*"))
|
||||
.addRevInclude(new Include("*").setRecurse(true));
|
||||
IBundleProvider results = myOrganizationDao.search(map);
|
||||
IBundleProvider results = myOrganizationDao.search(map, mySrd);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
assertThat(ids).as(ids.toString()).containsExactlyInAnyOrder("EpisodeOfCare/EOC-0", "EpisodeOfCare/EOC-1", "EpisodeOfCare/EOC-2", "EpisodeOfCare/EOC-3", "EpisodeOfCare/EOC-4", "Organization/ORG-0");
|
||||
}
|
||||
|
@ -110,7 +248,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
SearchParameterMap map = SearchParameterMap.newSynchronous()
|
||||
.addInclude(new Include("CarePlan.patient"));
|
||||
try {
|
||||
myCarePlanDao.search(map);
|
||||
myCarePlanDao.search(map, mySrd);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// good
|
||||
|
@ -120,7 +258,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
SearchParameterMap map2 = SearchParameterMap.newSynchronous()
|
||||
.addInclude(new Include("CarePlan:patient"));
|
||||
try {
|
||||
IBundleProvider results = myCarePlanDao.search(map2);
|
||||
IBundleProvider results = myCarePlanDao.search(map2, mySrd);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
assertThat(ids).as(ids.toString()).containsExactlyInAnyOrder("CarePlan/CP-1", "Patient/PAT-1");
|
||||
} catch (Exception e) {
|
||||
|
@ -161,7 +299,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
logAllResources();
|
||||
logAllResourceLinks();
|
||||
|
||||
// Non synchronous
|
||||
// Non-synchronous
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add("_id", new TokenParam("PRA8780542726"));
|
||||
map.addRevInclude(new Include("Procedure:part-of"));
|
||||
|
@ -194,7 +332,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
.setCount(10)
|
||||
.addInclude(new Include("*"))
|
||||
.addRevInclude(new Include("*").setRecurse(true));
|
||||
IBundleProvider results = myOrganizationDao.search(map);
|
||||
IBundleProvider results = myOrganizationDao.search(map, mySrd);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
assertThat(ids).as(ids.toString()).containsExactlyInAnyOrder("EpisodeOfCare/EOC-0", "EpisodeOfCare/EOC-1", "EpisodeOfCare/EOC-2", "EpisodeOfCare/EOC-3", "Organization/ORG-0", "Organization/ORG-P");
|
||||
|
||||
|
@ -210,7 +348,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
.add("_id", new TokenParam("ORG-0"))
|
||||
.addRevInclude(EpisodeOfCare.INCLUDE_ORGANIZATION);
|
||||
myCaptureQueriesListener.clear();
|
||||
IBundleProvider results = myOrganizationDao.search(map);
|
||||
IBundleProvider results = myOrganizationDao.search(map, mySrd);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertThat(ids).as(ids.toString()).containsExactlyInAnyOrder("EpisodeOfCare/EOC-0", "EpisodeOfCare/EOC-1", "EpisodeOfCare/EOC-2", "EpisodeOfCare/EOC-3", "EpisodeOfCare/EOC-4", "EpisodeOfCare/EOC-5", "EpisodeOfCare/EOC-6", "EpisodeOfCare/EOC-7", "EpisodeOfCare/EOC-8", "EpisodeOfCare/EOC-9", "Organization/ORG-0");
|
||||
|
@ -223,38 +361,40 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
Organization org = new Organization();
|
||||
org.setId("Organization/ORG-P");
|
||||
org.setName("ORG-P");
|
||||
myOrganizationDao.update(org);
|
||||
myOrganizationDao.update(org, mySrd);
|
||||
|
||||
org = new Organization();
|
||||
org.setId("Organization/ORG-0");
|
||||
org.setName("ORG-0");
|
||||
org.setPartOf(new Reference("Organization/ORG-P"));
|
||||
myOrganizationDao.update(org);
|
||||
myOrganizationDao.update(org, mySrd);
|
||||
|
||||
for (int i = 0; i < theEocCount; i++) {
|
||||
EpisodeOfCare eoc = new EpisodeOfCare();
|
||||
eoc.setId("EpisodeOfCare/EOC-" + i);
|
||||
eoc.getManagingOrganization().setReference("Organization/ORG-0");
|
||||
myEpisodeOfCareDao.update(eoc);
|
||||
myEpisodeOfCareDao.update(eoc, mySrd);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void createPatientWithReferencingCarePlan(int theCount) {
|
||||
org.hl7.fhir.r4.model.Patient patient = new Patient();
|
||||
patient.setId("Patient/PAT-1");
|
||||
myPatientDao.update(patient);
|
||||
myPatientDao.update(patient, mySrd);
|
||||
|
||||
for (int i = 1; i <= theCount; i++) {
|
||||
CarePlan carePlan = new CarePlan();
|
||||
carePlan.setId("CarePlan/CP-" + i);
|
||||
carePlan.getSubject().setReference("Patient/PAT-1");
|
||||
myCarePlanDao.update(carePlan);
|
||||
myCarePlanDao.update(carePlan, mySrd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/hapifhir/hapi-fhir/issues/4896
|
||||
* <a href="https://github.com/hapifhir/hapi-fhir/issues/4896">#4896</a>
|
||||
*/
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
@Test
|
||||
void testLastUpdatedDoesNotApplyToForwardOrRevIncludes() {
|
||||
// given
|
||||
|
@ -277,7 +417,7 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
// when
|
||||
// "Patient?_lastUpdated=gt2023-01-01&_revinclude=Group:member&_revinclude=CareTeam:subject&_include=Patient:organization");
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, Date.from(now))));
|
||||
map.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, java.util.Date.from(now))));
|
||||
map.addInclude(new Include("Patient:organization"));
|
||||
map.addRevInclude(new Include("Group:member"));
|
||||
map.addRevInclude(new Include("CareTeam:subject"));
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-caching-api</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -8,7 +8,7 @@
|
|||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<name>HAPI-FHIR</name>
|
||||
<description>An open-source implementation of the FHIR specification in Java.</description>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>7.3.6-SNAPSHOT</version>
|
||||
<version>7.3.7-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
Loading…
Reference in New Issue