SQL Join Rework (#2086)

* switched to adding annotations.  Just did a test with Token, but the sql looks clean.

* switched to adding annotations.  Just did a test with Token, but the sql looks clean.

* added the other six index links

* trying different annotations

* Start testing

* Update resources on package install

* Add changelog

* Join rework

* CLean up SQL builder

* Add docs

* SP rework

* Join work

* Work on params

* Work on refactor

* Work on chains

* Work on joins

* Rework queries

* Work on queries

* Many more tests passing

* Refs test

* Work on sorting

* Work on tests

* More joins work

* Work on tests

* Work on queries

* Tests passing

* More test fixes

* Test fixes

* Work on SQL

* Tests passing

* Add some tests

* Add some tests

* License headers

* Use entity manager to get datasourcd

* One more fix

* Model cleanup

* Ongoing work

* Fixes

* Fixes

* Work on joins

* Ongoing fixes

* Merge conflict

* Cleanup

* clean up unused fields

* Work on join

* COmpile fix

* Rework querying

* Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HibernateDialectProvider.java

Co-authored-by: Ken Stevens <khstevens@gmail.com>

* Address review comments

* Resolve fixmes

* Test fix

* Test fixes

* Test fix

Co-authored-by: Ken Stevens <khstevens@gmail.com>
This commit is contained in:
James Agnew 2020-10-26 05:24:26 -04:00 committed by GitHub
parent 4a5f5199b5
commit 8000d2d0cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 13169 additions and 858 deletions

View File

@ -208,7 +208,7 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
}
public boolean isEmpty() {
return StringUtils.isEmpty(myValue);
return StringUtils.isBlank(mySystem) && StringUtils.isBlank(myValue) && getMissing() == null;
}
/**

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
public class IoUtil {
/**
* Replacement for the deprecated commons-lang method of the same name. Use sparingly
* since they are right that most uses of this should be replaced with try-with-resources
*/
public static void closeQuietly(final AutoCloseable theCloseable) {
try {
if (theCloseable != null) {
theCloseable.close();
}
} catch (final Exception ioe) {
// ignore
}
}
}

View File

@ -101,6 +101,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully create
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSortParameter=Unknown _sort parameter value "{0}" for resource type "{1}" (Note: sort parameters values must use a valid Search Parameter). Valid values for this search are: {2}
ca.uhn.fhir.rest.api.PatchTypeEnum.missingPatchContentType=Missing or invalid content type for PATCH operation
ca.uhn.fhir.rest.api.PatchTypeEnum.invalidPatchContentType=Invalid Content-Type for PATCH operation: {0}
@ -115,11 +116,11 @@ ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destina
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.matchesFound=Matches found!
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.noMatchesFound=No matches found!

View File

@ -0,0 +1,30 @@
package ca.uhn.fhir.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.doThrow;
@ExtendWith(MockitoExtension.class)
public class IoUtilTest {
@Mock
private AutoCloseable myCloseable;
@Test
public void testCloseNull() {
// Should throw no exception
IoUtil.closeQuietly(null);
}
@Test
public void testCloseWithException() throws Exception {
doThrow(new Exception()).when(myCloseable).close();
// Should throw no exception
IoUtil.closeQuietly(myCloseable);
}
}

View File

@ -12,3 +12,17 @@
<li>MomentJS (Testpage Overlay): 2.15.1 -&gt; 2.27.0</li>
<li>Select2 (Testpage Overlay): 4.0.3 -&gt; 4.0.13</li>
</ul>"
- item:
type: "add"
title: "The JPA Server SQL generator for handling FHIR search operations has been completely rewritten to
no longer depend on the Hibernate QueryBuilder APIs. This rewrite produces much more efficient SQL in many cases
and should dramatically increase performance in such cases. For example, a 10x speedup has been observed on
Postgresql when searching a very large database where the search URL has multiple chained search params.
<br/><br/>
This new SQL builder will be disabled by default in HAPI FHIR 5.2.0 and Smile CDR 2020.11.R01, and can be enabled
via a setting in the DaoConfig (HAPI FHIR) and Storage Module config (Smile CDR). Snapshot/prerelease builds will
ship with this new SQL builder enabled by default, and the intention is to make this the default and ultimately
remove the legacy SQL builder in the next quarterly release.
<br/><br/>
We highly encourage testing of this new feature, and welcome feedback on how it performs in your environment.
"

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.api.config;
import ca.uhn.fhir.jpa.api.model.WarmCacheEntry;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.annotations.VisibleForTesting;
@ -83,9 +82,8 @@ public class DaoConfig {
private static final Integer DEFAULT_MAXIMUM_TRANSACTION_BUNDLE_SIZE = null;
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
private static final int DEFAULT_EXPUNGE_BATCH_SIZE = 800;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
private static final int DEFAULT_MAXIMUM_DELETE_CONFLICT_COUNT = 60;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/**
* Child Configurations
*/
@ -196,6 +194,32 @@ public class DaoConfig {
* @since 5.0.0
*/
private boolean myDeleteEnabled = true;
/**
* @since 5.1.0
*/
private boolean myLastNEnabled = false;
/**
* @since 5.2.0
*/
private boolean myUseLegacySearchBuilder = false;
/**
* Constructor
*/
public DaoConfig() {
setSubscriptionEnabled(true);
setSubscriptionPollDelay(0);
setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE);
setMarkResourcesForReindexingUponSearchParameterChange(true);
setReindexThreadCount(Runtime.getRuntime().availableProcessors());
setExpungeThreadCount(Runtime.getRuntime().availableProcessors());
setBundleTypesAllowedForStorage(DEFAULT_BUNDLE_TYPES_ALLOWED_FOR_STORAGE);
if ("true".equalsIgnoreCase(System.getProperty(DISABLE_STATUS_BASED_REINDEX))) {
ourLog.info("Status based reindexing is DISABLED");
setStatusBasedReindexingDisabled(true);
}
}
/**
* If set to <code>true</code> (default is <code>false</code>) the <code>$lastn</code> operation will be enabled for
@ -222,26 +246,27 @@ public class DaoConfig {
}
/**
* @since 5.1.0
* This method controls whether to use the new non-hibernate search SQL builder that was introduced in HAPI FHIR 5.2.0.
* By default this will be <code>false</code> meaning that the new SQL builder is used. Set to <code>true</code> to use the
* legacy SQL builder based on Hibernate.
* <p>Note that this method will be removed in HAPI FHIR 5.4.0</p>
*
* @since 5.3.0
*/
private boolean myLastNEnabled = false;
public boolean isUseLegacySearchBuilder() {
return myUseLegacySearchBuilder;
}
/**
* Constructor
* This method controls whether to use the new non-hibernate search SQL builder that was introduced in HAPI FHIR 5.2.0.
* By default this will be <code>false</code> meaning that the new SQL builder is used. Set to <code>true</code> to use the
* legacy SQL builder based on Hibernate.
* <p>Note that this method will be removed in HAPI FHIR 5.4.0</p>
*
* @since 5.3.0
*/
public DaoConfig() {
setSubscriptionEnabled(true);
setSubscriptionPollDelay(0);
setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE);
setMarkResourcesForReindexingUponSearchParameterChange(true);
setReindexThreadCount(Runtime.getRuntime().availableProcessors());
setExpungeThreadCount(Runtime.getRuntime().availableProcessors());
setBundleTypesAllowedForStorage(DEFAULT_BUNDLE_TYPES_ALLOWED_FOR_STORAGE);
if ("true".equalsIgnoreCase(System.getProperty(DISABLE_STATUS_BASED_REINDEX))) {
ourLog.info("Status based reindexing is DISABLED");
setStatusBasedReindexingDisabled(true);
}
public void setUseLegacySearchBuilder(boolean theUseLegacySearchBuilder) {
myUseLegacySearchBuilder = theUseLegacySearchBuilder;
}
/**
@ -995,43 +1020,6 @@ public class DaoConfig {
myModelConfig.setAllowExternalReferences(theAllowExternalReferences);
}
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
*
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
* </p>
* Default is {@literal true} beginning in HAPI FHIR 5.0
* </p>
*
* @since 5.0
*/
public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
myModelConfig.setUseOrdinalDatesForDayPrecisionSearches(theUseOrdinalDates);
}
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
*
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
* </p>
* Default is {@literal true} beginning in HAPI FHIR 5.0
* </p>
*
* @since 5.0
*/
public boolean getUseOrdinalDatesForDayPrecisionSearches() {
return myModelConfig.getUseOrdinalDatesForDayPrecisionSearches();
}
/**
* @see #setAllowInlineMatchUrlReferences(boolean)
*/
@ -2033,8 +2021,8 @@ public class DaoConfig {
*
* @since 5.0.0
*/
public void setDeleteEnabled(boolean theDeleteEnabled) {
myDeleteEnabled = theDeleteEnabled;
public boolean isDeleteEnabled() {
return myDeleteEnabled;
}
/**
@ -2046,11 +2034,60 @@ public class DaoConfig {
*
* @since 5.0.0
*/
public boolean isDeleteEnabled() {
return myDeleteEnabled;
public void setDeleteEnabled(boolean theDeleteEnabled) {
myDeleteEnabled = theDeleteEnabled;
}
public enum StoreMetaSourceInformationEnum {
/**
* <p>
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
* </p>
* <p>
* The default value for this setting is {@code 60}.
* </p>
*
* @since 5.1.0
*/
public Integer getMaximumDeleteConflictQueryCount() {
return myMaximumDeleteConflictQueryCount;
}
/**
* <p>
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
* </p>
* <p>
* The default value for this setting is {@code 60}.
* </p>
*
* @since 5.1.0
*/
public void setMaximumDeleteConflictQueryCount(Integer theMaximumDeleteConflictQueryCount) {
myMaximumDeleteConflictQueryCount = theMaximumDeleteConflictQueryCount;
}
/**
* <p>
* This determines whether $binary-access-write operations should first load the InputStream into memory before persisting the
* contents to the database. This needs to be enabled for MS SQL Server as this DB requires that the blob size be known
* in advance.
* </p>
* <p>
* Note that this setting should be enabled with caution as it can lead to significant demands on memory.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*
* @since 5.1.0
* @deprecated In 5.2.0 this setting no longer does anything
*/
@Deprecated
public void setPreloadBlobFromInputStream(Boolean thePreloadBlobFromInputStream) {
// ignore
}
public enum StoreMetaSourceInformationEnum {
NONE(false, false),
SOURCE_URI(true, false),
REQUEST_ID(false, true),
@ -2120,53 +2157,4 @@ public class DaoConfig {
ANY
}
/**
* <p>
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
* </p>
* <p>
* The default value for this setting is {@code 60}.
* </p>
*
* @since 5.1.0
*/
public Integer getMaximumDeleteConflictQueryCount() {
return myMaximumDeleteConflictQueryCount;
}
/**
* <p>
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
* </p>
* <p>
* The default value for this setting is {@code 60}.
* </p>
*
* @since 5.1.0
*/
public void setMaximumDeleteConflictQueryCount(Integer theMaximumDeleteConflictQueryCount) {
myMaximumDeleteConflictQueryCount = theMaximumDeleteConflictQueryCount;
}
/**
* <p>
* This determines whether $binary-access-write operations should first load the InputStream into memory before persisting the
* contents to the database. This needs to be enabled for MS SQL Server as this DB requires that the blob size be known
* in advance.
* </p>
* <p>
* Note that this setting should be enabled with caution as it can lead to significant demands on memory.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*
* @since 5.1.0
* @deprecated In 5.2.0 this setting no longer does anything
*/
@Deprecated
public void setPreloadBlobFromInputStream(Boolean thePreloadBlobFromInputStream) {
// ignore
}
}

View File

@ -183,6 +183,13 @@
<artifactId>javassist</artifactId>
</dependency>
<!-- SQL Builder -->
<dependency>
<groupId>com.healthmarketscience.sqlbuilder</groupId>
<artifactId>sqlbuilder</artifactId>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
@ -19,7 +20,7 @@ import ca.uhn.fhir.jpa.bulk.svc.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.dao.HistoryBuilder;
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
@ -51,6 +52,27 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SearchParamPresentPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SourcePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TagPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.UriPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.sql.GeneratedSql;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryExecutor;
import ca.uhn.fhir.jpa.search.builder.sql.SqlObjectFactory;
import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl;
import ca.uhn.fhir.jpa.search.cache.DatabaseSearchResultCacheSvcImpl;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
@ -414,33 +436,19 @@ public abstract class BaseConfig {
return new PersistedJpaBundleProviderFactory();
}
@Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER)
@Scope("prototype")
public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, String theUuid) {
return new PersistedJpaBundleProvider(theRequest, theUuid);
}
@Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER_BY_SEARCH)
@Scope("prototype")
public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, Search theSearch) {
return new PersistedJpaBundleProvider(theRequest, theSearch);
}
@Bean(name = PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER)
@Scope("prototype")
public PersistedJpaSearchFirstPageBundleProvider persistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theSearchTask, ISearchBuilder theSearchBuilder) {
return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest);
}
@Bean
public SearchBuilderFactory searchBuilderFactory() {
return new SearchBuilderFactory();
}
@Bean(name = SEARCH_BUILDER)
@Scope("prototype")
public SearchBuilder persistedJpaSearchFirstPageBundleProvider(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
return new SearchBuilder(theDao, theResourceName, theResourceType);
@Bean
public SqlObjectFactory sqlBuilderFactory() {
return new SqlObjectFactory();
}
@Bean
public HibernateDialectProvider hibernateDialectProvider() {
return new HibernateDialectProvider();
}
@Bean
@ -448,9 +456,136 @@ public abstract class BaseConfig {
return new HistoryBuilderFactory();
}
/* **************************************************************** *
* Prototype Beans Below *
* **************************************************************** */
@Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER)
@Scope("prototype")
public PersistedJpaBundleProvider newPersistedJpaBundleProvider(RequestDetails theRequest, String theUuid) {
return new PersistedJpaBundleProvider(theRequest, theUuid);
}
@Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER_BY_SEARCH)
@Scope("prototype")
public PersistedJpaBundleProvider newPersistedJpaBundleProvider(RequestDetails theRequest, Search theSearch) {
return new PersistedJpaBundleProvider(theRequest, theSearch);
}
@Bean(name = PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER)
@Scope("prototype")
public PersistedJpaSearchFirstPageBundleProvider newPersistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theSearchTask, ISearchBuilder theSearchBuilder) {
return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest);
}
@Bean
@Scope("prototype")
public CompositeUniqueSearchParameterPredicateBuilder newCompositeUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return new CompositeUniqueSearchParameterPredicateBuilder(theSearchSqlBuilder);
}
@Bean
@Scope("prototype")
public CoordsPredicateBuilder newCoordsPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new CoordsPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public DatePredicateBuilder newDatePredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new DatePredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public ForcedIdPredicateBuilder newForcedIdPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new ForcedIdPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public NumberPredicateBuilder newNumberPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new NumberPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public QuantityPredicateBuilder newQuantityPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new QuantityPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public ResourceLinkPredicateBuilder newResourceLinkPredicateBuilder(QueryStack theQueryStack, SearchQueryBuilder theSearchBuilder, boolean theReversed) {
return new ResourceLinkPredicateBuilder(theQueryStack, theSearchBuilder, theReversed);
}
@Bean
@Scope("prototype")
public ResourceTablePredicateBuilder newResourceTablePredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new ResourceTablePredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public TagPredicateBuilder newTagPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new TagPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public ResourceIdPredicateBuilder newResourceIdPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new ResourceIdPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public SearchParamPresentPredicateBuilder newSearchParamPresentPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new SearchParamPresentPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public StringPredicateBuilder newStringPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new StringPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public TokenPredicateBuilder newTokenPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new TokenPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public SourcePredicateBuilder newSourcePredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new SourcePredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public UriPredicateBuilder newUriPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new UriPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public SearchQueryExecutor newSearchQueryExecutor(GeneratedSql theGeneratedSql, Integer theMaxResultsToFetch) {
return new SearchQueryExecutor(theGeneratedSql, theMaxResultsToFetch);
}
@Bean(name = SEARCH_BUILDER)
@Scope("prototype")
public ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType, DaoConfig theDaoConfig) {
if (theDaoConfig.isUseLegacySearchBuilder()) {
return new LegacySearchBuilder(theDao, theResourceName, theResourceType);
}
return new SearchBuilder(theDao, theResourceName, theResourceType);
}
@Bean(name = HISTORY_BUILDER)
@Scope("prototype")
public HistoryBuilder persistedJpaSearchFirstPageBundleProvider(@Nullable String theResourceType, @Nullable Long theResourceId, @Nullable Date theRangeStartInclusive, @Nullable Date theRangeEndInclusive) {
public HistoryBuilder newPersistedJpaSearchFirstPageBundleProvider(@Nullable String theResourceType, @Nullable Long theResourceId, @Nullable Date theRangeStartInclusive, @Nullable Date theRangeEndInclusive) {
return new HistoryBuilder(theResourceType, theResourceId, theRangeStartInclusive, theRangeEndInclusive);
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.jpa.config;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.util.ReflectionUtil;
import org.apache.commons.lang3.Validate;
import org.hibernate.dialect.Dialect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
public class HibernateDialectProvider {
@Autowired
private LocalContainerEntityManagerFactoryBean myEntityManagerFactory;
private Dialect myDialect;
public Dialect getDialect() {
Dialect dialect = myDialect;
if (dialect == null) {
String dialectClass = (String) myEntityManagerFactory.getJpaPropertyMap().get("hibernate.dialect");
dialect = ReflectionUtil.newInstanceOrReturnNull(dialectClass, Dialect.class);
Validate.notNull(dialect, "Unable to create instance of class: %s", dialectClass);
myDialect = dialect;
}
return dialect;
}
}

View File

@ -65,11 +65,11 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_ERROR;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_INFO;
@ -248,7 +248,8 @@ public abstract class BaseStorageDao {
QualifierDetails qualifiedParamName = QualifierDetails.extractQualifiersFromParameterName(nextParamName);
RuntimeSearchParam param = searchParams.get(qualifiedParamName.getParamName());
if (param == null) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), new TreeSet<>(searchParams.keySet()));
Collection<String> validNames = mySearchParamRegistry.getValidSearchParameterNamesIncludingMeta(getResourceName());
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), getResourceName(), validNames);
throw new InvalidRequestException(msg);
}

View File

@ -49,7 +49,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import static ca.uhn.fhir.jpa.dao.SearchBuilder.toPredicateArray;
import static ca.uhn.fhir.jpa.dao.LegacySearchBuilder.toPredicateArray;
/**
* The HistoryBuilder is responsible for building history queries

View File

@ -116,6 +116,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static ca.uhn.fhir.jpa.search.builder.SearchBuilder.getMaximumPageSize;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -124,19 +125,10 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* The SearchBuilder is responsible for actually forming the SQL query that handles
* searches for resources
*/
public class SearchBuilder implements ISearchBuilder {
/**
* See loadResourcesByPid
* for an explanation of why we use the constant 800
*/
// NB: keep public
public static final int MAXIMUM_PAGE_SIZE = 800;
public static final int MAXIMUM_PAGE_SIZE_FOR_TESTING = 50;
public static boolean myUseMaxPageSize50ForTest = false;
public class LegacySearchBuilder implements ISearchBuilder {
private static final List<ResourcePersistentId> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class);
private static final Logger ourLog = LoggerFactory.getLogger(LegacySearchBuilder.class);
private static final ResourcePersistentId NO_MORE = new ResourcePersistentId(-1L);
private final String myResourceName;
private final Class<? extends IBaseResource> myResourceType;
@ -179,24 +171,12 @@ public class SearchBuilder implements ISearchBuilder {
/**
* Constructor
*/
public SearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
public LegacySearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
myCallingDao = theDao;
myResourceName = theResourceName;
myResourceType = theResourceType;
}
public static int getMaximumPageSize() {
if (myUseMaxPageSize50ForTest) {
return MAXIMUM_PAGE_SIZE_FOR_TESTING;
} else {
return MAXIMUM_PAGE_SIZE;
}
}
public static void setMaxPageSize50ForTest(boolean theIsTest) {
myUseMaxPageSize50ForTest = theIsTest;
}
@Override
public void setMaxResultsToFetch(Integer theMaxResultsToFetch) {
myMaxResultsToFetch = theMaxResultsToFetch;
@ -1408,7 +1388,7 @@ public class SearchBuilder implements ISearchBuilder {
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from);
lastUpdatedPredicates.add(from.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(thePids)));
cq.where(SearchBuilder.toPredicateArray(lastUpdatedPredicates));
cq.where(LegacySearchBuilder.toPredicateArray(lastUpdatedPredicates));
TypedQuery<Long> query = theEntityManager.createQuery(cq);
return ResourcePersistentId.fromLongList(query.getResultList());

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.config.BaseConfig;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -30,9 +31,11 @@ public class SearchBuilderFactory {
@Autowired
private ApplicationContext myApplicationContext;
@Autowired
private DaoConfig myDaoConfig;
public ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
return (ISearchBuilder) myApplicationContext.getBean(BaseConfig.SEARCH_BUILDER, theDao, theResourceName, theResourceType);
return (ISearchBuilder) myApplicationContext.getBean(BaseConfig.SEARCH_BUILDER, theDao, theResourceName, theResourceType, myDaoConfig);
}
}

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.querystack.QueryStack;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
@ -63,7 +63,7 @@ abstract class BasePredicateBuilder {
@Autowired
private PartitionSettings myPartitionSettings;
BasePredicateBuilder(SearchBuilder theSearchBuilder) {
BasePredicateBuilder(LegacySearchBuilder theSearchBuilder) {
myCriteriaBuilder = theSearchBuilder.getBuilder();
myQueryStack = theSearchBuilder.getQueryStack();
myResourceType = theSearchBuilder.getResourceType();
@ -172,7 +172,7 @@ abstract class BasePredicateBuilder {
case ENDS_BEFORE:
case STARTS_AFTER:
default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg);
}

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
@ -52,7 +52,7 @@ public class PredicateBuilder {
private final PredicateBuilderToken myPredicateBuilderToken;
private final PredicateBuilderUri myPredicateBuilderUri;
public PredicateBuilder(SearchBuilder theSearchBuilder, PredicateBuilderFactory thePredicateBuilderFactory) {
public PredicateBuilder(LegacySearchBuilder theSearchBuilder, PredicateBuilderFactory thePredicateBuilderFactory) {
myPredicateBuilderCoords = thePredicateBuilderFactory.newPredicateBuilderCoords(theSearchBuilder);
myPredicateBuilderDate = thePredicateBuilderFactory.newPredicateBuilderDate(theSearchBuilder);
myPredicateBuilderNumber = thePredicateBuilderFactory.newPredicateBuilderNumber(theSearchBuilder);

View File

@ -22,8 +22,9 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.SearchBox;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -50,7 +51,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class PredicateBuilderCoords extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderCoords.class);
PredicateBuilderCoords(SearchBuilder theSearchBuilder) {
PredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
@ -50,7 +50,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderDate.class);
PredicateBuilderDate(SearchBuilder theSearchBuilder) {
PredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -20,30 +20,30 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L%
*/
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Service;
@Service
public abstract class PredicateBuilderFactory {
@Lookup
public abstract PredicateBuilderCoords newPredicateBuilderCoords(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderCoords newPredicateBuilderCoords(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderDate newPredicateBuilderDate(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderDate newPredicateBuilderDate(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderNumber newPredicateBuilderNumber(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderNumber newPredicateBuilderNumber(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderQuantity newPredicateBuilderQuantity(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderQuantity newPredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderReference newPredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder);
public abstract PredicateBuilderReference newPredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder);
@Lookup
public abstract PredicateBuilderResourceId newPredicateBuilderResourceId(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderResourceId newPredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderString newPredicateBuilderString(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderString newPredicateBuilderString(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderTag newPredicateBuilderTag(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderTag newPredicateBuilderTag(LegacySearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderToken newPredicateBuilderToken(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder);
public abstract PredicateBuilderToken newPredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder);
@Lookup
public abstract PredicateBuilderUri newPredicateBuilderUri(SearchBuilder theSearchBuilder);
public abstract PredicateBuilderUri newPredicateBuilderUri(LegacySearchBuilder theSearchBuilder);
}

View File

@ -22,8 +22,9 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
@ -46,7 +47,7 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderNumber.class);
PredicateBuilderNumber(SearchBuilder theSearchBuilder) {
PredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -22,9 +22,10 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
@ -47,7 +48,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
@Scope("prototype")
class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicateBuilder {
PredicateBuilderQuantity(SearchBuilder theSearchBuilder) {
PredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -35,7 +35,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
@ -109,6 +109,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
@Component
@Scope("prototype")
public
class PredicateBuilderReference extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class);
private final PredicateBuilder myPredicateBuilder;
@ -125,7 +126,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
PredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
super(theSearchBuilder);
myPredicateBuilder = thePredicateBuilder;
}
@ -682,7 +683,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
} else {
String validNames = new TreeSet<>(mySearchParamRegistry.getActiveSearchParams(theResourceName).keySet()).toString();
Collection<String> validNames = mySearchParamRegistry.getValidSearchParameterNamesIncludingMeta(theResourceName);
String msg = myContext.getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "invalidSearchParameter", theParamName, theResourceName, validNames);
throw new InvalidRequestException(msg);
}
@ -881,7 +882,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
assert theOperation == SearchFilterParser.CompareOperation.eq;
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled");
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "sourceParamDisabled");
throw new InvalidRequestException(msg);
}

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -51,7 +51,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
@Autowired
IdHelperService myIdHelperService;
PredicateBuilderResourceId(SearchBuilder theSearchBuilder) {
PredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -22,8 +22,9 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
@ -43,7 +44,7 @@ import java.util.List;
@Component
@Scope("prototype")
class PredicateBuilderString extends BasePredicateBuilder implements IPredicateBuilder {
PredicateBuilderString(SearchBuilder theSearchBuilder) {
PredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
@ -53,7 +53,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
class PredicateBuilderTag extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderTag.class);
PredicateBuilderTag(SearchBuilder theSearchBuilder) {
PredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
@ -65,6 +65,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Component
@Scope("prototype")
public
class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBuilder {
private final PredicateBuilder myPredicateBuilder;
@Autowired
@ -72,7 +73,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
@Autowired
private ModelConfig myModelConfig;
PredicateBuilderToken(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
PredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
super(theSearchBuilder);
myPredicateBuilder = thePredicateBuilder;
}
@ -105,13 +106,13 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
String msg;
if (myModelConfig.isSuppressStringIndexingInTokens()) {
msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForServer");
}else{
} else {
msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForSearchParam");
}
throw new MethodNotAllowedException(msg);
}
myPredicateBuilder.addPredicateString(theResourceName, theSearchParam, theList, theRequestPartitionId);
myPredicateBuilder.addPredicateString(theResourceName, theSearchParam, theList, theOperation, theRequestPartitionId);
break;
}
}
@ -289,11 +290,11 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
String systemDesc = defaultIfBlank(theSystem, "(missing)");
String codeDesc = defaultIfBlank(theCode, "(missing)");
if (isBlank(theCode)) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(msg);
}
if (isBlank(theSystem)) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(msg);
}
}

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
@ -48,7 +48,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
@Autowired
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
PredicateBuilderUri(SearchBuilder theSearchBuilder) {
PredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}

View File

@ -386,7 +386,10 @@ public class SearchFilterParser {
ss,
sb,
in,
re
re,
ap,
sa,
eb
}
public enum FilterLogicalOperation {
@ -508,7 +511,7 @@ public class SearchFilterParser {
private String FValue;
private FilterValueType FValueType;
FilterParameterPath getParamPath() {
public FilterParameterPath getParamPath() {
return FParamPath;
}
@ -566,7 +569,7 @@ public class SearchFilterParser {
private Filter FFilter2;
Filter getFilter1() {
public Filter getFilter1() {
return FFilter1;
}
@ -586,7 +589,7 @@ public class SearchFilterParser {
this.FOperation = FOperation;
}
Filter getFilter2() {
public Filter getFilter2() {
return FFilter2;
}

View File

@ -166,7 +166,7 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
try {
theContext.newFluentPath().evaluate(temporaryInstance, nextPath, IBase.class);
} catch (Exception e) {
String msg = theContext.getLocalizer().getMessage(FhirResourceDaoSearchParameterR4.class, "invalidSearchParamExpression", nextPath, e.getMessage());
String msg = theContext.getLocalizer().getMessageSanitized(FhirResourceDaoSearchParameterR4.class, "invalidSearchParamExpression", nextPath, e.getMessage());
throw new UnprocessableEntityException(msg, e);
}
}

View File

@ -91,7 +91,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.SearchBuilder.toPredicateArray;
import static ca.uhn.fhir.jpa.dao.LegacySearchBuilder.toPredicateArray;
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

View File

@ -90,6 +90,7 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
@ -535,8 +536,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
* individually for pages as we return them to clients
*/
final Set<ResourcePersistentId> includedPids = new HashSet<>();
if (theParams.getEverythingMode() == null) {
includedPids.addAll(theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)", theRequestDetails));
}
includedPids.addAll(theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)", theRequestDetails));
includedPids.addAll(theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)", theRequestDetails));
List<ResourcePersistentId> includedPidsList = new ArrayList<>(includedPids);
List<IBaseResource> resources = new ArrayList<>();
@ -945,6 +949,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
// Create an initial search in the DB and give it an ID
saveSearch();
assert !TransactionSynchronizationManager.isActualTransactionActive();
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

View File

@ -0,0 +1,99 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.NotCondition;
import com.healthmarketscience.sqlbuilder.UnaryCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nullable;
import java.util.List;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toAndPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toEqualToOrInPredicate;
public abstract class BaseJoiningPredicateBuilder extends BasePredicateBuilder {
private final DbTable myTable;
private final DbColumn myColumnPartitionId;
BaseJoiningPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder, DbTable theTable) {
super(theSearchSqlBuilder);
myTable = theTable;
myColumnPartitionId = theTable.addColumn("PARTITION_ID");
}
public DbTable getTable() {
return myTable;
}
public abstract DbColumn getResourceIdColumn();
DbColumn getPartitionIdColumn() {
return myColumnPartitionId;
}
public Condition combineWithRequestPartitionIdPredicate(RequestPartitionId theRequestPartitionId, Condition theCondition) {
Condition partitionIdPredicate = createPartitionIdPredicate(theRequestPartitionId);
if (partitionIdPredicate == null) {
return theCondition;
}
return toAndPredicate(partitionIdPredicate, theCondition);
}
@Nullable
public Condition createPartitionIdPredicate(RequestPartitionId theRequestPartitionId) {
if (theRequestPartitionId != null && !theRequestPartitionId.isAllPartitions()) {
Condition condition;
Integer partitionId = theRequestPartitionId.getPartitionId();
if (partitionId != null) {
Object placeholder = generatePlaceholder(partitionId);
condition = BinaryCondition.equalTo(getPartitionIdColumn(), placeholder);
} else {
condition = UnaryCondition.isNull(getPartitionIdColumn());
}
return condition;
} else {
return null;
}
}
public Condition createPredicateResourceIds(boolean theInverse, List<Long> theResourceIds) {
Validate.notNull(theResourceIds, "theResourceIds must not be null");
// Handle the _id parameter by adding it to the tail
Condition inResourceIds = toEqualToOrInPredicate(getResourceIdColumn(), generatePlaceholders(theResourceIds));
if (theInverse) {
inResourceIds = new NotCondition(inResourceIds);
}
return inResourceIds;
}
}

View File

@ -0,0 +1,93 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.List;
public class BasePredicateBuilder {
private final SearchQueryBuilder mySearchSqlBuilder;
public BasePredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
mySearchSqlBuilder = theSearchSqlBuilder;
}
PartitionSettings getPartitionSettings() {
return mySearchSqlBuilder.getPartitionSettings();
}
RequestPartitionId getRequestPartitionId() {
return mySearchSqlBuilder.getRequestPartitionId();
}
String getResourceType() {
return mySearchSqlBuilder.getResourceType();
}
ModelConfig getModelConfig() {
return mySearchSqlBuilder.getModelConfig();
}
@Nonnull
String generatePlaceholder(Object theInput) {
return mySearchSqlBuilder.generatePlaceholder(theInput);
}
@Nonnull
List<String> generatePlaceholders(Collection<?> theValues) {
return mySearchSqlBuilder.generatePlaceholders(theValues);
}
protected FhirContext getFhirContext() {
return mySearchSqlBuilder.getFhirContext();
}
protected void setMatchNothing() {
mySearchSqlBuilder.setMatchNothing();
}
protected BinaryCondition createConditionForValueWithComparator(ParamPrefixEnum theComparator, DbColumn theColumn, Object theValue) {
return mySearchSqlBuilder.createConditionForValueWithComparator(theComparator, theColumn, theValue);
}
protected BaseJoiningPredicateBuilder getOrCreateQueryRootTable() {
return mySearchSqlBuilder.getOrCreateFirstPredicateBuilder();
}
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn theFromColumn, DbColumn theToColumn) {
mySearchSqlBuilder.addJoin(theFromTable, theToTable, theFromColumn, theToColumn);
}
}

View File

@ -0,0 +1,103 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toAndPredicate;
public abstract class BaseSearchParamPredicateBuilder extends BaseJoiningPredicateBuilder {
private final DbColumn myColumnMissing;
private final DbColumn myColumnResType;
private final DbColumn myColumnParamName;
private final DbColumn myColumnResId;
private final DbColumn myColumnHashIdentity;
public BaseSearchParamPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder, DbTable theTable) {
super(theSearchSqlBuilder, theTable);
myColumnResId = getTable().addColumn("RES_ID");
myColumnMissing = theTable.addColumn("SP_MISSING");
myColumnResType = theTable.addColumn("RES_TYPE");
myColumnParamName = theTable.addColumn("SP_NAME");
myColumnHashIdentity = theTable.addColumn("HASH_IDENTITY");
}
public DbColumn getColumnHashIdentity() {
return myColumnHashIdentity;
}
public DbColumn getResourceTypeColumn() {
return myColumnResType;
}
public DbColumn getColumnParamName() {
return myColumnParamName;
}
public DbColumn getMissingColumn() {
return myColumnMissing;
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResId;
}
public Condition combineWithHashIdentityPredicate(String theResourceName, String theParamName, Condition thePredicate) {
List<Condition> andPredicates = new ArrayList<>();
Condition hashIdentityPredicate = createHashIdentityPredicate(theResourceName, theParamName);
andPredicates.add(hashIdentityPredicate);
andPredicates.add(thePredicate);
return toAndPredicate(andPredicates);
}
@Nonnull
public Condition createHashIdentityPredicate(String theResourceType, String theParamName) {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), getRequestPartitionId(), theResourceType, theParamName);
String hashIdentityVal = generatePlaceholder(hashIdentity);
return BinaryCondition.equalTo(myColumnHashIdentity, hashIdentityVal);
}
public Condition createPredicateParamMissingForNonReference(String theResourceName, String theParamName, Boolean theMissing, RequestPartitionId theRequestPartitionId) {
ComboCondition condition = ComboCondition.and(
BinaryCondition.equalTo(getResourceTypeColumn(), generatePlaceholder(theResourceName)),
BinaryCondition.equalTo(getColumnParamName(), generatePlaceholder(theParamName)),
BinaryCondition.equalTo(getMissingColumn(), generatePlaceholder(theMissing))
);
return combineWithRequestPartitionIdPredicate(theRequestPartitionId, condition);
}
}

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
public class CompositeUniqueSearchParameterPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnString;
/**
* Constructor
*/
public CompositeUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_IDX_CMP_STRING_UNIQ"));
myColumnString = getTable().addColumn("IDX_STRING");
}
public Condition createPredicateIndexString(RequestPartitionId theRequestPartitionId, String theIndexString) {
BinaryCondition predicate = BinaryCondition.equalTo(myColumnString, generatePlaceholder(theIndexString));
return combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
}
}

View File

@ -0,0 +1,154 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.SearchBox;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.SpecialParam;
import ca.uhn.fhir.rest.param.TokenParam;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnLatitude;
private final DbColumn myColumnLongitude;
/**
* Constructor
*/
public CoordsPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_COORDS"));
myColumnLatitude = getTable().addColumn("SP_LATITUDE");
myColumnLongitude = getTable().addColumn("SP_LONGITUDE");
}
public Condition createPredicateCoords(SearchParameterMap theParams,
IQueryParameterType theParam,
String theResourceName,
RuntimeSearchParam theSearchParam,
CoordsPredicateBuilder theFrom,
RequestPartitionId theRequestPartitionId) {
String latitudeValue;
String longitudeValue;
double distanceKm = 0.0;
if (theParam instanceof TokenParam) { // DSTU3
TokenParam param = (TokenParam) theParam;
String value = param.getValue();
String[] parts = value.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Required format is 'latitude:longitude'");
}
latitudeValue = parts[0];
longitudeValue = parts[1];
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
}
QuantityParam distanceParam = theParams.getNearDistanceParam();
if (distanceParam != null) {
distanceKm = distanceParam.getValue().doubleValue();
}
} else if (theParam instanceof SpecialParam) { // R4
SpecialParam param = (SpecialParam) theParam;
String value = param.getValue();
String[] parts = value.split("\\|");
if (parts.length < 2 || parts.length > 4) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'");
}
latitudeValue = parts[0];
longitudeValue = parts[1];
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
}
if (parts.length >= 3) {
String distanceString = parts[2];
if (!isBlank(distanceString)) {
distanceKm = Double.parseDouble(distanceString);
}
}
} else {
throw new IllegalArgumentException("Invalid position type: " + theParam.getClass());
}
Condition latitudePredicate;
Condition longitudePredicate;
if (distanceKm == 0.0) {
latitudePredicate = theFrom.createPredicateLatitudeExact(latitudeValue);
longitudePredicate = theFrom.createPredicateLongitudeExact(longitudeValue);
} else if (distanceKm < 0.0) {
throw new IllegalArgumentException("Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be >= 0.0");
} else if (distanceKm > CoordCalculator.MAX_SUPPORTED_DISTANCE_KM) {
throw new IllegalArgumentException("Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be <= " + CoordCalculator.MAX_SUPPORTED_DISTANCE_KM);
} else {
double latitudeDegrees = Double.parseDouble(latitudeValue);
double longitudeDegrees = Double.parseDouble(longitudeValue);
SearchBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
latitudePredicate = theFrom.createLatitudePredicateFromBox(box);
longitudePredicate = theFrom.createLongitudePredicateFromBox(box);
}
ComboCondition singleCode = ComboCondition.and(latitudePredicate, longitudePredicate);
return combineWithHashIdentityPredicate(theResourceName, theSearchParam.getName(), singleCode);
}
public Condition createPredicateLatitudeExact(String theLatitudeValue) {
return BinaryCondition.equalTo(myColumnLatitude, generatePlaceholder(theLatitudeValue));
}
public Condition createPredicateLongitudeExact(String theLongitudeValue) {
return BinaryCondition.equalTo(myColumnLongitude, generatePlaceholder(theLongitudeValue));
}
public Condition createLatitudePredicateFromBox(SearchBox theBox) {
return ComboCondition.and(
BinaryCondition.greaterThanOrEq(myColumnLatitude, generatePlaceholder(theBox.getSouthWest().getLatitude())),
BinaryCondition.lessThanOrEq(myColumnLatitude, generatePlaceholder(theBox.getNorthEast().getLatitude()))
);
}
public Condition createLongitudePredicateFromBox(SearchBox theBox) {
if (theBox.crossesAntiMeridian()) {
return ComboCondition.or(
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getNorthEast().getLongitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getSouthWest().getLongitude()))
);
}
return ComboCondition.and(
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getSouthWest().getLongitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.getNorthEast().getLongitude()))
);
}
}

View File

@ -0,0 +1,247 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(DatePredicateBuilder.class);
private final DbColumn myColumnValueHigh;
private final DbColumn myColumnValueLow;
private final DbColumn myColumnValueLowDateOrdinal;
private final DbColumn myColumnValueHighDateOrdinal;
@Autowired
private DaoConfig myDaoConfig;
/**
* Constructor
*/
public DatePredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_DATE"));
myColumnValueLow = getTable().addColumn("SP_VALUE_LOW");
myColumnValueHigh = getTable().addColumn("SP_VALUE_HIGH");
myColumnValueLowDateOrdinal = getTable().addColumn("SP_VALUE_LOW_DATE_ORDINAL");
myColumnValueHighDateOrdinal = getTable().addColumn("SP_VALUE_HIGH_DATE_ORDINAL");
}
public Condition createPredicateDateWithoutIdentityPredicate(IQueryParameterType theParam,
String theResourceName,
String theParamName,
DatePredicateBuilder theFrom,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
Condition p;
if (theParam instanceof DateParam) {
DateParam date = (DateParam) theParam;
if (!date.isEmpty()) {
DateRangeParam range = new DateRangeParam(date);
p = createPredicateDateFromRange(theFrom, range, theOperation);
} else {
// TODO: handle missing date param?
p = null;
}
} else if (theParam instanceof DateRangeParam) {
DateRangeParam range = (DateRangeParam) theParam;
p = createPredicateDateFromRange(
theFrom,
range,
theOperation);
} else {
throw new IllegalArgumentException("Invalid token type: " + theParam.getClass());
}
return p;
}
private Condition createPredicateDateFromRange(DatePredicateBuilder theFrom,
DateRangeParam theRange,
SearchFilterParser.CompareOperation theOperation) {
Date lowerBoundInstant = theRange.getLowerBoundAsInstant();
Date upperBoundInstant = theRange.getUpperBoundAsInstant();
DateParam lowerBound = theRange.getLowerBound();
DateParam upperBound = theRange.getUpperBound();
Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
Comparable genericLowerBound;
Comparable genericUpperBound;
/**
* If all present search parameters are of DAY precision, and {@link ca.uhn.fhir.jpa.model.entity.ModelConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
* then we attempt to use the ordinal field for date comparisons instead of the date field.
*/
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getModelConfig().getUseOrdinalDatesForDayPrecisionSearches();
Condition lt;
Condition gt = null;
Condition lb = null;
Condition ub = null;
DatePredicateBuilder.ColumnEnum lowValueField;
DatePredicateBuilder.ColumnEnum highValueField;
if (isOrdinalComparison) {
lowValueField = DatePredicateBuilder.ColumnEnum.LOW_DATE_ORDINAL;
highValueField = DatePredicateBuilder.ColumnEnum.HIGH_DATE_ORDINAL;
genericLowerBound = lowerBoundAsOrdinal;
genericUpperBound = upperBoundAsOrdinal;
} else {
lowValueField = DatePredicateBuilder.ColumnEnum.LOW;
highValueField = DatePredicateBuilder.ColumnEnum.HIGH;
genericLowerBound = lowerBoundInstant;
genericUpperBound = upperBoundInstant;
}
if (theOperation == SearchFilterParser.CompareOperation.lt) {
if (lowerBoundInstant == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare theOperation");
}
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
lb = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN, genericLowerBound);
} else if (theOperation == SearchFilterParser.CompareOperation.le) {
if (upperBoundInstant == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare theOperation");
}
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
lb = theFrom.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
} else if (theOperation == SearchFilterParser.CompareOperation.gt) {
if (upperBoundInstant == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare theOperation");
}
lb = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN, genericUpperBound);
} else if (theOperation == SearchFilterParser.CompareOperation.ge) {
if (lowerBoundInstant == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare theOperation");
}
lb = theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
} else if (theOperation == SearchFilterParser.CompareOperation.ne) {
if ((lowerBoundInstant == null) ||
(upperBoundInstant == null)) {
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare theOperation");
}
lt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN, genericLowerBound);
gt = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN, genericUpperBound);
lb = ComboCondition.or(lt, gt);
} else if ((theOperation == SearchFilterParser.CompareOperation.eq) || (theOperation == null)) {
if (lowerBoundInstant != null) {
gt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
lt = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
if (lowerBound.getPrefix() == ParamPrefixEnum.STARTS_AFTER || lowerBound.getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt;
} else {
lb = ComboCondition.or(gt, lt);
}
}
if (upperBoundInstant != null) {
gt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
lt = theFrom.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt;
} else {
ub = ComboCondition.or(gt, lt);
}
}
} else {
throw new InvalidRequestException(String.format("Unsupported operator specified, operator=%s",
theOperation.name()));
}
if (isOrdinalComparison) {
ourLog.trace("Ordinal date range is {} - {} ", lowerBoundAsOrdinal, upperBoundAsOrdinal);
} else {
ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
}
if (lb != null && ub != null) {
return (ComboCondition.and(lb, ub));
} else if (lb != null) {
return (lb);
} else {
return (ub);
}
}
public DbColumn getColumnValueLow() {
return myColumnValueLow;
}
private boolean isNullOrDayPrecision(DateParam theDateParam) {
return theDateParam == null || theDateParam.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal();
}
private Condition createPredicate(ColumnEnum theColumn, ParamPrefixEnum theComparator, Object theValue) {
DbColumn column;
switch (theColumn) {
case LOW:
column = myColumnValueLow;
break;
case LOW_DATE_ORDINAL:
column = myColumnValueLowDateOrdinal;
break;
case HIGH:
column = myColumnValueHigh;
break;
case HIGH_DATE_ORDINAL:
column = myColumnValueHighDateOrdinal;
break;
default:
throw new IllegalArgumentException();
}
return createConditionForValueWithComparator(theComparator, column, theValue);
}
public enum ColumnEnum {
LOW,
LOW_DATE_ORDINAL,
HIGH,
HIGH_DATE_ORDINAL
}
}

View File

@ -0,0 +1,56 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ForcedIdPredicateBuilder extends BaseJoiningPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(ForcedIdPredicateBuilder.class);
private final DbColumn myColumnResourceId;
private final DbColumn myColumnForcedId;
/**
* Constructor
*/
public ForcedIdPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_FORCED_ID"));
myColumnResourceId = getTable().addColumn("RESOURCE_PID");
myColumnForcedId = getTable().addColumn("FORCED_ID");
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResourceId;
}
public DbColumn getColumnForcedId() {
return myColumnForcedId;
}
}

View File

@ -0,0 +1,113 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.dao.predicate.SearchFuzzUtil;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.math.BigDecimal;
import java.math.MathContext;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public class NumberPredicateBuilder extends BaseSearchParamPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(NumberPredicateBuilder.class);
private final DbColumn myColumnValue;
@Autowired
private FhirContext myFhirContext;
/**
* Constructor
*/
public NumberPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_NUMBER"));
myColumnValue = getTable().addColumn("SP_VALUE");
}
public Condition createPredicateNumeric(String theResourceName, String theParamName, SearchFilterParser.CompareOperation theOperation, BigDecimal theValue, RequestPartitionId theRequestPartitionId, IQueryParameterType theActualParam) {
Condition numericPredicate = createPredicateNumeric(this, theOperation, theValue, myColumnValue, "invalidNumberPrefix", myFhirContext, theActualParam);
return combineWithHashIdentityPredicate(theResourceName, theParamName, numericPredicate);
}
public DbColumn getColumnValue() {
return myColumnValue;
}
static Condition createPredicateNumeric(BaseSearchParamPredicateBuilder theIndexTable, SearchFilterParser.CompareOperation theOperation, BigDecimal theValue, DbColumn theColumn, String theInvalidValueKey, FhirContext theFhirContext, IQueryParameterType theActualParam) {
Condition num;
// Per discussions with Grahame Grieve and James Agnew on 11/13/19, modified logic for EQUAL and NOT_EQUAL operators below so as to
// use exact value matching. The "fuzz amount" matching is still used with the APPROXIMATE operator.
SearchFilterParser.CompareOperation operation = defaultIfNull(theOperation, SearchFilterParser.CompareOperation.eq);
switch (operation) {
case gt:
num = BinaryCondition.greaterThan(theColumn, theIndexTable.generatePlaceholder(theValue));
break;
case ge:
num = BinaryCondition.greaterThanOrEq(theColumn, theIndexTable.generatePlaceholder(theValue));
break;
case lt:
num = BinaryCondition.lessThan(theColumn, theIndexTable.generatePlaceholder(theValue));
break;
case le:
num = BinaryCondition.lessThanOrEq(theColumn, theIndexTable.generatePlaceholder(theValue));
break;
case eq:
num = BinaryCondition.equalTo(theColumn, theIndexTable.generatePlaceholder(theValue));
break;
case ne:
num = BinaryCondition.notEqualTo(theColumn, theIndexTable.generatePlaceholder(theValue));
break;
case ap:
BigDecimal mul = SearchFuzzUtil.calculateFuzzAmount(ParamPrefixEnum.APPROXIMATE, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Condition lowPred = BinaryCondition.greaterThanOrEq(theColumn, theIndexTable.generatePlaceholder(low));
Condition highPred = BinaryCondition.lessThanOrEq(theColumn, theIndexTable.generatePlaceholder(high));
num = ComboCondition.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high);
break;
default:
String paramValue = theActualParam.getValueAsQueryToken(theFhirContext);
String msg = theIndexTable.getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, theInvalidValueKey, operation, paramValue);
throw new InvalidRequestException(msg);
}
return num;
}
}

View File

@ -0,0 +1,115 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.criteria.CriteriaBuilder;
import java.math.BigDecimal;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class QuantityPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnHashIdentitySystemUnits;
private final DbColumn myColumnHashIdentityUnits;
private final DbColumn myColumnValue;
@Autowired
private FhirContext myFhirContext;
/**
* Constructor
*/
public QuantityPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_QUANTITY"));
myColumnHashIdentitySystemUnits = getTable().addColumn("HASH_IDENTITY_SYS_UNITS");
myColumnHashIdentityUnits = getTable().addColumn("HASH_IDENTITY_AND_UNITS");
myColumnValue = getTable().addColumn("SP_VALUE");
}
public Condition createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, QuantityPredicateBuilder theFrom, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
String systemValue;
String unitsValue;
ParamPrefixEnum cmpValue;
BigDecimal valueValue;
if (theParam instanceof BaseQuantityDt) {
BaseQuantityDt param = (BaseQuantityDt) theParam;
systemValue = param.getSystemElement().getValueAsString();
unitsValue = param.getUnitsElement().getValueAsString();
cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
valueValue = param.getValueElement().getValue();
} else if (theParam instanceof QuantityParam) {
QuantityParam param = (QuantityParam) theParam;
systemValue = param.getSystem();
unitsValue = param.getUnits();
cmpValue = param.getPrefix();
valueValue = param.getValue();
} else {
throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
}
Condition hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = BinaryCondition.equalTo(myColumnHashIdentitySystemUnits, generatePlaceholder(hash));
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, unitsValue);
hashPredicate = BinaryCondition.equalTo(myColumnHashIdentityUnits, generatePlaceholder(hash));
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName);
hashPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hash));
}
SearchFilterParser.CompareOperation operation = theOperation;
if (operation == null && cmpValue != null) {
operation = QueryStack.toOperation(cmpValue);
}
operation = defaultIfNull(operation, SearchFilterParser.CompareOperation.eq);
Condition numericPredicate = NumberPredicateBuilder.createPredicateNumeric(this, operation, valueValue, myColumnValue, "invalidQuantityPrefix", myFhirContext, theParam);
return ComboCondition.and(hashPredicate, numericPredicate);
}
public DbColumn getColumnValue() {
return myColumnValue;
}
}

View File

@ -0,0 +1,135 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(ResourceIdPredicateBuilder.class);
@Autowired
private IdHelperService myIdHelperService;
/**
* Constructor
*/
public ResourceIdPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder);
}
@Nullable
public Condition createPredicateResourceId(@Nullable DbColumn theSourceJoinColumn, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
Set<ResourcePersistentId> allOrPids = null;
SearchFilterParser.CompareOperation defaultOperation = SearchFilterParser.CompareOperation.eq;
for (List<? extends IQueryParameterType> nextValue : theValues) {
Set<ResourcePersistentId> orPids = new HashSet<>();
boolean haveValue = false;
for (IQueryParameterType next : nextValue) {
String value = next.getValueAsQueryToken(getFhirContext());
if (value != null && value.startsWith("|")) {
value = value.substring(1);
}
IdType valueAsId = new IdType(value);
if (isNotBlank(value)) {
haveValue = true;
try {
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, theResourceName, valueAsId.getIdPart());
orPids.add(pid);
} catch (ResourceNotFoundException e) {
// This is not an error in a search, it just results in no matches
ourLog.debug("Resource ID {} was requested but does not exist", valueAsId.getIdPart());
}
}
if (next instanceof TokenParam) {
if (((TokenParam) next).getModifier() == TokenParamModifier.NOT) {
defaultOperation = SearchFilterParser.CompareOperation.ne;
}
}
}
if (haveValue) {
if (allOrPids == null) {
allOrPids = orPids;
} else {
allOrPids.retainAll(orPids);
}
}
}
if (allOrPids != null && allOrPids.isEmpty()) {
setMatchNothing();
} else if (allOrPids != null) {
SearchFilterParser.CompareOperation operation = defaultIfNull(theOperation, defaultOperation);
assert operation == SearchFilterParser.CompareOperation.eq || operation == SearchFilterParser.CompareOperation.ne;
List<Long> resourceIds = ResourcePersistentId.toLongList(allOrPids);
if (theSourceJoinColumn == null) {
BaseJoiningPredicateBuilder queryRootTable = super.getOrCreateQueryRootTable();
switch (operation) {
default:
case eq:
return queryRootTable.createPredicateResourceIds(false, resourceIds);
case ne:
return queryRootTable.createPredicateResourceIds(true, resourceIds);
}
} else {
return QueryStack.toEqualToOrInPredicate(theSourceJoinColumn, generatePlaceholders(resourceIds), operation == SearchFilterParser.CompareOperation.ne);
}
}
return null;
}
}

View File

@ -0,0 +1,614 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.SpecialParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import com.google.common.collect.Lists;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toAndPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toEqualToOrInPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toOrPredicate;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.trim;
public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(ResourceLinkPredicateBuilder.class);
private final DbColumn myColumnSrcType;
private final DbColumn myColumnSrcPath;
private final DbColumn myColumnTargetResourceId;
private final DbColumn myColumnTargetResourceUrl;
private final DbColumn myColumnSrcResourceId;
private final DbColumn myColumnTargetResourceType;
private final QueryStack myQueryStack;
private final boolean myReversed;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private IdHelperService myIdHelperService;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private MatchUrlService myMatchUrlService;
/**
* Constructor
*/
public ResourceLinkPredicateBuilder(QueryStack theQueryStack, SearchQueryBuilder theSearchSqlBuilder, boolean theReversed) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RES_LINK"));
myColumnSrcResourceId = getTable().addColumn("SRC_RESOURCE_ID");
myColumnSrcType = getTable().addColumn("SOURCE_RESOURCE_TYPE");
myColumnSrcPath = getTable().addColumn("SRC_PATH");
myColumnTargetResourceId = getTable().addColumn("TARGET_RESOURCE_ID");
myColumnTargetResourceUrl = getTable().addColumn("TARGET_RESOURCE_URL");
myColumnTargetResourceType = getTable().addColumn("TARGET_RESOURCE_TYPE");
myReversed = theReversed;
myQueryStack = theQueryStack;
}
public DbColumn getColumnSourcePath() {
return myColumnSrcPath;
}
public DbColumn getColumnTargetResourceId() {
return myColumnTargetResourceId;
}
public DbColumn getColumnSrcResourceId() {
return myColumnSrcResourceId;
}
public DbColumn getColumnTargetResourceType() {
return myColumnTargetResourceType;
}
@Override
public DbColumn getResourceIdColumn() {
if (myReversed) {
return myColumnTargetResourceId;
} else {
return myColumnSrcResourceId;
}
}
public Condition createPredicate(RequestDetails theRequest, String theResourceType, String theParamName, List<? extends IQueryParameterType> theReferenceOrParamList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
List<IIdType> targetIds = new ArrayList<>();
List<String> targetQualifiedUrls = new ArrayList<>();
for (int orIdx = 0; orIdx < theReferenceOrParamList.size(); orIdx++) {
IQueryParameterType nextOr = theReferenceOrParamList.get(orIdx);
if (nextOr instanceof ReferenceParam) {
ReferenceParam ref = (ReferenceParam) nextOr;
if (isBlank(ref.getChain())) {
/*
* Handle non-chained search, e.g. Patient?organization=Organization/123
*/
IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null);
if (dt.hasBaseUrl()) {
if (myDaoConfig.getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) {
dt = dt.toUnqualified();
targetIds.add(dt);
} else {
targetQualifiedUrls.add(dt.getValue());
}
} else {
targetIds.add(dt);
}
} else {
/*
* Handle chained search, e.g. Patient?organization.name=Kwik-e-mart
*/
return addPredicateReferenceWithChain(theResourceType, theParamName, theReferenceOrParamList, ref, theRequest, theRequestPartitionId);
}
} else {
throw new IllegalArgumentException("Invalid token type (expecting ReferenceParam): " + nextOr.getClass());
}
}
for (IIdType next : targetIds) {
if (!next.hasResourceType()) {
warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, null);
}
}
List<String> pathsToMatch = createResourceLinkPaths(theResourceType, theParamName);
boolean inverse;
if ((theOperation == null) || (theOperation == SearchFilterParser.CompareOperation.eq)) {
inverse = false;
} else {
inverse = true;
}
List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIdsWithCache(theRequestPartitionId, targetIds);
List<Long> targetPidList = ResourcePersistentId.toLongList(targetPids);
if (targetPidList.isEmpty() && targetQualifiedUrls.isEmpty()) {
setMatchNothing();
return null;
} else {
Condition retVal = createPredicateReference(inverse, pathsToMatch, targetPidList, targetQualifiedUrls);
return combineWithRequestPartitionIdPredicate(getRequestPartitionId(), retVal);
}
}
private Condition createPredicateReference(boolean theInverse, List<String> thePathsToMatch, List<Long> theTargetPidList, List<String> theTargetQualifiedUrls) {
Condition targetPidCondition = null;
if (!theTargetPidList.isEmpty()) {
List<String> placeholders = generatePlaceholders(theTargetPidList);
targetPidCondition = toEqualToOrInPredicate(myColumnTargetResourceId, placeholders, theInverse);
}
Condition targetUrlsCondition = null;
if (!theTargetQualifiedUrls.isEmpty()) {
List<String> placeholders = generatePlaceholders(theTargetQualifiedUrls);
targetUrlsCondition = toEqualToOrInPredicate(myColumnTargetResourceUrl, placeholders, theInverse);
}
Condition joinedCondition;
if (targetPidCondition != null && targetUrlsCondition != null) {
joinedCondition = ComboCondition.or(targetPidCondition, targetUrlsCondition);
} else if (targetPidCondition != null) {
joinedCondition = targetPidCondition;
} else {
joinedCondition = targetUrlsCondition;
}
Condition pathPredicate = createPredicateSourcePaths(thePathsToMatch);
joinedCondition = ComboCondition.and(pathPredicate, joinedCondition);
return joinedCondition;
}
@Nonnull
private Condition createPredicateSourcePaths(List<String> thePathsToMatch) {
return toEqualToOrInPredicate(myColumnSrcPath, generatePlaceholders(thePathsToMatch));
}
public Condition createPredicateSourcePaths(String theResourceName, String theParamName) {
List<String> pathsToMatch = createResourceLinkPaths(theResourceName, theParamName);
return createPredicateSourcePaths(pathsToMatch);
}
private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, @Nullable List<String> theCandidateTargetTypes) {
StringBuilder builder = new StringBuilder();
builder.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ");
builder.append("This is less efficient than using a qualified type. ");
if (theCandidateTargetTypes != null) {
builder.append("[" + theParamName + "] resolves to [" + theCandidateTargetTypes.stream().collect(Collectors.joining(",")) + "].");
builder.append("If you know what you're looking for, try qualifying it using the form ");
builder.append(theCandidateTargetTypes.stream().map(cls -> "[" + cls + ":" + theParamName + "]").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("'");
}
String message = builder
.toString();
StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage(message);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
}
/**
* This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain
* on the device.
*/
private Condition addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, ReferenceParam theReferenceParam, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
/*
* Which resource types can the given chained parameter actually link to? This might be a list
* where the chain is unqualified, as in: Observation?subject.identifier=(...)
* since subject can link to several possible target types.
*
* If the user has qualified the chain, as in: Observation?subject:Patient.identifier=(...)
* this is just a simple 1-entry list.
*/
final List<String> resourceTypes = determineCandidateResourceTypesForChain(theResourceName, theParamName, theReferenceParam);
/*
* Handle chain on _type
*/
if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) {
List<String> pathsToMatch = createResourceLinkPaths(theResourceName, theParamName);
Condition typeCondition = createPredicateSourcePaths(pathsToMatch);
String typeValue = theReferenceParam.getValue();
try {
getFhirContext().getResourceDefinition(typeValue).getImplementingClass();
} catch (DataFormatException e) {
throw newInvalidResourceTypeException(typeValue);
}
if (!resourceTypes.contains(typeValue)) {
throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
}
Condition condition = BinaryCondition.equalTo(myColumnTargetResourceType, generatePlaceholder(theReferenceParam.getValue()));
return toAndPredicate(typeCondition, condition);
}
boolean foundChainMatch = false;
List<String> candidateTargetTypes = new ArrayList<>();
List<Condition> orPredicates = new ArrayList<>();
QueryStack childQueryFactory = myQueryStack.newChildQueryFactoryWithFullBuilderReuse();
for (String nextType : resourceTypes) {
String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
remainingChain = chain.substring(chainDotIndex + 1);
chain = chain.substring(0, chainDotIndex);
}
RuntimeResourceDefinition typeDef = getFhirContext().getResourceDefinition(nextType);
String subResourceName = typeDef.getName();
IDao dao = myDaoRegistry.getResourceDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType);
continue;
}
int qualifierIndex = chain.indexOf(':');
String qualifier = null;
if (qualifierIndex != -1) {
qualifier = chain.substring(qualifierIndex);
chain = chain.substring(0, qualifierIndex);
}
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null;
if (!isMeta) {
param = mySearchParamRegistry.getSearchParamByName(typeDef, chain);
if (param == null) {
ourLog.debug("Type {} doesn't have search param {}", nextType, param);
continue;
}
}
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterType next : theList) {
String nextValue = next.getValueAsQueryToken(getFhirContext());
IQueryParameterType chainValue = mapReferenceChainToRawParamType(remainingChain, param, theParamName, qualifier, nextType, chain, isMeta, nextValue);
if (chainValue == null) {
continue;
}
foundChainMatch = true;
orValues.add(chainValue);
}
if (!foundChainMatch) {
throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain()));
}
candidateTargetTypes.add(nextType);
List<Condition> andPredicates = new ArrayList<>();
List<List<IQueryParameterType>> chainParamValues = Collections.singletonList(orValues);
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId));
orPredicates.add(toAndPredicate(andPredicates));
}
if (candidateTargetTypes.isEmpty()) {
throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain()));
}
if (candidateTargetTypes.size() > 1) {
warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, candidateTargetTypes);
}
Condition multiTypeOrPredicate = toOrPredicate(orPredicates);
List<String> pathsToMatch = createResourceLinkPaths(theResourceName, theParamName);
Condition pathPredicate = createPredicateSourcePaths(pathsToMatch);
return toAndPredicate(pathPredicate, multiTypeOrPredicate);
}
@Nonnull
private List<String> determineCandidateResourceTypesForChain(String theResourceName, String theParamName, ReferenceParam theReferenceParam) {
final List<Class<? extends IBaseResource>> resourceTypes;
if (!theReferenceParam.hasResourceType()) {
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
resourceTypes = new ArrayList<>();
if (param.hasTargets()) {
Set<String> targetTypes = param.getTargets();
for (String next : targetTypes) {
resourceTypes.add(getFhirContext().getResourceDefinition(next).getImplementingClass());
}
}
if (resourceTypes.isEmpty()) {
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResourceName);
RuntimeSearchParam searchParamByName = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
if (searchParamByName == null) {
throw new InternalErrorException("Could not find parameter " + theParamName);
}
String paramPath = searchParamByName.getPath();
if (paramPath.endsWith(".as(Reference)")) {
paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference";
}
if (paramPath.contains(".extension(")) {
int startIdx = paramPath.indexOf(".extension(");
int endIdx = paramPath.indexOf(')', startIdx);
if (startIdx != -1 && endIdx != -1) {
paramPath = paramPath.substring(0, startIdx + 10) + paramPath.substring(endIdx + 1);
}
}
Class<? extends IBaseResource> resourceType = getFhirContext().getResourceDefinition(theResourceName).getImplementingClass();
BaseRuntimeChildDefinition def = getFhirContext().newTerser().getDefinition(resourceType, paramPath);
if (def instanceof RuntimeChildChoiceDefinition) {
RuntimeChildChoiceDefinition choiceDef = (RuntimeChildChoiceDefinition) def;
resourceTypes.addAll(choiceDef.getResourceTypes());
} else if (def instanceof RuntimeChildResourceDefinition) {
RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def;
resourceTypes.addAll(resDef.getResourceTypes());
if (resourceTypes.size() == 1) {
if (resourceTypes.get(0).isInterface()) {
throw new InvalidRequestException("Unable to perform search for unqualified chain '" + theParamName + "' as this SearchParameter does not declare any target types. Add a qualifier of the form '" + theParamName + ":[ResourceType]' to perform this search.");
}
}
} else {
throw new ConfigurationException("Property " + paramPath + " of type " + getResourceType() + " is not a resource: " + def.getClass());
}
}
if (resourceTypes.isEmpty()) {
for (BaseRuntimeElementDefinition<?> next : getFhirContext().getElementDefinitions()) {
if (next instanceof RuntimeResourceDefinition) {
RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition) next;
resourceTypes.add(nextResDef.getImplementingClass());
}
}
}
} else {
try {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(theReferenceParam.getResourceType());
resourceTypes = new ArrayList<>(1);
resourceTypes.add(resDef.getImplementingClass());
} catch (DataFormatException e) {
throw newInvalidResourceTypeException(theReferenceParam.getResourceType());
}
}
return resourceTypes
.stream()
.map(t -> getFhirContext().getResourceType(t))
.collect(Collectors.toList());
}
public List<String> createResourceLinkPaths(String theResourceName, String theParamName) {
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResourceName);
RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
List<String> path = param.getPathsSplit();
/*
* SearchParameters can declare paths on multiple resource
* types. Here we only want the ones that actually apply.
*/
path = new ArrayList<>(path);
ListIterator<String> iter = path.listIterator();
while (iter.hasNext()) {
String nextPath = trim(iter.next());
if (!nextPath.contains(theResourceName + ".")) {
iter.remove();
}
}
return path;
}
private IQueryParameterType mapReferenceChainToRawParamType(String remainingChain, RuntimeSearchParam param, String theParamName, String qualifier, String nextType, String chain, boolean isMeta, String resourceId) {
IQueryParameterType chainValue;
if (remainingChain != null) {
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", nextType, chain, remainingChain);
return null;
}
chainValue = new ReferenceParam();
chainValue.setValueAsQueryToken(getFhirContext(), theParamName, qualifier, resourceId);
((ReferenceParam) chainValue).setChain(remainingChain);
} else if (isMeta) {
IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
type.setValueAsQueryToken(getFhirContext(), theParamName, qualifier, resourceId);
chainValue = type;
} else {
chainValue = toParameterType(param, qualifier, resourceId);
}
return chainValue;
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
case SPECIAL:
if ("Location.position".equals(theParam.getPath())) {
qp = new SpecialParam();
break;
}
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
case URI:
case HAS:
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
}
return qp;
}
@Nonnull
private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) {
String searchParamName = theResourceName + ":" + theParamName;
String msg = getFhirContext().getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
return new InvalidRequestException(msg);
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
IQueryParameterType qp = toParameterType(theParam);
qp.setValueAsQueryToken(getFhirContext(), theParam.getName(), theQualifier, theValueAsQueryToken);
return qp;
}
@Nonnull
private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
String msg = getFhirContext().getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
throw new InvalidRequestException(msg);
}
@Nonnull
public Condition createEverythingPredicate(String theResourceName, Long theTargetPid) {
if (theTargetPid != null) {
return BinaryCondition.equalTo(myColumnTargetResourceId, generatePlaceholder(theTargetPid));
} else {
return BinaryCondition.equalTo(myColumnTargetResourceType, generatePlaceholder(theResourceName));
}
}
}

View File

@ -0,0 +1,86 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.NotCondition;
import com.healthmarketscience.sqlbuilder.UnaryCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import java.util.Set;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toAndPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toEqualToOrInPredicate;
public class ResourceTablePredicateBuilder extends BaseJoiningPredicateBuilder {
private final DbColumn myColumnResId;
private final DbColumn myColumnResDeletedAt;
private final DbColumn myColumnResType;
private final DbColumn myColumnLastUpdated;
private final DbColumn myColumnLanguage;
/**
* Constructor
*/
public ResourceTablePredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RESOURCE"));
myColumnResId = getTable().addColumn("RES_ID");
myColumnResType = getTable().addColumn("RES_TYPE");
myColumnResDeletedAt = getTable().addColumn("RES_DELETED_AT");
myColumnLastUpdated = getTable().addColumn("RES_UPDATED");
myColumnLanguage = getTable().addColumn("RES_LANGUAGE");
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResId;
}
public Condition createResourceTypeAndNonDeletedPredicates() {
BinaryCondition typePredicate = null;
if (getResourceType() != null) {
typePredicate = BinaryCondition.equalTo(myColumnResType, generatePlaceholder(getResourceType()));
}
return toAndPredicate(
typePredicate,
UnaryCondition.isNull(myColumnResDeletedAt)
);
}
public DbColumn getLastUpdatedColumn() {
return myColumnLastUpdated;
}
public Condition createLanguagePredicate(Set<String> theValues, boolean theNegated) {
Condition condition = toEqualToOrInPredicate(myColumnLanguage, generatePlaceholders(theValues));
if (theNegated) {
condition = new NotCondition(condition);
}
return condition;
}
public DbColumn getColumnLastUpdated() {
return myColumnLastUpdated;
}
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.springframework.beans.factory.annotation.Autowired;
public class SearchParamPresentPredicateBuilder extends BaseJoiningPredicateBuilder {
private final DbColumn myColumnResourceId;
private final DbColumn myColumnHashPresence;
@Autowired
private PartitionSettings myPartitionSettings;
/**
* Constructor
*/
public SearchParamPresentPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RES_PARAM_PRESENT"));
myColumnResourceId = getTable().addColumn("RES_ID");
myColumnHashPresence = getTable().addColumn("HASH_PRESENCE");
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResourceId;
}
public Condition createPredicateParamMissingForReference(String theResourceName, String theParamName, boolean theMissing, RequestPartitionId theRequestPartitionId) {
Long hash = SearchParamPresent.calculateHashPresence(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName, !theMissing);
BinaryCondition predicate = BinaryCondition.equalTo(myColumnHashPresence, generatePlaceholder(hash));
return combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
}
}

View File

@ -0,0 +1,62 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SourcePredicateBuilder extends BaseJoiningPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(SourcePredicateBuilder.class);
private final DbColumn myColumnSourceUri;
private final DbColumn myColumnRequestId;
private final DbColumn myResourceIdColumn;
/**
* Constructor
*/
public SourcePredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RES_VER_PROV"));
myResourceIdColumn = getTable().addColumn("RES_PID");
myColumnSourceUri = getTable().addColumn("SOURCE_URI");
myColumnRequestId = getTable().addColumn("REQUEST_ID");
}
@Override
public DbColumn getResourceIdColumn() {
return myResourceIdColumn;
}
public Condition createPredicateSourceUri(String theSourceUri) {
return BinaryCondition.equalTo(myColumnSourceUri, generatePlaceholder(theSourceUri));
}
public Condition createPredicateRequestId(String theRequestId) {
return BinaryCondition.equalTo(myColumnRequestId, generatePlaceholder(theRequestId));
}
}

View File

@ -0,0 +1,237 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.StringUtil;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
public class StringPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnResId;
private final DbColumn myColumnValueExact;
private final DbColumn myColumnValueNormalized;
private final DbColumn myColumnHashNormPrefix;
private final DbColumn myColumnHashIdentity;
private final DbColumn myColumnHashExact;
@Autowired
private DaoConfig myDaoConfig;
/**
* Constructor
*/
public StringPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_STRING"));
myColumnResId = getTable().addColumn("RES_ID");
myColumnValueExact = getTable().addColumn("SP_VALUE_EXACT");
myColumnValueNormalized = getTable().addColumn("SP_VALUE_NORMALIZED");
myColumnHashNormPrefix = getTable().addColumn("HASH_NORM_PREFIX");
myColumnHashIdentity = getTable().addColumn("HASH_IDENTITY");
myColumnHashExact = getTable().addColumn("HASH_EXACT");
}
public DbColumn getColumnValueNormalized() {
return myColumnValueNormalized;
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResId;
}
public Condition createPredicateString(IQueryParameterType theParameter,
String theResourceName,
RuntimeSearchParam theSearchParam,
StringPredicateBuilder theFrom,
SearchFilterParser.CompareOperation operation) {
String rawSearchTerm;
String paramName = theSearchParam.getName();
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
if (!id.isText()) {
throw new IllegalStateException("Trying to process a text search on a non-text token parameter");
}
rawSearchTerm = id.getValue();
} else if (theParameter instanceof StringParam) {
StringParam id = (StringParam) theParameter;
rawSearchTerm = id.getValue();
if (id.isContains()) {
if (!myDaoConfig.isAllowContainsSearches()) {
throw new MethodNotAllowedException(":contains modifier is disabled on this server");
}
} else {
rawSearchTerm = theSearchParam.encode(rawSearchTerm);
}
} else if (theParameter instanceof IPrimitiveDatatype<?>) {
IPrimitiveDatatype<?> id = (IPrimitiveDatatype<?>) theParameter;
rawSearchTerm = id.getValueAsString();
} else {
throw new IllegalArgumentException("Invalid token type: " + theParameter.getClass());
}
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + paramName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
if (exactMatch) {
// Exact match
return theFrom.createPredicateExact(theResourceName, paramName, rawSearchTerm);
} else {
// Normalized Match
String normalizedString = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm);
String likeExpression;
if ((theParameter instanceof StringParam) &&
(((((StringParam) theParameter).isContains()) &&
(myDaoConfig.isAllowContainsSearches())) ||
(operation == SearchFilterParser.CompareOperation.co))) {
likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
} else if ((operation != SearchFilterParser.CompareOperation.ne) &&
(operation != SearchFilterParser.CompareOperation.gt) &&
(operation != SearchFilterParser.CompareOperation.lt) &&
(operation != SearchFilterParser.CompareOperation.ge) &&
(operation != SearchFilterParser.CompareOperation.le)) {
if (operation == SearchFilterParser.CompareOperation.ew) {
likeExpression = createRightMatchLikeExpression(normalizedString);
} else {
likeExpression = createLeftMatchLikeExpression(normalizedString);
}
} else {
likeExpression = normalizedString;
}
Condition predicate;
if ((operation == null) ||
(operation == SearchFilterParser.CompareOperation.sw)) {
predicate = theFrom.createPredicateNormalLike(theResourceName, paramName, normalizedString, likeExpression);
} else if ((operation == SearchFilterParser.CompareOperation.ew) || (operation == SearchFilterParser.CompareOperation.co)) {
predicate = theFrom.createPredicateLikeExpressionOnly(theResourceName, paramName, likeExpression, false);
} else if (operation == SearchFilterParser.CompareOperation.eq) {
predicate = theFrom.createPredicateNormal(theResourceName, paramName, normalizedString);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
predicate = theFrom.createPredicateLikeExpressionOnly(theResourceName, paramName, likeExpression, true);
} else if (operation == SearchFilterParser.CompareOperation.gt) {
predicate = theFrom.createPredicateNormalGreaterThan(theResourceName, paramName, likeExpression);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
predicate = theFrom.createPredicateNormalGreaterThanOrEqual(theResourceName, paramName, likeExpression);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
predicate = theFrom.createPredicateNormalLessThan(theResourceName, paramName, likeExpression);
} else if (operation == SearchFilterParser.CompareOperation.le) {
predicate = theFrom.createPredicateNormalLessThanOrEqual(theResourceName, paramName, likeExpression);
} else {
throw new IllegalArgumentException("Don't yet know how to handle operation " + operation + " on a string");
}
return predicate;
}
}
@Nonnull
public Condition createPredicateExact(String theResourceType, String theParamName, String theTheValueExact) {
long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), getRequestPartitionId(), theResourceType, theParamName, theTheValueExact);
String placeholderValue = generatePlaceholder(hash);
return BinaryCondition.equalTo(myColumnHashExact, placeholderValue);
}
@Nonnull
public Condition createPredicateNormalLike(String theResourceType, String theParamName, String theNormalizedString, String theLikeExpression) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), getRequestPartitionId(), getModelConfig(), theResourceType, theParamName, theNormalizedString);
Condition hashPredicate = BinaryCondition.equalTo(myColumnHashNormPrefix, generatePlaceholder(hash));
Condition valuePredicate = BinaryCondition.like(myColumnValueNormalized, generatePlaceholder(theLikeExpression));
return ComboCondition.and(hashPredicate, valuePredicate);
}
@Nonnull
public Condition createPredicateNormal(String theResourceType, String theParamName, String theNormalizedString) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), getRequestPartitionId(), getModelConfig(), theResourceType, theParamName, theNormalizedString);
Condition hashPredicate = BinaryCondition.equalTo(myColumnHashNormPrefix, generatePlaceholder(hash));
Condition valuePredicate = BinaryCondition.equalTo(myColumnValueNormalized, generatePlaceholder(theNormalizedString));
return ComboCondition.and(hashPredicate, valuePredicate);
}
private Condition createPredicateNormalGreaterThanOrEqual(String theResourceType, String theParamName, String theNormalizedString) {
Condition hashPredicate = createHashIdentityPredicate(theResourceType, theParamName);
Condition valuePredicate = BinaryCondition.greaterThanOrEq(myColumnValueNormalized, generatePlaceholder(theNormalizedString));
return ComboCondition.and(hashPredicate, valuePredicate);
}
private Condition createPredicateNormalGreaterThan(String theResourceType, String theParamName, String theNormalizedString) {
Condition hashPredicate = createHashIdentityPredicate(theResourceType, theParamName);
Condition valuePredicate = BinaryCondition.greaterThan(myColumnValueNormalized, generatePlaceholder(theNormalizedString));
return ComboCondition.and(hashPredicate, valuePredicate);
}
private Condition createPredicateNormalLessThanOrEqual(String theResourceType, String theParamName, String theNormalizedString) {
Condition hashPredicate = createHashIdentityPredicate(theResourceType, theParamName);
Condition valuePredicate = BinaryCondition.lessThanOrEq(myColumnValueNormalized, generatePlaceholder(theNormalizedString));
return ComboCondition.and(hashPredicate, valuePredicate);
}
private Condition createPredicateNormalLessThan(String theResourceType, String theParamName, String theNormalizedString) {
Condition hashPredicate = createHashIdentityPredicate(theResourceType, theParamName);
Condition valuePredicate = BinaryCondition.lessThan(myColumnValueNormalized, generatePlaceholder(theNormalizedString));
return ComboCondition.and(hashPredicate, valuePredicate);
}
@Nonnull
public Condition createPredicateLikeExpressionOnly(String theResourceType, String theParamName, String theLikeExpression, boolean theInverse) {
long hashIdentity = ResourceIndexedSearchParamString.calculateHashIdentity(getPartitionSettings(), getRequestPartitionId(), theResourceType, theParamName);
BinaryCondition identityPredicate = BinaryCondition.equalTo(myColumnHashIdentity, generatePlaceholder(hashIdentity));
BinaryCondition likePredicate;
if (theInverse) {
likePredicate = BinaryCondition.notLike(myColumnValueNormalized, generatePlaceholder(theLikeExpression));
} else {
likePredicate = BinaryCondition.like(myColumnValueNormalized, generatePlaceholder(theLikeExpression));
}
return ComboCondition.and(identityPredicate, likePredicate);
}
public static String createLeftAndRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]") + "%";
}
public static String createLeftMatchLikeExpression(String likeExpression) {
return likeExpression.replace("%", "[%]") + "%";
}
public static String createRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]");
}
}

View File

@ -0,0 +1,91 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import com.google.common.collect.Lists;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TagPredicateBuilder extends BaseJoiningPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(TagPredicateBuilder.class);
private final DbColumn myColumnResId;
private final DbTable myTagDefinitionTable;
private final DbColumn myTagDefinitionColumnTagId;
private final DbColumn myTagDefinitionColumnTagSystem;
private final DbColumn myTagDefinitionColumnTagCode;
private final DbColumn myColumnTagId;
private final DbColumn myTagDefinitionColumnTagType;
public TagPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RES_TAG"));
myColumnResId = getTable().addColumn("RES_ID");
myColumnTagId = getTable().addColumn("TAG_ID");
myTagDefinitionTable = theSearchSqlBuilder.addTable("HFJ_TAG_DEF");
myTagDefinitionColumnTagId = myTagDefinitionTable.addColumn("TAG_ID");
myTagDefinitionColumnTagSystem = myTagDefinitionTable.addColumn("TAG_SYSTEM");
myTagDefinitionColumnTagCode = myTagDefinitionTable.addColumn("TAG_CODE");
myTagDefinitionColumnTagType = myTagDefinitionTable.addColumn("TAG_TYPE");
}
public Condition createPredicateTag(TagTypeEnum theTagType, List<Pair<String, String>> theTokens, String theParamName, RequestPartitionId theRequestPartitionId) {
addJoin(getTable(), myTagDefinitionTable, myColumnTagId, myTagDefinitionColumnTagId);
return createPredicateTagList(theTagType, theTokens);
}
private Condition createPredicateTagList(TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
Condition typePredicate = BinaryCondition.equalTo(myTagDefinitionColumnTagType, generatePlaceholder(theTagType.ordinal()));
List<Condition> orPredicates = Lists.newArrayList();
for (Pair<String, String> next : theTokens) {
Condition codePredicate = BinaryCondition.equalTo(myTagDefinitionColumnTagCode, generatePlaceholder(next.getRight()));
if (isNotBlank(next.getLeft())) {
Condition systemPredicate = BinaryCondition.equalTo(myTagDefinitionColumnTagSystem, generatePlaceholder(next.getLeft()));
orPredicates.add(ComboCondition.and(typePredicate, systemPredicate, codePredicate));
} else {
orPredicates.add(ComboCondition.and(typePredicate, codePredicate));
}
}
return ComboCondition.or(orPredicates.toArray(new Condition[0]));
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResId;
}
}

View File

@ -0,0 +1,321 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import com.google.common.collect.Sets;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.InCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toAndPredicate;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toOrPredicate;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnResId;
private final DbColumn myColumnHashSystemAndValue;
private final DbColumn myColumnHashSystem;
private final DbColumn myColumnHashValue;
private final DbColumn myColumnSystem;
private final DbColumn myColumnValue;
@Autowired
private ITermReadSvc myTerminologySvc;
/**
* Constructor
*/
public TokenPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_TOKEN"));
myColumnResId = getTable().addColumn("RES_ID");
myColumnHashSystem = getTable().addColumn("HASH_SYS");
myColumnHashSystemAndValue = getTable().addColumn("HASH_SYS_AND_VALUE");
myColumnHashValue = getTable().addColumn("HASH_VALUE");
myColumnSystem = getTable().addColumn("SP_SYSTEM");
myColumnValue = getTable().addColumn("SP_VALUE");
}
@Override
public DbColumn getResourceIdColumn() {
return myColumnResId;
}
public Condition createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
RuntimeSearchParam theSearchParam,
RequestPartitionId theRequestPartitionId) {
return createPredicateToken(
theParameters,
theResourceName,
theSearchParam,
null,
theRequestPartitionId);
}
public Condition createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
RuntimeSearchParam theSearchParam,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
final List<FhirVersionIndependentConcept> codes = new ArrayList<>();
String paramName = theSearchParam.getName();
SearchFilterParser.CompareOperation operation = theOperation;
TokenParamModifier modifier = null;
for (IQueryParameterType nextParameter : theParameters) {
String code;
String system;
if (nextParameter instanceof TokenParam) {
TokenParam id = (TokenParam) nextParameter;
system = id.getSystem();
code = (id.getValue());
modifier = id.getModifier();
} else if (nextParameter instanceof BaseIdentifierDt) {
BaseIdentifierDt id = (BaseIdentifierDt) nextParameter;
system = id.getSystemElement().getValueAsString();
code = (id.getValueElement().getValue());
} else if (nextParameter instanceof BaseCodingDt) {
BaseCodingDt id = (BaseCodingDt) nextParameter;
system = id.getSystemElement().getValueAsString();
code = (id.getCodeElement().getValue());
} else if (nextParameter instanceof NumberParam) {
NumberParam number = (NumberParam) nextParameter;
system = null;
code = number.getValueAsQueryToken(getFhirContext());
} else {
throw new IllegalArgumentException("Invalid token type: " + nextParameter.getClass());
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(
"Parameter[" + paramName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(
"Parameter[" + paramName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
}
/*
* Process token modifiers (:in, :below, :above)
*/
if (modifier == TokenParamModifier.IN) {
codes.addAll(myTerminologySvc.expandValueSet(null, code));
} else if (modifier == TokenParamModifier.ABOVE) {
system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system);
codes.addAll(myTerminologySvc.findCodesAbove(system, code));
} else if (modifier == TokenParamModifier.BELOW) {
system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system);
codes.addAll(myTerminologySvc.findCodesBelow(system, code));
} else {
if (modifier == TokenParamModifier.NOT && operation == null) {
operation = SearchFilterParser.CompareOperation.ne;
}
codes.add(new FhirVersionIndependentConcept(system, code));
}
}
List<FhirVersionIndependentConcept> sortedCodesList = codes
.stream()
.filter(t -> t.getCode() != null || t.getSystem() != null)
.sorted()
.distinct()
.collect(Collectors.toList());
if (codes.isEmpty()) {
// This will never match anything
setMatchNothing();
return null;
}
Condition predicate;
if (operation == SearchFilterParser.CompareOperation.ne) {
/*
* For a token :not search, we look for index rows that have the right identity (i.e. it's the right resource and
* param name) but not the actual provided token value.
*/
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName);
Condition hashIdentityPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hashIdentity));
Condition hashValuePredicate = createPredicateOrList(theResourceName, theSearchParam.getName(), sortedCodesList, false);
predicate = toAndPredicate(hashIdentityPredicate, hashValuePredicate);
} else {
predicate = createPredicateOrList(theResourceName, theSearchParam.getName(), sortedCodesList, true);
}
return predicate;
}
private String determineSystemIfMissing(RuntimeSearchParam theSearchParam, String code, String theSystem) {
String retVal = theSystem;
if (retVal == null) {
if (theSearchParam != null) {
Set<String> valueSetUris = Sets.newHashSet();
for (String nextPath : theSearchParam.getPathsSplit()) {
Class<? extends IBaseResource> type = getFhirContext().getResourceDefinition(getResourceType()).getImplementingClass();
BaseRuntimeChildDefinition def = getFhirContext().newTerser().getDefinition(type, nextPath);
if (def instanceof BaseRuntimeDeclaredChildDefinition) {
String valueSet = ((BaseRuntimeDeclaredChildDefinition) def).getBindingValueSet();
if (isNotBlank(valueSet)) {
valueSetUris.add(valueSet);
}
}
}
if (valueSetUris.size() == 1) {
String valueSet = valueSetUris.iterator().next();
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setFailOnMissingCodeSystem(false);
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(options, valueSet);
for (FhirVersionIndependentConcept nextCandidate : candidateCodes) {
if (nextCandidate.getCode().equals(code)) {
retVal = nextCandidate.getSystem();
break;
}
}
}
}
}
return retVal;
}
public DbColumn getColumnSystem() {
return myColumnSystem;
}
public DbColumn getColumnValue() {
return myColumnValue;
}
private void validateHaveSystemAndCodeForToken(String theParamName, String theCode, String theSystem) {
String systemDesc = defaultIfBlank(theSystem, "(missing)");
String codeDesc = defaultIfBlank(theCode, "(missing)");
if (isBlank(theCode)) {
String msg = getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(msg);
}
if (isBlank(theSystem)) {
String msg = getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(msg);
}
}
private Condition createPredicateOrList(String theResourceType, String theSearchParamName, List<FhirVersionIndependentConcept> theCodes, boolean theWantEquals) {
Condition[] conditions = new Condition[theCodes.size()];
Long[] hashes = new Long[theCodes.size()];
DbColumn[] columns = new DbColumn[theCodes.size()];
boolean haveMultipleColumns = false;
for (int i = 0; i < conditions.length; i++) {
FhirVersionIndependentConcept nextToken = theCodes.get(i);
long hash;
DbColumn column;
if (nextToken.getSystem() == null) {
hash = ResourceIndexedSearchParamToken.calculateHashValue(getPartitionSettings(), getRequestPartitionId(), theResourceType, theSearchParamName, nextToken.getCode());
column = myColumnHashValue;
} else if (isBlank(nextToken.getCode())) {
hash = ResourceIndexedSearchParamToken.calculateHashSystem(getPartitionSettings(), getRequestPartitionId(), theResourceType, theSearchParamName, nextToken.getSystem());
column = myColumnHashSystem;
} else {
hash = ResourceIndexedSearchParamToken.calculateHashSystemAndValue(getPartitionSettings(), getRequestPartitionId(), theResourceType, theSearchParamName, nextToken.getSystem(), nextToken.getCode());
column = myColumnHashSystemAndValue;
}
hashes[i] = hash;
columns[i] = column;
if (i > 0 && columns[0] != columns[i]) {
haveMultipleColumns = true;
}
}
if (!haveMultipleColumns && conditions.length > 1) {
List<Long> values = Arrays.asList(hashes);
InCondition predicate = new InCondition(columns[0], generatePlaceholders(values));
if (!theWantEquals) {
predicate.setNegate(true);
}
return predicate;
}
for (int i = 0; i < conditions.length; i++) {
String valuePlaceholder = generatePlaceholder(hashes[i]);
if (theWantEquals) {
conditions[i] = BinaryCondition.equalTo(columns[i], valuePlaceholder);
} else {
conditions[i] = BinaryCondition.notEqualTo(columns[i], valuePlaceholder);
}
}
if (conditions.length > 1) {
if (theWantEquals) {
return toOrPredicate(conditions);
} else {
return toAndPredicate(conditions);
}
} else {
return conditions[0];
}
}
}

View File

@ -0,0 +1,197 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.toEqualToOrInPredicate;
import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.createLeftAndRightMatchLikeExpression;
import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.createLeftMatchLikeExpression;
import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.createRightMatchLikeExpression;
public class UriPredicateBuilder extends BaseSearchParamPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(UriPredicateBuilder.class);
private final DbColumn myColumnUri;
private final DbColumn myColumnHashUri;
@Autowired
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
/**
* Constructor
*/
public UriPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_URI"));
myColumnUri = getTable().addColumn("SP_URI");
myColumnHashUri = getTable().addColumn("HASH_URI");
}
public Condition addPredicate(List<? extends IQueryParameterType> theUriOrParameterList, String theParamName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequestDetails) {
List<Condition> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theUriOrParameterList) {
if (nextOr instanceof UriParam) {
UriParam param = (UriParam) nextOr;
String value = param.getValue();
if (value == null) {
continue;
}
if (param.getQualifier() == UriParamQualifierEnum.ABOVE) {
/*
* :above is an inefficient query- It means that the user is supplying a more specific URL (say
* http://example.com/foo/bar/baz) and that we should match on any URLs that are less
* specific but otherwise the same. For example http://example.com and http://example.com/foo would both
* match.
*
* We do this by querying the DB for all candidate URIs and then manually checking each one. This isn't
* very efficient, but this is also probably not a very common type of query to do.
*
* If we ever need to make this more efficient, lucene could certainly be used as an optimization.
*/
String msg = "Searching for candidate URI:above parameters for Resource["+getResourceType()+"] param["+theParamName+"]";
ourLog.info(msg);
StorageProcessingMessage message = new StorageProcessingMessage();
ourLog.warn(msg);
message.setMessage(msg);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(StorageProcessingMessage.class, message);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_WARNING, params);
Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(getResourceType(), theParamName);
List<String> toFind = new ArrayList<>();
for (String next : candidates) {
if (value.length() >= next.length()) {
if (value.startsWith(next)) {
toFind.add(next);
}
}
}
if (toFind.isEmpty()) {
continue;
}
Condition uriPredicate = toEqualToOrInPredicate(myColumnUri, generatePlaceholders(toFind));
Condition hashAndUriPredicate = combineWithHashIdentityPredicate(getResourceType(), theParamName, uriPredicate);
codePredicates.add(hashAndUriPredicate);
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
Condition uriPredicate = BinaryCondition.like(myColumnUri, generatePlaceholder(createLeftMatchLikeExpression(value)));
Condition hashAndUriPredicate = combineWithHashIdentityPredicate(getResourceType(), theParamName, uriPredicate);
codePredicates.add(hashAndUriPredicate);
} else {
Condition uriPredicate = null;
if (theOperation == null || theOperation == SearchFilterParser.CompareOperation.eq) {
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionSettings(), getRequestPartitionId(), getResourceType(), theParamName, value);
Condition hashPredicate = BinaryCondition.equalTo(myColumnHashUri, generatePlaceholder(hashUri));
codePredicates.add(hashPredicate);
} else if (theOperation == SearchFilterParser.CompareOperation.ne) {
uriPredicate = BinaryCondition.notEqualTo(myColumnUri, generatePlaceholder(value));
} else if (theOperation == SearchFilterParser.CompareOperation.co) {
uriPredicate = BinaryCondition.like(myColumnUri, generatePlaceholder(createLeftAndRightMatchLikeExpression(value)));
} else if (theOperation == SearchFilterParser.CompareOperation.gt) {
uriPredicate = BinaryCondition.greaterThan(myColumnUri, generatePlaceholder(value));
} else if (theOperation == SearchFilterParser.CompareOperation.lt) {
uriPredicate = BinaryCondition.lessThan(myColumnUri, generatePlaceholder(value));
} else if (theOperation == SearchFilterParser.CompareOperation.ge) {
uriPredicate = BinaryCondition.greaterThanOrEq(myColumnUri, generatePlaceholder(value));
} else if (theOperation == SearchFilterParser.CompareOperation.le) {
uriPredicate = BinaryCondition.lessThanOrEq(myColumnUri, generatePlaceholder(value));
} else if (theOperation == SearchFilterParser.CompareOperation.sw) {
uriPredicate = BinaryCondition.like(myColumnUri, generatePlaceholder(createLeftMatchLikeExpression(value)));
} else if (theOperation == SearchFilterParser.CompareOperation.ew) {
uriPredicate = BinaryCondition.like(myColumnUri, generatePlaceholder(createRightMatchLikeExpression(value)));
} else {
throw new IllegalArgumentException(String.format("Unsupported operator specified in _filter clause, %s",
theOperation.toString()));
}
if (uriPredicate != null) {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), getRequestPartitionId(), getResourceType(), theParamName);
BinaryCondition hashIdentityPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hashIdentity));
codePredicates.add(ComboCondition.and(hashIdentityPredicate, uriPredicate));
}
}
} else {
throw new IllegalArgumentException("Invalid URI type: " + nextOr.getClass());
}
}
/*
* If we haven't found any of the requested URIs in the candidates, then the whole query should match nothing
*/
if (codePredicates.isEmpty()) {
setMatchNothing();
return null;
}
ComboCondition orPredicate = ComboCondition.or(codePredicates.toArray(new Condition[0]));
Condition outerPredicate = combineWithHashIdentityPredicate(getResourceType(), theParamName, orPredicate);
return outerPredicate;
}
public DbColumn getColumnValue() {
return myColumnUri;
}
}

View File

@ -0,0 +1,56 @@
package ca.uhn.fhir.jpa.search.builder.sql;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import java.util.List;
import java.util.regex.Pattern;
/**
* Represents the SQL generated by this query
*/
public class GeneratedSql {
public static final Pattern INLINE_EQ_PATTERN = Pattern.compile("=\\s*['0-9]");
public static final Pattern INLINE_IN_PATTERN = Pattern.compile("(in|IN)\\s*\\(\\s*['0-9]");
private final String mySql;
private final List<Object> myBindVariables;
private final boolean myMatchNothing;
public GeneratedSql(boolean theMatchNothing, String theSql, List<Object> theBindVariables) {
assert INLINE_EQ_PATTERN.matcher(theSql).find() == false : "Non-bound SQL parameter found: " + theSql;
assert INLINE_IN_PATTERN.matcher(theSql).find() == false : "Non-bound SQL parameter found: " + theSql;
myMatchNothing = theMatchNothing;
mySql = theSql;
myBindVariables = theBindVariables;
}
public boolean isMatchNothing() {
return myMatchNothing;
}
public List<Object> getBindVariables() {
return myBindVariables;
}
public String getSql() {
return mySql;
}
}

View File

@ -0,0 +1,515 @@
package ca.uhn.fhir.jpa.search.builder.sql;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SearchParamPresentPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SourcePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TagPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.UriPredicateBuilder;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.FunctionCall;
import com.healthmarketscience.sqlbuilder.InCondition;
import com.healthmarketscience.sqlbuilder.OrderObject;
import com.healthmarketscience.sqlbuilder.SelectQuery;
import com.healthmarketscience.sqlbuilder.dbspec.Join;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbJoin;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import org.apache.commons.lang3.Validate;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.engine.spi.RowSelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public class SearchQueryBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(SearchQueryBuilder.class);
private final String myBindVariableSubstitutionBase;
private final ArrayList<Object> myBindVariableValues;
private final DbSpec mySpec;
private final DbSchema mySchema;
private final SelectQuery mySelect;
private final PartitionSettings myPartitionSettings;
private final RequestPartitionId myRequestPartitionId;
private final String myResourceType;
private final ModelConfig myModelConfig;
private final FhirContext myFhirContext;
private final SqlObjectFactory mySqlBuilderFactory;
private final boolean myCountQuery;
private final Dialect myDialect;
private boolean myMatchNothing;
private ResourceTablePredicateBuilder myResourceTableRoot;
private boolean myHaveAtLeastOnePredicate;
private BaseJoiningPredicateBuilder myFirstPredicateBuilder;
/**
* Constructor
*/
public SearchQueryBuilder(FhirContext theFhirContext, ModelConfig theModelConfig, PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, SqlObjectFactory theSqlBuilderFactory, HibernateDialectProvider theDialectProvider, boolean theCountQuery) {
this(theFhirContext, theModelConfig, thePartitionSettings, theRequestPartitionId, theResourceType, theSqlBuilderFactory, UUID.randomUUID().toString() + "-", theDialectProvider.getDialect(), theCountQuery, new ArrayList<>());
}
/**
* Constructor for child SQL Builders
*/
private SearchQueryBuilder(FhirContext theFhirContext, ModelConfig theModelConfig, PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, SqlObjectFactory theSqlBuilderFactory, String theBindVariableSubstitutionBase, Dialect theDialect, boolean theCountQuery, ArrayList<Object> theBindVariableValues) {
myFhirContext = theFhirContext;
myModelConfig = theModelConfig;
myPartitionSettings = thePartitionSettings;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
mySqlBuilderFactory = theSqlBuilderFactory;
myCountQuery = theCountQuery;
myDialect = theDialect;
mySpec = new DbSpec();
mySchema = mySpec.addDefaultSchema();
mySelect = new SelectQuery();
myBindVariableSubstitutionBase = theBindVariableSubstitutionBase;
myBindVariableValues = theBindVariableValues;
}
public FhirContext getFhirContext() {
return myFhirContext;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a Composite Unique search parameter
*/
public CompositeUniqueSearchParameterPredicateBuilder addCompositeUniquePredicateBuilder() {
CompositeUniqueSearchParameterPredicateBuilder retVal = mySqlBuilderFactory.newCompositeUniqueSearchParameterPredicateBuilder(this);
addTable(retVal, null);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a COORDS search parameter
*/
public CoordsPredicateBuilder addCoordsPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
CoordsPredicateBuilder retVal = mySqlBuilderFactory.coordsPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a DATE search parameter
*/
public DatePredicateBuilder addDatePredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
DatePredicateBuilder retVal = mySqlBuilderFactory.dateIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder for selecting a forced ID. This is only intended for use with sorts so it can not
* be the root query.
*/
public ForcedIdPredicateBuilder addForcedIdPredicateBuilder(@Nonnull DbColumn theSourceJoinColumn) {
Validate.isTrue(theSourceJoinColumn != null);
ForcedIdPredicateBuilder retVal = mySqlBuilderFactory.newForcedIdPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a NUMBER search parameter
*/
public NumberPredicateBuilder addNumberPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
NumberPredicateBuilder retVal = mySqlBuilderFactory.numberIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a QUANTITY search parameter
*/
public ResourceTablePredicateBuilder addResourceTablePredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
ResourceTablePredicateBuilder retVal = mySqlBuilderFactory.resourceTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a QUANTITY search parameter
*/
public QuantityPredicateBuilder addQuantityPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
QuantityPredicateBuilder retVal = mySqlBuilderFactory.quantityIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a <code>_source</code> search parameter
*/
public SourcePredicateBuilder addSourcePredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
SourcePredicateBuilder retVal = mySqlBuilderFactory.newSourcePredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a REFERENCE search parameter
*/
public ResourceLinkPredicateBuilder addReferencePredicateBuilder(QueryStack theQueryStack, @Nullable DbColumn theSourceJoinColumn) {
ResourceLinkPredicateBuilder retVal = mySqlBuilderFactory.referenceIndexTable(theQueryStack, this, false);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a reosource link where the
* source and target are reversed. This is used for _has queries.
*/
public ResourceLinkPredicateBuilder addReferencePredicateBuilderReversed(QueryStack theQueryStack, DbColumn theSourceJoinColumn) {
ResourceLinkPredicateBuilder retVal = mySqlBuilderFactory.referenceIndexTable(theQueryStack, this, true);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a STRING search parameter
*/
public StringPredicateBuilder addStringPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
StringPredicateBuilder retVal = mySqlBuilderFactory.stringIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a <code>_tag</code> search parameter
*/
public TagPredicateBuilder addTagPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
TagPredicateBuilder retVal = mySqlBuilderFactory.newTagPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a TOKEN search parameter
*/
public TokenPredicateBuilder addTokenPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
TokenPredicateBuilder retVal = mySqlBuilderFactory.tokenIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a <code>:missing</code> search parameter
*/
public SearchParamPresentPredicateBuilder addSearchParamPresentPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
SearchParamPresentPredicateBuilder retVal = mySqlBuilderFactory.searchParamPresentPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a URI search parameter
*/
public UriPredicateBuilder addUriPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
UriPredicateBuilder retVal = mySqlBuilderFactory.uriIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
public ResourceIdPredicateBuilder newResourceIdBuilder() {
return mySqlBuilderFactory.resourceId(this);
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for an arbitrary table
*/
private void addTable(BaseJoiningPredicateBuilder thePredicateBuilder, @Nullable DbColumn theSourceJoinColumn) {
if (theSourceJoinColumn != null) {
DbTable fromTable = theSourceJoinColumn.getTable();
DbTable toTable = thePredicateBuilder.getTable();
DbColumn toColumn = thePredicateBuilder.getResourceIdColumn();
addJoin(fromTable, toTable, theSourceJoinColumn, toColumn);
} else {
if (myFirstPredicateBuilder == null) {
ResourceTablePredicateBuilder root;
if (thePredicateBuilder instanceof ResourceTablePredicateBuilder) {
root = (ResourceTablePredicateBuilder) thePredicateBuilder;
} else {
root = mySqlBuilderFactory.resourceTable(this);
}
if (myCountQuery) {
mySelect.addCustomColumns(FunctionCall.count().setIsDistinct(true).addColumnParams(root.getResourceIdColumn()));
} else {
mySelect.addColumns(root.getResourceIdColumn());
}
mySelect.addFromTable(root.getTable());
myFirstPredicateBuilder = root;
if (thePredicateBuilder instanceof ResourceTablePredicateBuilder) {
return;
}
}
DbTable fromTable = myFirstPredicateBuilder.getTable();
DbTable toTable = thePredicateBuilder.getTable();
DbColumn fromColumn = myFirstPredicateBuilder.getResourceIdColumn();
DbColumn toColumn = thePredicateBuilder.getResourceIdColumn();
addJoin(fromTable, toTable, fromColumn, toColumn);
}
}
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn theFromColumn, DbColumn theToColumn) {
Join join = new DbJoin(mySpec, theFromTable, theToTable, new DbColumn[]{theFromColumn}, new DbColumn[]{theToColumn});
mySelect.addJoins(SelectQuery.JoinType.LEFT_OUTER, join);
}
/**
* Generate and return the SQL generated by this builder
*/
public GeneratedSql generate(@Nullable Integer theOffset, @Nullable Integer theMaxResultsToFetch) {
getOrCreateFirstPredicateBuilder();
mySelect.validate();
String sql = mySelect.toString();
List<Object> bindVariables = new ArrayList<>();
while (true) {
int idx = sql.indexOf(myBindVariableSubstitutionBase);
if (idx == -1) {
break;
}
int endIdx = sql.indexOf("'", idx + myBindVariableSubstitutionBase.length());
String substitutionIndexString = sql.substring(idx + myBindVariableSubstitutionBase.length(), endIdx);
int substitutionIndex = Integer.parseInt(substitutionIndexString);
bindVariables.add(myBindVariableValues.get(substitutionIndex));
sql = sql.substring(0, idx - 1) + "?" + sql.substring(endIdx + 1);
}
Integer maxResultsToFetch = theMaxResultsToFetch;
Integer offset = theOffset;
if (offset != null && offset == 0) {
offset = null;
}
if (maxResultsToFetch != null || offset != null) {
maxResultsToFetch = defaultIfNull(maxResultsToFetch, 10000);
sql = "SELECT " +
myFirstPredicateBuilder.getResourceIdColumn().getColumnNameSQL() +
" FROM ( " + sql + " ) " +
" AS " + myFirstPredicateBuilder.getResourceIdColumn().getColumnNameSQL();
LimitHandler limitHandler = myDialect.getLimitHandler();
RowSelection selection = new RowSelection();
selection.setFirstRow(offset);
selection.setMaxRows(maxResultsToFetch);
sql = limitHandler.processSql(sql, selection);
if (limitHandler.supportsLimit()) {
bindVariables.add(maxResultsToFetch);
}
if (limitHandler.supportsLimitOffset() && offset != null) {
bindVariables.add(offset);
}
}
return new GeneratedSql(myMatchNothing, sql, bindVariables);
}
/**
* If at least one predicate builder already exists, return the last one added to the chain. If none has been selected, create a builder on HFJ_RESOURCE, add it and return it.
*/
public BaseJoiningPredicateBuilder getOrCreateFirstPredicateBuilder() {
if (myFirstPredicateBuilder == null) {
getOrCreateResourceTablePredicateBuilder();
}
return myFirstPredicateBuilder;
}
public ResourceTablePredicateBuilder getOrCreateResourceTablePredicateBuilder() {
if (myResourceTableRoot == null) {
ResourceTablePredicateBuilder resourceTable = mySqlBuilderFactory.resourceTable(this);
addTable(resourceTable, null);
Condition typeAndDeletionPredicate = resourceTable.createResourceTypeAndNonDeletedPredicates();
addPredicate(typeAndDeletionPredicate);
myResourceTableRoot = resourceTable;
}
return myResourceTableRoot;
}
/**
* The SQL Builder library has one annoying limitation, which is that it does not use/understand bind variables
* for its generated SQL. So we work around this by replacing our contents with a string in the SQL consisting
* of <code>[random UUID]-[value index]</code> and then
*/
public String generatePlaceholder(Object theValue) {
String placeholder = myBindVariableSubstitutionBase + myBindVariableValues.size();
myBindVariableValues.add(theValue);
return placeholder;
}
public List<String> generatePlaceholders(Collection<?> theValues) {
return theValues
.stream()
.map(t -> generatePlaceholder(t))
.collect(Collectors.toList());
}
public void setMatchNothing() {
myMatchNothing = true;
}
public DbTable addTable(String theTableName) {
return mySchema.addTable(theTableName);
}
public PartitionSettings getPartitionSettings() {
return myPartitionSettings;
}
public RequestPartitionId getRequestPartitionId() {
return myRequestPartitionId;
}
public String getResourceType() {
return myResourceType;
}
public ModelConfig getModelConfig() {
return myModelConfig;
}
public void addPredicate(@Nonnull Condition theCondition) {
assert theCondition != null;
mySelect.addCondition(theCondition);
myHaveAtLeastOnePredicate = true;
}
public ComboCondition addPredicateLastUpdated(DateRangeParam theDateRange) {
ResourceTablePredicateBuilder resourceTableRoot = getOrCreateResourceTablePredicateBuilder();
List<Condition> conditions = new ArrayList<>(2);
if (theDateRange.getLowerBoundAsInstant() != null) {
BinaryCondition condition = createConditionForValueWithComparator(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, resourceTableRoot.getLastUpdatedColumn(), theDateRange.getLowerBoundAsInstant());
conditions.add(condition);
}
if (theDateRange.getUpperBoundAsInstant() != null) {
BinaryCondition condition = createConditionForValueWithComparator(ParamPrefixEnum.LESSTHAN_OR_EQUALS, resourceTableRoot.getLastUpdatedColumn(), theDateRange.getUpperBoundAsInstant());
conditions.add(condition);
}
return ComboCondition.and(conditions.toArray(new Condition[0]));
}
public void addResourceIdsPredicate(List<Long> thePidList) {
DbColumn resourceIdColumn = getOrCreateFirstPredicateBuilder().getResourceIdColumn();
InCondition predicate = new InCondition(resourceIdColumn, generatePlaceholders(thePidList));
addPredicate(predicate);
}
public BinaryCondition createConditionForValueWithComparator(ParamPrefixEnum theComparator, DbColumn theColumn, Object theValue) {
switch (theComparator) {
case LESSTHAN:
return BinaryCondition.lessThan(theColumn, generatePlaceholder(theValue));
case LESSTHAN_OR_EQUALS:
return BinaryCondition.lessThanOrEq(theColumn, generatePlaceholder(theValue));
case GREATERTHAN:
return BinaryCondition.greaterThan(theColumn, generatePlaceholder(theValue));
case GREATERTHAN_OR_EQUALS:
return BinaryCondition.greaterThanOrEq(theColumn, generatePlaceholder(theValue));
default:
throw new IllegalArgumentException();
}
}
public SearchQueryBuilder newChildSqlBuilder() {
return new SearchQueryBuilder(myFhirContext, myModelConfig, myPartitionSettings, myRequestPartitionId, myResourceType, mySqlBuilderFactory, myBindVariableSubstitutionBase, myDialect, false, myBindVariableValues);
}
public SelectQuery getSelect() {
return mySelect;
}
public boolean haveAtLeastOnePredicate() {
return myHaveAtLeastOnePredicate;
}
public void addSort(DbColumn theColumnValueNormalized, boolean theAscending) {
OrderObject.NullOrder nullOrder = OrderObject.NullOrder.LAST;
addSort(theColumnValueNormalized, theAscending, nullOrder);
}
public void addSort(DbColumn theTheColumnValueNormalized, boolean theTheAscending, OrderObject.NullOrder theNullOrder) {
OrderObject.Dir direction = theTheAscending ? OrderObject.Dir.ASCENDING : OrderObject.Dir.DESCENDING;
OrderObject orderObject = new OrderObject(direction, theTheColumnValueNormalized);
orderObject.setNullOrder(theNullOrder);
mySelect.addCustomOrderings(orderObject);
}
}

View File

@ -0,0 +1,147 @@
package ca.uhn.fhir.jpa.search.builder.sql;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.IoUtil;
import org.apache.commons.lang3.Validate;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import java.io.Closeable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Iterator;
public class SearchQueryExecutor implements Iterator<Long>, Closeable {
private static final Long NO_MORE = -1L;
private static final SearchQueryExecutor NO_VALUE_EXECUTOR = new SearchQueryExecutor();
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private static final Logger ourLog = LoggerFactory.getLogger(SearchQueryExecutor.class);
private final GeneratedSql myGeneratedSql;
private final Integer myMaxResultsToFetch;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager;
private boolean myQueryInitialized;
private Connection myConnection;
private PreparedStatement myStatement;
private ScrollableResultsIterator<Number> myResultSet;
private Long myNext;
/**
* Constructor
*/
public SearchQueryExecutor(GeneratedSql theGeneratedSql, Integer theMaxResultsToFetch) {
Validate.notNull(theGeneratedSql, "theGeneratedSql must not be null");
myGeneratedSql = theGeneratedSql;
myQueryInitialized = false;
myMaxResultsToFetch = theMaxResultsToFetch;
}
/**
* Internal constructor for empty executor
*/
private SearchQueryExecutor() {
assert NO_MORE != null;
myGeneratedSql = null;
myMaxResultsToFetch = null;
myNext = NO_MORE;
}
@Override
public void close() {
IoUtil.closeQuietly(myResultSet);
}
@Override
public boolean hasNext() {
fetchNext();
return !NO_MORE.equals(myNext);
}
@Override
public Long next() {
fetchNext();
Validate.isTrue(hasNext(), "Can not call next() right now, no data remains");
Long next = myNext;
myNext = null;
return next;
}
private void fetchNext() {
if (myNext == null) {
String sql = myGeneratedSql.getSql();
Object[] args = myGeneratedSql.getBindVariables().toArray(EMPTY_OBJECT_ARRAY);
try {
if (!myQueryInitialized) {
/*
* Note that we use the spring managed connection, and the expectation is that a transaction that
* is managed by Spring has been started before this method is called.
*/
assert TransactionSynchronizationManager.isSynchronizationActive();
Query nativeQuery = myEntityManager.createNativeQuery(sql);
org.hibernate.query.Query<?> hibernateQuery = (org.hibernate.query.Query<?>) nativeQuery;
for (int i = 1; i <= args.length; i++) {
hibernateQuery.setParameter(i, args[i - 1]);
}
ourLog.trace("About to execute SQL: {}", sql);
ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
myResultSet = new ScrollableResultsIterator<>(scrollableResults);
myQueryInitialized = true;
}
if (myResultSet == null || !myResultSet.hasNext()) {
myNext = NO_MORE;
} else {
Number next = myResultSet.next();
myNext = next.longValue();
}
} catch (Exception e) {
ourLog.error("Failed to create or execute SQL query", e);
close();
throw new InternalErrorException(e);
}
}
}
public static SearchQueryExecutor emptyExecutor() {
return NO_VALUE_EXECUTOR;
}
}

View File

@ -0,0 +1,110 @@
package ca.uhn.fhir.jpa.search.builder.sql;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SearchParamPresentPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SourcePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TagPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.UriPredicateBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class SqlObjectFactory {
@Autowired
private ApplicationContext myApplicationContext;
public CompositeUniqueSearchParameterPredicateBuilder newCompositeUniqueSearchParameterPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(CompositeUniqueSearchParameterPredicateBuilder.class, theSearchSqlBuilder);
}
public CoordsPredicateBuilder coordsPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(CoordsPredicateBuilder.class, theSearchSqlBuilder);
}
public DatePredicateBuilder dateIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(DatePredicateBuilder.class, theSearchSqlBuilder);
}
public ForcedIdPredicateBuilder newForcedIdPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(ForcedIdPredicateBuilder.class, theSearchSqlBuilder);
}
public NumberPredicateBuilder numberIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(NumberPredicateBuilder.class, theSearchSqlBuilder);
}
public QuantityPredicateBuilder quantityIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(QuantityPredicateBuilder.class, theSearchSqlBuilder);
}
public ResourceLinkPredicateBuilder referenceIndexTable(QueryStack theQueryStack, SearchQueryBuilder theSearchSqlBuilder, boolean theReversed) {
return myApplicationContext.getBean(ResourceLinkPredicateBuilder.class, theQueryStack, theSearchSqlBuilder, theReversed);
}
public ResourceTablePredicateBuilder resourceTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(ResourceTablePredicateBuilder.class, theSearchSqlBuilder);
}
public ResourceIdPredicateBuilder resourceId(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(ResourceIdPredicateBuilder.class, theSearchSqlBuilder);
}
public SearchParamPresentPredicateBuilder searchParamPresentPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(SearchParamPresentPredicateBuilder.class, theSearchSqlBuilder);
}
public StringPredicateBuilder stringIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(StringPredicateBuilder.class, theSearchSqlBuilder);
}
public TokenPredicateBuilder tokenIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(TokenPredicateBuilder.class, theSearchSqlBuilder);
}
public UriPredicateBuilder uriIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(UriPredicateBuilder.class, theSearchSqlBuilder);
}
public TagPredicateBuilder newTagPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(TagPredicateBuilder.class, theSearchSqlBuilder);
}
public SourcePredicateBuilder newSourcePredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(SourcePredicateBuilder.class, theSearchSqlBuilder);
}
public SearchQueryExecutor newSearchQueryExecutor(GeneratedSql theGeneratedSql, Integer theMaxResultsToFetch) {
return myApplicationContext.getBean(SearchQueryExecutor.class, theGeneratedSql, theMaxResultsToFetch);
}
}

View File

@ -20,7 +20,8 @@ package ca.uhn.fhir.jpa.util;
* #L%
*/
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import java.util.List;
import java.util.function.Consumer;

View File

@ -26,6 +26,7 @@ import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.trim;

View File

@ -21,13 +21,13 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class SearchBuilderTest {
public class LegacySearchBuilderTest {
@Test
public void testIncludeIterator() {
BaseHapiFhirDao<?> mockDao = mock(BaseHapiFhirDao.class);
SearchBuilder searchBuilder = new SearchBuilder(mockDao, null, null);
LegacySearchBuilder searchBuilder = new LegacySearchBuilder(mockDao, null, null);
searchBuilder.setDaoConfigForUnitTest(new DaoConfig());
searchBuilder.setParamsForUnitTest(new SearchParameterMap());
EntityManager mockEntityManager = mock(EntityManager.class);
@ -46,7 +46,7 @@ public class SearchBuilderTest {
resultList.add(link);
when(mockQuery.getResultList()).thenReturn(resultList);
SearchBuilder.IncludesIterator includesIterator = searchBuilder.new IncludesIterator(pidSet, null);
LegacySearchBuilder.IncludesIterator includesIterator = searchBuilder.new IncludesIterator(pidSet, null);
// hasNext() should return false if the pid added was already on our list going in.
assertFalse(includesIterator.hasNext());
}

View File

@ -493,14 +493,16 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
assertThat(foundResources, contains(p2id.getValue()));
// Search by chain
map = new SearchParameterMap();
myCaptureQueriesListener.clear();
map = new SearchParameterMap().setLoadSynchronous(true);
map.add("sibling", new ReferenceParam("name", "P1"));
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(foundResources, contains(p2id.getValue()));
// Search by two level chain
map = new SearchParameterMap();
map = new SearchParameterMap().setLoadSynchronous(true);
map.add("patient", new ReferenceParam("sibling.name", "P1"));
results = myAppointmentDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
@ -1031,7 +1033,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
myPatientDao.search(map).size();
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, careprovider, deathdate, deceased, email, family, gender, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, careprovider, deathdate, deceased, email, family, gender, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
@ -1068,7 +1070,7 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
myPatientDao.search(map).size();
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, careprovider, deathdate, deceased, email, family, gender, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, careprovider, deathdate, deceased, email, family, gender, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
// Try with normal gender SP

View File

@ -11,7 +11,6 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.TransactionStatus;
@ -24,7 +23,6 @@ import ca.uhn.fhir.model.primitive.Base64BinaryDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.util.TestUtil;
public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
@ -360,7 +358,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId4, devId1));
assertThat(actual, containsInAnyOrder(ptId2, ptId1, obsId1, obsId4, devId1));
/*
* Make one previous match no longer match

View File

@ -37,7 +37,6 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

View File

@ -69,14 +69,12 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -1600,7 +1598,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
found = toList(myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_BIRTHDATE + "AAAA", new DateParam(ParamPrefixEnum.GREATERTHAN, "2000-01-01"))));
assertEquals(0, found.size());
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"birthdateAAAA\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, careprovider, deathdate, deceased, email, family, gender, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"birthdateAAAA\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, careprovider, deathdate, deceased, email, family, gender, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
@ -1629,13 +1627,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myObservationDao.create(obs, mySrd);
List<Observation> found = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("value-quantity", new QuantityDt(111))));
List<Observation> found = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("value-quantity", new QuantityParam(111))));
assertEquals(1, found.size());
found = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("value-quantity", new QuantityDt(112))));
found = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("value-quantity", new QuantityParam(112))));
assertEquals(0, found.size());
found = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("value-quantity", new QuantityDt(212))));
found = toList(myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add("value-quantity", new QuantityParam(212))));
assertEquals(0, found.size());
}
@ -1665,7 +1663,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
// found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "F"));
// assertEquals(0, found.size());
SearchParameterMap map = new SearchParameterMap();
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "001testPersistSearchParams"));
map.add(Patient.SP_GENDER, new IdentifierDt("urn:some:wrong:system", AdministrativeGenderEnum.MALE.getCode()));
found = toList(myPatientDao.search(map));
@ -2112,14 +2110,14 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
pm.setSort(new SortSpec(Patient.SP_BIRTHDATE));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate"));
pm.setSort(new SortSpec(Patient.SP_BIRTHDATE).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate"));
@ -2164,21 +2162,21 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
pm.setSort(new SortSpec(BaseResource.SP_RES_ID));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(id1, id2, id3, id4, idMethodName));
assertThat(actual, contains(idMethodName, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(BaseResource.SP_RES_ID).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(id1, id2, id3, id4, idMethodName));
assertThat(actual, contains(idMethodName, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(BaseResource.SP_RES_ID).setOrder(SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(idMethodName, id4, id3, id2, id1));
assertThat(actual, contains(id4, id3, id2, id1, idMethodName));
}
@Test
@ -2404,14 +2402,14 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
pm.setSort(new SortSpec(Patient.SP_FAMILY));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));
pm.setSort(new SortSpec(Patient.SP_FAMILY).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));

View File

@ -49,6 +49,7 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@ -429,7 +430,14 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, request);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
runInTransaction(()->{
ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(), "Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");

View File

@ -1015,7 +1015,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
myPatientDao.search(map).size();
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
@ -1053,7 +1053,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
myPatientDao.search(map).size();
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
// Try with normal gender SP

View File

@ -457,7 +457,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1)));
assertThat(actual, containsInAnyOrder(toValues(ptId1, ptId2, obsId1, obsId4, devId1)));
/*
* Make one previous match no longer match

View File

@ -92,7 +92,6 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -109,6 +108,7 @@ import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
@ -1424,9 +1424,11 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
assertThat(patients, hasItems(id1a, id1b, id2));
}
{
SearchParameterMap params = new SearchParameterMap();
SearchParameterMap params = SearchParameterMap.newSynchronous();
params.setLastUpdated(new DateRangeParam(beforeR2, null));
myCaptureQueriesListener.clear();
List<IIdType> patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(patients, hasItems(id2));
assertThat(patients, not(hasItems(id1a, id1b)));
}
@ -3287,7 +3289,9 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
TokenParam param = new TokenParam();
param.setMissing(false);
params.add(Observation.SP_CODE, param);
myCaptureQueriesListener.clear();
List<IIdType> patients = toUnqualifiedVersionlessIds(myObservationDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(patients, not(containsInRelativeOrder(missing)));
assertThat(patients, containsInRelativeOrder(notMissing));
}
@ -3458,7 +3462,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
map = new SearchParameterMap();
map.setSort(new SortSpec("_id", SortOrderEnum.ASC));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains(id1, id2, "Patient/AA", "Patient/AB"));
assertThat(ids, contains("Patient/AA", "Patient/AB", id1, id2));
}
@ -3614,7 +3618,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
map = new SearchParameterMap();
map.setSort(new SortSpec(Patient.SP_FAMILY, SortOrderEnum.ASC).setChain(new SortSpec(Patient.SP_GIVEN, SortOrderEnum.ASC)));
ids = toUnqualifiedVersionlessIds(myPatientDao.search(map));
assertThat(ids.toString(), ids, contains(pid1, pid2, pid3, pid4, pid5));
assertThat(ids.toString(), ids, contains(pid2, pid4, pid5, pid3, pid1));
assertEquals(5, ids.size());
}
@ -3670,7 +3674,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
map.setSort(new SortSpec("gender").setChain(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
ourLog.info("IDS: {}", ids);
assertThat(ids.toString(), ids, contains("Patient/CA", "Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
assertThat(ids.toString(), ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB", "Patient/CA"));
map = new SearchParameterMap();
map.add(Patient.SP_ACTIVE, new TokenParam(null, "true"));

View File

@ -30,7 +30,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
@ -81,7 +80,6 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -2025,7 +2023,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE + "AAAA", new DateParam(ParamPrefixEnum.GREATERTHAN, "2000-01-01")).setLoadSynchronous(true)));
assertEquals(0, found.size());
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"birthdateAAAA\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"birthdateAAAA\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, animal-breed, animal-species, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
@ -2600,14 +2598,14 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
pm.setSort(new SortSpec(Patient.SP_BIRTHDATE));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate"));
pm.setSort(new SortSpec(Patient.SP_BIRTHDATE).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate"));
@ -2652,21 +2650,21 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
pm.setSort(new SortSpec(IAnyResource.SP_RES_ID));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual.toString(), actual, contains(id1, id2, id3, id4, idMethodName));
assertThat(actual.toString(), actual, contains(idMethodName, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual.toString(), actual, contains(id1, id2, id3, id4, idMethodName));
assertThat(actual.toString(), actual, contains(idMethodName, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual.toString(), actual, contains(idMethodName, id4, id3, id2, id1));
assertThat(actual.toString(), actual, contains(id4, id3, id2, id1, idMethodName));
}
@Test
@ -2891,14 +2889,14 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
pm.setSort(new SortSpec(Patient.SP_FAMILY));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));
pm.setSort(new SortSpec(Patient.SP_FAMILY).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.CoordCalculatorTest;
import ca.uhn.fhir.jpa.util.SearchBox;
@ -27,13 +27,13 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class PredicateBuilderCoordsTest {
PredicateBuilderCoords myPredicateBuilderCoords;
private SearchBuilder mySearchBuilder;
private LegacySearchBuilder mySearchBuilder;
private CriteriaBuilder myBuilder;
private From myFrom;
@BeforeEach
public void before() {
mySearchBuilder = mock(SearchBuilder.class);
mySearchBuilder = mock(LegacySearchBuilder.class);
myBuilder = mock(CriteriaBuilder.class);
myFrom = mock(From.class);
myPredicateBuilderCoords = new PredicateBuilderCoords(mySearchBuilder);

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilderTest;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilderTest;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
public class SearchFuzzUtilTest {
private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilderTest.class);
private static final Logger ourLog = LoggerFactory.getLogger(LegacySearchBuilderTest.class);
@Test
public void testCalculateMultiplierEqualNoDecimal() {

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
@ -72,6 +73,7 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
@ -204,6 +206,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao;
@Autowired
protected IResourceIndexedSearchParamCoordsDao myResourceIndexedSearchParamCoordsDao;
@Autowired
protected IResourceIndexedSearchParamQuantityDao myResourceIndexedSearchParamQuantityDao;
@Autowired
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@ -458,6 +462,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
private IdHelperService myIdHelperService;
@Autowired
protected IBatchJobSubmitter myBatchJobSubmitter;
@Autowired
protected ValidationSettings myValidationSettings;
@AfterEach()
public void afterCleanupDao() {
@ -532,6 +538,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
myDaoConfig.setHardTagListLimit(1000);
myDaoConfig.setIncludeLimit(2000);
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
myValidationSettings.setLocalReferenceValidationDefaultPolicy(new ValidationSettings().getLocalReferenceValidationDefaultPolicy());
}
@Override

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.IResourceProvenanceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
@ -15,21 +15,29 @@ import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.RiskAssessment;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterAll;
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.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@SuppressWarnings({"Duplicates"})
public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4FilterTest.class);
@Autowired
private IResourceProvenanceDao myResourceProvenanceDao;
@AfterEach
public void after() {
myDaoConfig.setFilterParameterEnabled(new DaoConfig().isFilterParameterEnabled());
@ -103,7 +111,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name eq smi"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
@ -162,6 +170,38 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
}
@Test
public void testSourceComparatorEq() {
myDaoConfig.setStoreMetaSourceInformation(DaoConfig.StoreMetaSourceInformationEnum.SOURCE_URI_AND_REQUEST_ID);
Patient p = new Patient();
p.getMeta().setSource("http://source");
p.addName().setFamily("Smith").addGiven("John");
p.setActive(true);
String ptId = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
p = new Patient();
p.addName().setFamily("Smith").addGiven("John2");
p.setActive(true);
myPatientDao.create(p).getId().toUnqualifiedVersionless();
SearchParameterMap map;
List<String> found;
runInTransaction(() -> {
ourLog.info("Provenance:\n * {}", myResourceProvenanceDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("_source eq http://source"));
myCaptureQueriesListener.clear();
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(found, containsInAnyOrder(ptId));
}
@Test
public void testFilterDisabled() {
myDaoConfig.setFilterParameterEnabled(false);
@ -176,6 +216,9 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
}
}
/**
* Use the Encounter DAO to find things that would match a Patient
*/
@Test
public void testRetrieveDifferentTypeEq() {
@ -190,24 +233,29 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq active or _id eq %s",
idVal)));
map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq active or _id eq %s", idVal)));
myCaptureQueriesListener.clear();
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
assertThat(found, Matchers.empty());
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s",
idVal)));
map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq inactive or _id eq %s", idVal)));
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s",
idVal)));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, containsInAnyOrder(id1));
map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq inactive or _id eq Patient/FOO")));
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", idVal)));
found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map));
assertThat(found, empty());
}
@ -272,9 +320,10 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
CarePlan cp = new CarePlan();
cp.getSubject().setReference(ptId.getValue());
String cpId = myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue();
myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue();
cp = new CarePlan();
cp.getSubject().setReference(ptId2.getValue());
cp.addActivity().getDetail().addPerformer().setReference(ptId2.getValue());
String cpId2 = myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue();
@ -284,23 +333,43 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("subject ne " + ptId.getValue()));
myCaptureQueriesListener.clear();
found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map));
assertThat(found, containsInAnyOrder(cpId2));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("subject ne " + ptId.getIdPart()));
found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(found, containsInAnyOrder(cpId2));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("(subject ne " + ptId.getIdPart() + ") and (performer ne " + ptId2.getValue() + ")"));
found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@Test
public void testLanguageComparatorEq() {
Patient p = new Patient();
p.setLanguage("en");
p.addName().setFamily("Smith").addGiven("John");
p.setBirthDateElement(new DateType("1955-01-01"));
p.setActive(true);
String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("_language eq en"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, containsInAnyOrder(id1));
}
@Test
public void testStringComparatorCo() {
@ -369,7 +438,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name sw mi"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
@ -405,7 +474,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name ew it"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
@ -447,7 +516,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("given gt john"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -483,7 +552,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("given lt frank"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -506,7 +575,9 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("family ge jones"));
myCaptureQueriesListener.clear();
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(found, containsInAnyOrder(id1, id2));
map = new SearchParameterMap();
@ -525,7 +596,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("given ge jon"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -561,7 +632,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("family le jackson"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -601,7 +672,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("birthdate ne 1955-01-01"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
@ -633,7 +704,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("birthdate gt 1955-01-01"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -659,7 +730,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("birthdate lt 1955-01-01"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -691,7 +762,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("birthdate ge 1955-01-02"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -717,7 +788,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("birthdate le 1954-12-31"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
@ -764,7 +835,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability eq 0.1"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -840,7 +911,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability gt 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -875,7 +946,7 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability lt 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, Matchers.empty());
assertThat(found, empty());
}
@ -961,17 +1032,17 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url eq http://hl7.org/foo/baz")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url eq http://hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url eq http://hl7.org/foo/bar/bar")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@ -987,17 +1058,17 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ne http://hl7.org/foo/baz")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ne http://hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ne http://hl7.org/foo/baz and url ne http://hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@ -1013,17 +1084,17 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url co http://hl7.org/foo")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1, vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url co baz")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url co http://hl7.org/foo/bat")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@ -1039,17 +1110,17 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url gt http://hl7.org/foo")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1, vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url gt http://hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url gt http://hl7.org/foo/baza")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@ -1065,15 +1136,15 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url lt http://hl7.org/foo")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url lt http://hl7.org/foo/baz")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url lt http://hl7.org/foo/bara")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId2));
@ -1091,13 +1162,13 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ge http://hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1, vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ge http://hl7.org/foo/baza")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@ -1113,15 +1184,15 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url le http://hl7.org/foo/baz")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1, vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url le http://hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url lt http://hl7.org/foo/baza")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1, vsId2));
@ -1139,13 +1210,13 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url sw http://hl7.org")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1, vsId2));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url sw hl7.org/foo/bar")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@ -1161,15 +1232,27 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
IIdType vsId2 = myValueSetDao.create(vs2, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider result;
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ew baz")));
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(vsId1));
result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Constants.PARAM_FILTER,
result = myValueSetDao.search(SearchParameterMap.newSynchronous().add(Constants.PARAM_FILTER,
new StringParam("url ew ba")));
assertThat(toUnqualifiedVersionlessIds(result), Matchers.empty());
assertThat(toUnqualifiedVersionlessIds(result), empty());
}
@Test
public void testUnknownSearchParam() {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("foo eq smith"));
try {
myPatientDao.search(map);
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
}

View File

@ -535,7 +535,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(1, StringUtils.countMatches(myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true).toLowerCase(), "join"));
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true).toLowerCase();
assertEquals(2, StringUtils.countMatches(sql, "join"), sql);
}

View File

@ -70,11 +70,13 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
@ -807,27 +809,45 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
String sql;
// Search by ref
map = new SearchParameterMap();
map = SearchParameterMap.newSynchronous();
map.add("sibling", new ReferenceParam(p1id.getValue()));
myCaptureQueriesListener.clear();
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, contains(p2id.getValue()));
sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(sql, countMatches(sql, "JOIN"), equalTo(1));
assertThat(sql, countMatches(sql, "t0.SRC_PATH = 'Patient.extension('http://acme.org/sibling')'"), equalTo(1));
assertThat(sql, countMatches(sql, "t0.TARGET_RESOURCE_ID = '"), equalTo(1));
// Search by chain
map = new SearchParameterMap();
map = SearchParameterMap.newSynchronous();
map.add("sibling", new ReferenceParam("name", "P1"));
myCaptureQueriesListener.clear();
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, contains(p2id.getValue()));
sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(sql, countMatches(sql, "JOIN"), equalTo(2));
assertThat(sql, countMatches(sql, "SRC_PATH = 'Patient.extension('http://acme.org/sibling')'"), equalTo(1));
assertThat(sql, countMatches(sql, "HASH_NORM_PREFIX = '"), equalTo(39));
assertThat(sql, countMatches(sql, "SP_VALUE_NORMALIZED LIKE "), equalTo(39));
// Search by two level chain
map = new SearchParameterMap();
map = SearchParameterMap.newSynchronous();
map.add("patient", new ReferenceParam("sibling.name", "P1"));
myCaptureQueriesListener.clear();
results = myAppointmentDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, containsInAnyOrder(appid.getValue()));
sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(sql, countMatches(sql, "JOIN"), equalTo(3));
assertThat(sql, countMatches(sql, "SRC_PATH = 'Appointment.participant.actor.where(resolve() is Patient)'"), equalTo(1));
assertThat(sql, countMatches(sql, "SRC_PATH = 'Patient.extension('http://acme.org/sibling')'"), equalTo(1));
assertThat(sql, countMatches(sql, "SP_VALUE_NORMALIZED LIKE 'P1%'"), equalTo(39));
}
@ -1426,7 +1446,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
myPatientDao.search(map).size();
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
@ -1464,7 +1484,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
myPatientDao.search(map).size();
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
// Try with normal gender SP

View File

@ -451,7 +451,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1)));
assertThat(actual, containsInAnyOrder(toValues(ptId1, ptId2, obsId1, obsId4, devId1)));
/*
* Make one previous match no longer match

View File

@ -1,9 +1,9 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
@ -66,7 +66,7 @@ public class FhirResourceDaoR4SearchLastNAsyncIT extends BaseR4SearchLastN {
@Test
public void testLastNChunking() {
runInTransaction(()->{
runInTransaction(() -> {
for (Search search : mySearchDao.findAll()) {
mySearchDao.updateDeleted(search.getId(), true);
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
@ -64,25 +65,25 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseR4SearchLastN {
assertEquals(4, queries.size());
// The first and third chunked queries should have a full complement of PIDs
StringBuilder firstQueryPattern = new StringBuilder(".*RES_ID in \\('[0-9]+'");
StringBuilder firstQueryPattern = new StringBuilder(".*RES_ID IN \\('[0-9]+'");
for (int pidIndex = 1; pidIndex < 50; pidIndex++) {
firstQueryPattern.append(" , '[0-9]+'");
firstQueryPattern.append(",'[0-9]+'");
}
firstQueryPattern.append("\\).*");
assertThat(queries.get(0), matchesPattern(firstQueryPattern.toString()));
assertThat(queries.get(2), matchesPattern(firstQueryPattern.toString()));
assertThat(queries.get(0).toUpperCase().replaceAll(" , ", ","), matchesPattern(firstQueryPattern.toString()));
assertThat(queries.get(2).toUpperCase().replaceAll(" , ", ","), matchesPattern(firstQueryPattern.toString()));
// the second and fourth chunked queries should be padded with "-1".
StringBuilder secondQueryPattern = new StringBuilder(".*RES_ID in \\('[0-9]+'");
StringBuilder secondQueryPattern = new StringBuilder(".*RES_ID IN \\('[0-9]+'");
for (int pidIndex = 1; pidIndex < 25; pidIndex++) {
secondQueryPattern.append(" , '[0-9]+'");
secondQueryPattern.append(",'[0-9]+'");
}
for (int pidIndex = 0; pidIndex < 25; pidIndex++) {
secondQueryPattern.append(" , '-1'");
secondQueryPattern.append(",'-1'");
}
secondQueryPattern.append("\\).*");
assertThat(queries.get(1), matchesPattern(secondQueryPattern.toString()));
assertThat(queries.get(3), matchesPattern(secondQueryPattern.toString()));
assertThat(queries.get(1).toUpperCase().replaceAll(" , ", ","), matchesPattern(secondQueryPattern.toString()));
assertThat(queries.get(3).toUpperCase().replaceAll(" , ", ","), matchesPattern(secondQueryPattern.toString()));
}

View File

@ -17,6 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@ -31,11 +32,24 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
}
@Test
public void testIndexMissingFieldsDisabledDontAllowInSearch() {
public void testIndexMissingFieldsDisabledDontAllowInSearch_NonReference() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
SearchParameterMap params = new SearchParameterMap();
params.add("foo", new StringParam().setMissing(true));
params.add(Patient.SP_ACTIVE, new StringParam().setMissing(true));
try {
myPatientDao.search(params);
} catch (MethodNotAllowedException e) {
assertEquals(":missing modifier is disabled on this server", e.getMessage());
}
}
@Test
public void testIndexMissingFieldsDisabledDontAllowInSearch_Reference() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
SearchParameterMap params = new SearchParameterMap();
params.add(Patient.SP_ORGANIZATION, new StringParam().setMissing(true));
try {
myPatientDao.search(params);
} catch (MethodNotAllowedException e) {
@ -162,6 +176,39 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
}
}
@Test
public void testSearchWithMissingCoords() {
String locId = myLocationDao.create(new Location(), mySrd).getId().toUnqualifiedVersionless().getValue();
String locId2 = myLocationDao.create(new Location().setPosition(new Location.LocationPositionComponent(new DecimalType(10), new DecimalType(10))), mySrd).getId().toUnqualifiedVersionless().getValue();
runInTransaction(()->{
ourLog.info("Coords:\n * {}", myResourceIndexedSearchParamCoordsDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
{
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
TokenParam param = new TokenParam();
param.setMissing(true);
params.add(Location.SP_NEAR, param);
myCaptureQueriesListener.clear();
List<String> patients = toUnqualifiedVersionlessIdValues(myLocationDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(patients, containsInRelativeOrder(locId));
assertThat(patients, not(containsInRelativeOrder(locId2)));
}
{
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
TokenParam param = new TokenParam();
param.setMissing(false);
params.add(Location.SP_NEAR, param);
List<String> patients = toUnqualifiedVersionlessIdValues(myLocationDao.search(params));
assertThat(patients, containsInRelativeOrder(locId2));
assertThat(patients, not(containsInRelativeOrder(locId)));
}
}
@Test
public void testSearchWithMissingDate2() {
MedicationRequest mr1 = new MedicationRequest();

View File

@ -121,7 +121,6 @@ import org.hl7.fhir.r4.model.Substance;
import org.hl7.fhir.r4.model.Task;
import org.hl7.fhir.r4.model.Timing;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -148,6 +147,7 @@ import java.util.TreeSet;
import java.util.stream.Collectors;
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
@ -206,7 +206,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(StructureDefinition.SP_VALUESET, new ReferenceParam("http://foo2"));
myCaptureQueriesListener.clear();
List<String> ids = toUnqualifiedVersionlessIdValues(myStructureDefinitionDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(ids, empty());
}
}
@ -415,8 +417,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
searchSql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertEquals(0, StringUtils.countMatches(searchSql, "RES_DELETED_AT"));
assertEquals(0, StringUtils.countMatches(searchSql, "RES_TYPE"));
assertEquals(0, countMatches(searchSql, "RES_DELETED_AT"));
assertEquals(0, countMatches(searchSql, "RES_TYPE"));
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc1Id, enc2Id));
@ -478,9 +480,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Patient").setChain(PARAM_TYPE));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, contains(enc1Id));
assertThat(ids.toString(), ids, contains(enc1Id));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
map = new SearchParameterMap();
@ -540,8 +544,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
map.add(DiagnosticReport.SP_PERFORMER, new ReferenceParam("CareTeam").setChain(PARAM_TYPE));
results = myDiagnosticReportDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids.toString(), ids, contains(drId1.getValue()));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids.toString(), ids, contains(drId1.getValue()));
}
@ -558,10 +562,18 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
ma.setMedication(new Reference(medId));
IIdType moId = myMedicationAdministrationDao.create(ma).getId().toUnqualified();
SearchParameterMap map = new SearchParameterMap();
runInTransaction(() -> {
ourLog.info("Resource Links:\n * {}", myResourceLinkDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
ourLog.info("Token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(MedicationAdministration.SP_MEDICATION, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().add(new ReferenceParam("code", "04823543"))));
myCaptureQueriesListener.clear();
IBundleProvider results = myMedicationAdministrationDao.search(map);
List<String> ids = toUnqualifiedIdValues(results);
myCaptureQueriesListener.logSelectQueries();
assertThat(ids, contains(moId.getValue()));
}
@ -739,23 +751,41 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
pat.getManagingOrganization().setReferenceElement(orgId);
IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless();
Patient pat2 = new Patient();
pat2.addAddress().addLine(methodName + "2");
pat2.getManagingOrganization().setReferenceElement(orgId);
IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless();
MedicationRequest mo = new MedicationRequest();
mo.getSubject().setReferenceElement(patId);
mo.setMedication(new Reference(medId));
IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless();
HttpServletRequest request = mock(HttpServletRequest.class);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2));
// Nothing links to this one
Patient pat2 = new Patient();
pat2.addAddress().addLine(methodName + "2");
pat2.getManagingOrganization().setReferenceElement(orgId);
IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless();
runInTransaction(()->{
ourLog.info("Links:\n * {}", myResourceLinkDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
// All patient IDs
HttpServletRequest request = mock(HttpServletRequest.class);
myCaptureQueriesListener.clear();
myCaptureQueriesListener.setCaptureQueryStackTrace(true);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, null, mySrd);
List<IIdType> actual = toUnqualifiedVersionlessIds(resp);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(actual, containsInAnyOrder(orgId, medId, patId, moId, patId2));
assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Specific patient ID with linked stuff
request = mock(HttpServletRequest.class);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId));
// Specific patient ID with no linked stuff
request = mock(HttpServletRequest.class);
resp = myPatientDao.patientInstanceEverything(request, patId2, null, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(patId2, orgId));
}
/**
@ -921,7 +951,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
myCaptureQueriesListener.clear();
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
// No targets exist
params = new SearchParameterMap();
@ -1385,6 +1417,37 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testSearchByIdParamInverse() {
String id1;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
}
String id2;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("002");
id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
}
SearchParameterMap params;
// inverse
params = SearchParameterMap.newSynchronous();
params.add("_id", new TokenParam(id1).setModifier(TokenParamModifier.NOT));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id2));
// Non-inverse
params = SearchParameterMap.newSynchronous();
params.add("_id", new TokenParam(id1));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
}
@Test
public void testSearchByIdParam_QueryIsMinimal() {
// With only an _id parameter
@ -1398,11 +1461,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertEquals(1, selectQueries.size());
String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase();
ourLog.debug("SQL Query:\n{}", sqlQuery);
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_id in"));
assertEquals(0, StringUtils.countMatches(sqlQuery, "join"));
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_type='diagnosticreport'"));
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_deleted_at is null"));
ourLog.info("SQL Query:\n{}", sqlQuery);
assertEquals(1, countMatches(sqlQuery, "res_id = '123'"), sqlQuery);
assertEquals(0, countMatches(sqlQuery, "join"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "res_type = 'diagnosticreport'"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "res_deleted_at is null"), sqlQuery);
}
// With an _id parameter and a standard search param
{
@ -1417,11 +1480,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase();
ourLog.info("SQL Query:\n{}", sqlQuery);
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_id in"));
assertEquals(1, StringUtils.countMatches(sqlQuery, "join"));
assertEquals(1, StringUtils.countMatches(sqlQuery, "hash_sys_and_value"));
assertEquals(0, StringUtils.countMatches(sqlQuery, "diagnosticreport"));
assertEquals(0, StringUtils.countMatches(sqlQuery, "res_deleted_at"));
assertEquals(1, countMatches(sqlQuery, "res_id = '123'"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "join"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "hash_sys_and_value"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "res_type = 'diagnosticreport"), sqlQuery); // could be 0
assertEquals(1, countMatches(sqlQuery, "res_deleted_at"), sqlQuery); // could be 0
}
}
@ -1438,10 +1501,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase();
ourLog.info("SQL Query:\n{}", sqlQuery);
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_id in"));
assertEquals(0, StringUtils.countMatches(sqlQuery, "join"));
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_type='diagnosticreport'"));
assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_deleted_at is null"));
assertEquals(1, countMatches(sqlQuery, "res_id = '123'"), sqlQuery);
assertEquals(0, countMatches(sqlQuery, "join"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "res_type = 'diagnosticreport'"), sqlQuery);
assertEquals(1, countMatches(sqlQuery, "res_deleted_at is null"), sqlQuery);
}
@Test
@ -1526,10 +1589,14 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
// With lastupdated
params = new SearchParameterMap();
params = SearchParameterMap.newSynchronous();
params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart())));
params.setLastUpdated(new DateRangeParam(new Date(betweenTime), null));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id2));
myCaptureQueriesListener.clear();
IBundleProvider search = myPatientDao.search(params);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(toUnqualifiedVersionlessIds(search).toString(), toUnqualifiedVersionlessIds(search), containsInAnyOrder(id2));
}
@ -1706,7 +1773,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 150, "http://bar", "code1");
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, val);
myCaptureQueriesListener.clear();
IBundleProvider result = myObservationDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue()));
}
{
@ -2171,6 +2240,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
SearchParameterMap params;
List result;
params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("_id", new StringParam("TEST"));
@ -2179,7 +2250,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("_language", new StringParam("TEST"));
assertEquals(1, toList(myPatientDao.search(params)).size());
myCaptureQueriesListener.clear();
result = toList(myPatientDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertEquals(1, result.size());
params = new SearchParameterMap();
params.setLoadSynchronous(true);
@ -2249,7 +2323,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
params.setLoadSynchronous(true);
params.add(IAnyResource.SP_RES_LANGUAGE, new StringParam("en_CA"));
myCaptureQueriesListener.clear();
List<IBaseResource> patients = toList(myPatientDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertEquals(1, patients.size());
assertEquals(id1.toUnqualifiedVersionless(), patients.get(0).getIdElement().toUnqualifiedVersionless());
}
@ -2668,7 +2744,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
e2.addPrediction().setProbability(new DecimalType(4));
IIdType id2 = myRiskAssessmentDao.create(e2, mySrd).getId();
{
myCaptureQueriesListener.clear();
IBundleProvider found = myRiskAssessmentDao.search(new SearchParameterMap().setLoadSynchronous(true).add(RiskAssessment.SP_PROBABILITY, new NumberParam(">2")));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, found.size().intValue());
assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1.toUnqualifiedVersionless(), id2.toUnqualifiedVersionless()));
}
@ -3037,8 +3115,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
List<IIdType> result;
SearchParameterMap params;
myCaptureQueriesListener.clear();
result = toUnqualifiedVersionlessIds(myObservationDao
.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypesXX"))));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(result, containsInAnyOrder(obsId01));
assertEquals(1, result.size());
@ -3148,24 +3228,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testSearchStringParamWithLike() {
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringOrListParam().addOr(new StringParam("AAA")).addOr(new StringParam("BBB")));
map.setLoadSynchronous(true);
myPatientDao.search(map);
List<String> queries = myCaptureQueriesListener
.getCapturedQueries()
.stream()
.map(t -> t.getSql(true, true))
.filter(t -> t.contains("select"))
.collect(Collectors.toList());
ourLog.info("Queries:\n " + queries.stream().collect(Collectors.joining("\n ")));
}
@Test
public void testSearchTokenListLike() {
@ -3193,8 +3255,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
.collect(Collectors.toList());
String resultingQueryNotFormatted = queries.get(0);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "HASH_VALUE"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, containsString("HASH_VALUE in ('3140583648400062149' , '4929264259256651518')"));
assertEquals(1, countMatches(resultingQueryNotFormatted, "HASH_VALUE"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, containsString("HASH_VALUE IN ('3140583648400062149','4929264259256651518')"));
// Ensure that the search actually worked
assertEquals(2, search.size().intValue());
@ -3229,8 +3291,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
.collect(Collectors.toList());
String resultingQueryNotFormatted = queries.get(0);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "HASH_VALUE"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, containsString("HASH_VALUE in ('3140583648400062149' , '4929264259256651518')"));
assertEquals(2, countMatches(resultingQueryNotFormatted, "HASH_VALUE"), resultingQueryNotFormatted);
assertEquals(1, countMatches(resultingQueryNotFormatted, "HASH_SYS"), resultingQueryNotFormatted);
// Ensure that the search actually worked
assertEquals(3, search.size().intValue());
@ -3394,8 +3456,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
String id2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless().getValue();
{
myCaptureQueriesListener.clear();
IBundleProvider found = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Patient.SP_FAMILY, new StringParam("AAA")));
assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(toUnqualifiedVersionlessIdValues(found).toString(), toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1));
assertEquals(1, found.size().intValue());
}
{
@ -3443,8 +3507,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
.collect(Collectors.toList());
String searchQuery = queries.get(0);
assertEquals(3, StringUtils.countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"), searchQuery);
assertEquals(4, StringUtils.countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"), searchQuery);
assertEquals(3, countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"), searchQuery);
assertEquals(5, countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"), searchQuery);
}
@ -3468,9 +3532,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
.collect(Collectors.toList());
String searchQuery = queries.get(0);
assertEquals(1, StringUtils.countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"), searchQuery);
assertEquals(1, StringUtils.countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"), searchQuery);
assertEquals(2, StringUtils.countMatches(searchQuery.toUpperCase(), "AND RESOURCETA0_.RES_UPDATED"), searchQuery);
assertEquals(1, countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"), searchQuery);
assertEquals(2, countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"), searchQuery);
assertEquals(2, countMatches(searchQuery.toUpperCase(), "RES_UPDATED"), searchQuery);
}
@Disabled
@ -3665,20 +3729,33 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myPatientDao.create(patient, mySrd);
patient = new Patient();
patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam002");
patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam003");
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
myPatientDao.create(patient, mySrd);
patient = new Patient();
patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam004");
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
myPatientDao.create(patient, mySrd);
runInTransaction(()->{
ourLog.info("Token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().filter(t->t.getParamName().equals("identifier")).map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
{
SearchParameterMap map = new SearchParameterMap();
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", null));
myCaptureQueriesListener.clear();
IBundleProvider retrieved = myPatientDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertEquals(2, retrieved.size().intValue());
}
{
SearchParameterMap map = new SearchParameterMap();
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", ""));
myCaptureQueriesListener.clear();
IBundleProvider retrieved = myPatientDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertEquals(2, retrieved.size().intValue());
}
}
@ -3704,20 +3781,69 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
female = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
}
runInTransaction(()->{
ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
List<String> patients;
SearchParameterMap params;
// Yes match - one value
params = new SearchParameterMap();
params.add(Patient.SP_GENDER, new TokenParam(null, "male"));
params.setLoadSynchronous(true);
patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params));
assertThat(patients, contains(male));
// Yes match - two values
params = new SearchParameterMap();
params.add(Patient.SP_GENDER, new TokenOrListParam()
.addOr(new TokenParam(null, "male"))
.addOr(new TokenParam(null, "blah"))
);
params.setLoadSynchronous(true);
patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params));
assertThat(patients, contains(male));
// Yes match - two values with different specificities
params = new SearchParameterMap();
params.add(Patient.SP_GENDER, new TokenOrListParam()
.addOr(new TokenParam(null, "male"))
.addOr(new TokenParam("http://help-im-a-bug", "blah"))
);
params.setLoadSynchronous(true);
patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params));
assertThat(patients, contains(male));
// No match - one value
params = new SearchParameterMap();
params.add(Patient.SP_GENDER, new TokenParam(null, "male").setModifier(TokenParamModifier.NOT));
params.setLoadSynchronous(true);
myCaptureQueriesListener.clear();
patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(patients, contains(female));
// No match - two values
params = new SearchParameterMap();
params.add(Patient.SP_GENDER, new TokenOrListParam()
.addOr(new TokenParam(null, "male").setModifier(TokenParamModifier.NOT))
.addOr(new TokenParam(null, "blah").setModifier(TokenParamModifier.NOT))
);
params.setLoadSynchronous(true);
patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params));
assertThat(patients, contains(female));
// No match - two values with different specificities
params = new SearchParameterMap();
params.add(Patient.SP_GENDER, new TokenOrListParam()
.addOr(new TokenParam(null, "male").setModifier(TokenParamModifier.NOT))
.addOr(new TokenParam("http://help-im-a-bug", "blah").setModifier(TokenParamModifier.NOT))
);
params.setLoadSynchronous(true);
patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params));
assertThat(patients, contains(female));
}
@Test
@ -3823,8 +3949,16 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null);
map.add(Observation.SP_VALUE_QUANTITY, param);
myCaptureQueriesListener.clear();
found = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(found), contains(id1));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(toUnqualifiedVersionlessIdValues(found).toString(), toUnqualifiedVersionlessIdValues(found), contains(id1));
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(0, countMatches(searchQuery.toLowerCase(), "partition"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value"), searchQuery);
map = new SearchParameterMap();
map.setLoadSynchronous(true);
@ -4107,9 +4241,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(patients.toString(), patients, contains(obsId1));
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search query:\n{}", searchQuery);
assertEquals(1, StringUtils.countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(1, StringUtils.countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
assertEquals(2, StringUtils.countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_low_date_ordinal >= '20200605'"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_low_date_ordinal <= '20200606'"), searchQuery);
}
// Two AND instances of 1 SP and 1 instance of another
@ -4125,9 +4259,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(patients.toString(), patients, contains(obsId1));
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search query:\n{}", searchQuery);
assertEquals(2, StringUtils.countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(2, StringUtils.countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
assertEquals(4, StringUtils.countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
assertEquals(0, countMatches(searchQuery.toLowerCase(), "partition"), searchQuery);
assertEquals(2, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(2, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
assertEquals(4, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
}
// Period search
@ -4140,9 +4275,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(patients.toString(), patients, containsInAnyOrder(obsId3, obsId4));
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search query:\n{}", searchQuery);
assertEquals(1, StringUtils.countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(1, StringUtils.countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
assertEquals(1, StringUtils.countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
}
}
@ -4160,9 +4295,13 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
myCaptureQueriesListener.clear();
IBundleProvider values = myPatientDao.search(map);
assertEquals(null, values.size());
assertEquals(5, values.getResources(0, 1000).size());
String sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertEquals(1, countMatches(sql, "limit '5'"), sql);
}
@Test
@ -4627,6 +4766,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
List<IIdType> patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params));
assertThat(patients, containsInAnyOrder(tag2id));
}
// TODO: get multiple/AND working
{
// And tags
@ -4675,9 +4815,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
{
// One tag
SearchParameterMap params = new SearchParameterMap();
SearchParameterMap params = SearchParameterMap.newSynchronous();
params.add("_tag", new TokenParam("urn:taglist", methodName + "1a").setModifier(TokenParamModifier.NOT));
myCaptureQueriesListener.clear();
List<IIdType> patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(patients, containsInAnyOrder(tag2id));
assertThat(patients, not(containsInAnyOrder(tag1id)));
}
@ -5116,12 +5258,14 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
// Matches
Encounter e1 = new Encounter();
e1.setPeriod(new Period().setStartElement(new DateTimeType("2020-09-14T12:00:00Z")).setEndElement(new DateTimeType("2020-09-14T12:00:00Z")));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e1));
String e1Id = myEncounterDao.create(e1).getId().toUnqualifiedVersionless().getValue();
Communication c1 = new Communication();
c1.getEncounter().setReference(e1Id);
myCommunicationDao.create(c1);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(c1));
String c1Id = myCommunicationDao.create(c1).getId().toUnqualifiedVersionless().getValue();
// Doesn't match
// Doesn't match (wrong date)
Encounter e2 = new Encounter();
e2.setPeriod(new Period().setStartElement(new DateTimeType("2020-02-14T12:00:00Z")).setEndElement(new DateTimeType("2020-02-14T12:00:00Z")));
String e2Id = myEncounterDao.create(e2).getId().toUnqualifiedVersionless().getValue();
@ -5129,12 +5273,36 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
c2.getEncounter().setReference(e2Id);
myCommunicationDao.create(c2);
SearchParameterMap map = SearchParameterMap.newSynchronous();
// Doesn't match (wrong field - Encounter.location.period is also indexed in the "location-period" SP)
Encounter e3 = new Encounter();
e3.addLocation().setPeriod(new Period().setStartElement(new DateTimeType("2020-09-14T12:00:00Z")).setEndElement(new DateTimeType("2020-09-14T12:00:00Z")));
String e3Id = myEncounterDao.create(e3).getId().toUnqualifiedVersionless().getValue();
Communication c3 = new Communication();
c3.getEncounter().setReference(e3Id);
myCommunicationDao.create(c3);
runInTransaction(()->{
ourLog.info("Links:\n * {}", myResourceLinkDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
ourLog.info("Dates:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
SearchParameterMap map;
map = SearchParameterMap.newSynchronous();
map.add(Communication.SP_ENCOUNTER, new ReferenceParam("ge2020-09-14").setChain("date"));
map.add(Communication.SP_ENCOUNTER, new ReferenceParam("le2020-09-15").setChain("date"));
myCaptureQueriesListener.clear();
IBundleProvider outcome = myCommunicationDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(c1Id));
assertEquals(1, outcome.sizeOrThrowNpe());
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(4, countMatches(searchSql, "JOIN"));
assertEquals(1, countMatches(searchSql, "SELECT"));
}

View File

@ -93,7 +93,6 @@ import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.r4.model.Substance;
import org.hl7.fhir.r4.model.Task;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -501,8 +500,9 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
.collect(Collectors.toList());
String resultingQueryNotFormatted = queries.get(0);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "SP_VALUE"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, containsString("SP_VALUE in ('BAR' , 'FOO')"));
assertEquals(0, StringUtils.countMatches(resultingQueryNotFormatted, "SP_VALUE"), resultingQueryNotFormatted);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "HASH_VALUE"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, containsString("HASH_VALUE IN ('3140583648400062149','4929264259256651518')"));
// Ensure that the search actually worked
assertEquals(2, search.size().intValue());
@ -2362,36 +2362,38 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam1");
patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem")
.setDisplay("testSearchTokenParamDisplay");
myPatientDao.create(patient, mySrd);
String id1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002");
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
myPatientDao.create(patient, mySrd);
String id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue(null);
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
myPatientDao.create(patient, mySrd);
String id3 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
patient = new Patient();
patient.addIdentifier().setSystem("urn:system2").setValue("testSearchTokenParam002");
patient.addName().setFamily("Tester").addGiven("testSearchTokenParam2");
myPatientDao.create(patient, mySrd);
String id4 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
{
// Match system="urn:system" and value = *
SearchParameterMap map = new SearchParameterMap();
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", null));
IBundleProvider retrieved = myPatientDao.search(map);
assertEquals(2, retrieved.size().intValue());
myCaptureQueriesListener.clear();
List<String> values = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(values, containsInAnyOrder(id1, id2));
}
{
// Match system="urn:system" and value = ""
SearchParameterMap map = new SearchParameterMap();
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", ""));
IBundleProvider retrieved = myPatientDao.search(map);
assertEquals(2, retrieved.size().intValue());
myCaptureQueriesListener.clear();
List<String> values = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(values, containsInAnyOrder(id1, id2));
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
@ -8,6 +9,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SortSpec;
@ -18,13 +20,15 @@ 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.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.AopProxyUtils;
@ -41,8 +45,15 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
@ -53,6 +64,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
private ISearchDao mySearchEntityDao;
@Autowired
private ISearchResultDao mySearchResultDao;
@Autowired
private MatchUrlService myMatchUrlService;
@BeforeEach
public void before() {
@ -123,8 +136,22 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
String uuid;
List<String> ids;
// Search with count only
params = new SearchParameterMap();
// Search with count only (synchronous)
params = new SearchParameterMap().setLoadSynchronous(true);
params.add(Patient.SP_NAME, new StringParam("FAM"));
params.setSummaryMode((SummaryEnum.COUNT));
myCaptureQueriesListener.clear();
results = myPatientDao.search(params);
String sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(sql, containsString("COUNT(DISTINCT "));
uuid = results.getUuid();
ourLog.info("** Search returned UUID: {}", uuid);
assertEquals(201, results.size().intValue());
ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true);
assertThat(ids, empty());
// Search with count only (non-synchronous)
params = new SearchParameterMap().setLoadSynchronous(false);
params.add(Patient.SP_NAME, new StringParam("FAM"));
params.setSummaryMode((SummaryEnum.COUNT));
results = myPatientDao.search(params);
@ -744,13 +771,28 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
String resultingQueryNotFormatted = queries.get(0);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "Patient.managingOrganization"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, containsString("TARGET_RESOURCE_ID in ('" + ids.get(0) + "' , '" + ids.get(1) + "' , '" + ids.get(2) + "' , '" + ids.get(3) + "' , '" + ids.get(4) + "')"));
assertThat(resultingQueryNotFormatted, containsString("TARGET_RESOURCE_ID IN ('" + ids.get(0) + "','" + ids.get(1) + "','" + ids.get(2) + "','" + ids.get(3) + "','" + ids.get(4) + "')"));
// Ensure that the search actually worked
assertEquals(5, search.size().intValue());
}
@Test
public void testChainedSearchUsesJoinNotSubselect() {
myCaptureQueriesListener.clear();
RuntimeResourceDefinition resourceDef = myFhirCtx.getResourceDefinition("Observation");
SearchParameterMap params = myMatchUrlService.translateMatchUrl("/Observation?subject:patient.identifier=urn:oid:ZOOP.MRN.OID|1234", resourceDef, null);
params.setLoadSynchronous(true);
myObservationDao.search(params);
myCaptureQueriesListener.logSelectQueries();
String selectQuery = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, true);
ourLog.info(selectQuery);
assertEquals(2, StringUtils.countMatches(selectQuery, "JOIN"));
assertEquals(1, StringUtils.countMatches(selectQuery, "SELECT"));
}
@AfterEach
public void afterResetDao() {
@ -1128,7 +1170,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
// The search itself
resultingQueryNotFormatted = queries.get(1);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "Patient.managingOrganization"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, matchesPattern(".*TARGET_RESOURCE_ID in \\('[0-9]+' , '[0-9]+' , '[0-9]+' , '[0-9]+' , '[0-9]+'\\).*"));
assertThat(resultingQueryNotFormatted, matchesPattern(".*TARGET_RESOURCE_ID IN \\('[0-9]+','[0-9]+','[0-9]+','[0-9]+','[0-9]+'\\).*"));
// Ensure that the search actually worked
assertEquals(5, search.size().intValue());

View File

@ -90,12 +90,12 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
map = new SearchParameterMap();
map.setSort(new SortSpec("_id", SortOrderEnum.ASC));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains(id1, id2, "Patient/AA", "Patient/AB"));
assertThat(ids, contains("Patient/AA", "Patient/AB", id1, id2));
map = new SearchParameterMap();
map.setSort(new SortSpec("_id", SortOrderEnum.DESC));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/AB", "Patient/AA", id2, id1));
assertThat(ids, contains(id2, id1, "Patient/AB", "Patient/AA"));
}
@Test
@ -258,7 +258,7 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
map.setSort(new SortSpec(Patient.SP_FAMILY, SortOrderEnum.ASC).setChain(new SortSpec(Patient.SP_GIVEN, SortOrderEnum.ASC)));
ids = toUnqualifiedVersionlessIds(myPatientDao.search(map));
ourLog.info("** Got IDs: {}", ids);
assertThat(ids, contains(pid1, pid2, pid3, pid4, pid5));
assertThat(ids.toString(), ids, contains(pid2, pid4, pid5, pid3, pid1));
assertEquals(5, ids.size());
}
@ -316,7 +316,7 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
map.setSort(new SortSpec("gender").setChain(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
ourLog.info("IDS: {}", ids);
assertThat(ids, contains("Patient/CA", "Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB", "Patient/CA"));
map = new SearchParameterMap();
map.add(Patient.SP_ACTIVE, new TokenParam(null, "true"));

View File

@ -98,7 +98,6 @@ import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.Timing;
import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -122,12 +121,14 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
@ -2385,7 +2386,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_BIRTHDATE + "AAAA", new DateParam(ParamPrefixEnum.GREATERTHAN, "2000-01-01")).setLoadSynchronous(true)));
assertEquals(0, found.size());
} catch (InvalidRequestException e) {
assertEquals("Unknown search parameter \"birthdateAAAA\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
assertEquals("Unknown search parameter \"birthdateAAAA\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, _lastUpdated, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]", e.getMessage());
}
}
@ -3029,16 +3030,14 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
pm.setSort(new SortSpec(Patient.SP_BIRTHDATE));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
// Nulls first for H2
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate"));
pm.setSort(new SortSpec(Patient.SP_BIRTHDATE).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
// Nulls first for H2
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testtestSortByDate"));
@ -3046,7 +3045,6 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id3, id2, id1, id4));
// assertThat(actual, contains(id4, id3, id2, id1));
}
@ -3084,6 +3082,20 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
assertThat(actual, contains(toValues(id3, id2, id1)));
}
@Test
public void testSortByInvalidParameter() {
SearchParameterMap pm = SearchParameterMap.newSynchronous();
pm.setSort(new SortSpec("hello", SortOrderEnum.DESC));
try {
myObservationDao.search(pm);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unknown _sort parameter value \"hello\" for resource type \"Observation\" (Note: sort parameters values must use a valid Search Parameter). Valid values for this search are: [_id, _language, _lastUpdated, based-on, category, code, code-value-concept, code-value-date, code-value-quantity, code-value-string, combo-code, combo-code-value-concept, combo-code-value-quantity, combo-data-absent-reason, combo-value-concept, combo-value-quantity, component-code, component-code-value-concept, component-code-value-quantity, component-data-absent-reason, component-value-concept, component-value-quantity, data-absent-reason, date, derived-from, device, encounter, focus, has-member, identifier, method, part-of, patient, performer, specimen, status, subject, value-concept, value-date, value-quantity, value-string]", e.getMessage());
}
}
@Test
public void testSortById() {
String methodName = "testSortBTyId";
@ -3097,15 +3109,21 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
p = new Patient();
p.setId(methodName);
p.setId(methodName+"1");
p.addIdentifier().setSystem("urn:system").setValue(methodName);
IIdType idMethodName = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless();
assertEquals(methodName, idMethodName.getIdPart());
IIdType idMethodName1 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless();
assertEquals(methodName+"1", idMethodName1.getIdPart());
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
p = new Patient();
p.setId(methodName+"2");
p.addIdentifier().setSystem("urn:system").setValue(methodName);
IIdType idMethodName2 = myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless();
assertEquals(methodName+"2", idMethodName2.getIdPart());
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
@ -3113,26 +3131,26 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
SearchParameterMap pm;
List<IIdType> actual;
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(IAnyResource.SP_RES_ID));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(id1, id2, id3, id4, idMethodName));
assertEquals(6, actual.size());
assertThat(actual, contains(idMethodName1, idMethodName2, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(id1, id2, id3, id4, idMethodName));
assertEquals(6, actual.size());
assertThat(actual, contains(idMethodName1, idMethodName2, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(idMethodName, id4, id3, id2, id1));
assertEquals(6, actual.size());
assertThat(actual, contains(id4, id3, id2, id1, idMethodName2, idMethodName1));
}
@Test
@ -3140,6 +3158,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
String methodName = "testSortByLastUpdated";
Patient p = new Patient();
p.setActive(true);
p.addIdentifier().setSystem("urn:system1").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
@ -3147,6 +3166,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
TestUtil.sleepOneClick();
p = new Patient();
p.setActive(true);
p.addIdentifier().setSystem("urn:system2").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
@ -3154,6 +3174,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
TestUtil.sleepOneClick();
p = new Patient();
p.setActive(true);
p.addIdentifier().setSystem("urn:system3").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
@ -3161,6 +3182,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
TestUtil.sleepOneClick();
p = new Patient();
p.setActive(true);
p.addIdentifier().setSystem("urn:system4").setValue(methodName);
p.addName().setFamily(methodName);
IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
@ -3168,22 +3190,30 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
SearchParameterMap pm;
List<IIdType> actual;
pm = new SearchParameterMap();
// With no search parameter
pm = SearchParameterMap.newSynchronous();
pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
// With a search parameter
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_ACTIVE, new TokenParam("true"));
pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertThat(actual, contains(id1, id2, id3, id4));
pm = SearchParameterMap.newSynchronous();
pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertThat(actual, contains(id4, id3, id2, id1));
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam(null, methodName));
pm.setSort(new SortSpec(Patient.SP_NAME).setChain(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.DESC)));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
@ -3224,7 +3254,6 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
}
@Test
@Disabled
public void testSortByQuantity() {
Observation res;
@ -3246,13 +3275,13 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
SearchParameterMap pm = new SearchParameterMap();
pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY));
List<IIdType> actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm));
List<IIdType> actual = toUnqualifiedVersionlessIds(myObservationDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY, SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm));
actual = toUnqualifiedVersionlessIds(myObservationDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id1, id2, id3, id4));
@ -3353,27 +3382,30 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
SearchParameterMap pm;
List<IIdType> actual;
pm = new SearchParameterMap();
myCaptureQueriesListener.clear();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));
pm.setSort(new SortSpec(Patient.SP_FAMILY));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
String sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(sql, countMatches(sql, "JOIN"), equalTo(2));
assertThat(sql, countMatches(sql, "ORDER BY"), equalTo(1));
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));
pm.setSort(new SortSpec(Patient.SP_FAMILY).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id1, id2, id3));
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm = SearchParameterMap.newSynchronous();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));
pm.setSort(new SortSpec(Patient.SP_FAMILY).setOrder(SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id3, id2, id1, id4));
// assertThat(actual, contains(id4, id3, id2, id1));
}
/**
@ -3492,32 +3524,31 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
}
@Test
@Disabled
public void testSortByUri() {
ConceptMap res = new ConceptMap();
res.addGroup().setSource("http://foo2");
res.setUrl("http://foo2");
IIdType id2 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless();
res = new ConceptMap();
res.addGroup().setSource("http://foo1");
res.setUrl("http://foo1");
IIdType id1 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless();
res = new ConceptMap();
res.addGroup().setSource("http://bar3");
res.setUrl("http://foo3");
IIdType id3 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless();
res = new ConceptMap();
res.addGroup().setSource("http://bar4");
res.setUrl("http://foo4");
IIdType id4 = myConceptMapDao.create(res, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap pm = new SearchParameterMap();
pm.setSort(new SortSpec(ConceptMap.SP_SOURCE));
pm.setSort(new SortSpec(ConceptMap.SP_URL));
List<IIdType> actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.setSort(new SortSpec(Encounter.SP_LENGTH, SortOrderEnum.DESC));
pm.setSort(new SortSpec(ConceptMap.SP_URL, SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id3, id2, id1));
@ -3534,13 +3565,13 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
SearchParameterMap map;
map = new SearchParameterMap();
map = SearchParameterMap.newSynchronous();
map.add(IAnyResource.SP_RES_ID, new StringParam(id1.getIdPart()));
map.setLastUpdated(new DateRangeParam("2001", "2003"));
map.setSort(new SortSpec(Constants.PARAM_LASTUPDATED));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), empty());
map = new SearchParameterMap();
map = SearchParameterMap.newSynchronous();
map.add(IAnyResource.SP_RES_ID, new StringParam(id1.getIdPart()));
map.setLastUpdated(new DateRangeParam("2001", "2003"));
map.setSort(new SortSpec(Patient.SP_NAME));

View File

@ -442,8 +442,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='Patient?identifier=urn%7C111'",
"HASH_SYS_AND_VALUE in ('-3122824860083758210')"
"IDX_STRING = 'Patient?identifier=urn%7C111'",
"HASH_SYS_AND_VALUE = '-3122824860083758210'"
));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
@ -461,7 +461,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(id1));
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, containsString("HASH_SYS_AND_VALUE in ('4101160957635429999' , '-3122824860083758210')"));
assertThat(unformattedSql, containsString("HASH_SYS_AND_VALUE IN ('4101160957635429999','-3122824860083758210')"));
assertThat(unformattedSql, not(containsString(("IDX_STRING"))));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
@ -579,8 +579,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId));
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'",
"HASH_SYS_AND_VALUE in ('6795110643554413877')"
"IDX_STRING = 'ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'",
"HASH_SYS_AND_VALUE = '6795110643554413877'"
));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
@ -601,8 +601,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId));
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"SRC_PATH in ('ServiceRequest.subject.where(resolve() is Patient)')",
"SRC_PATH in ('ServiceRequest.performer')"
"SRC_PATH = 'ServiceRequest.subject.where(resolve() is Patient)'",
"SRC_PATH = 'ServiceRequest.performer'"
));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));

View File

@ -439,6 +439,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
myGroupDao.update(group);
Patient patient = new Patient();
patient.getText().setStatus(Narrative.NarrativeStatus.GENERATED).setDivAsString("<div>Hello</div>");
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
@ -450,7 +451,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED).setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
@ -459,9 +460,11 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
OperationOutcome oo;
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("Unable to resolve resource 'Group/123'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
@ -523,9 +526,11 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
OperationOutcome oo;
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("Unable to resolve resource 'Group/123'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));

View File

@ -28,6 +28,9 @@ import ca.uhn.fhir.rest.param.DateAndListParam;
import ca.uhn.fhir.rest.param.DateOrListParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
@ -50,6 +53,8 @@ import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.PractitionerRole;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -645,7 +650,6 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
}
@Test
public void testCreateInTransaction_ServerId_WithPartition() {
createUniqueCompositeSp();
@ -769,7 +773,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(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(t -> t.toString()).collect(Collectors.joining("\n * ")));
assertEquals(3, myResourceIndexedSearchParamTokenDao.countForResourceId(patientId));
});
@ -790,7 +794,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(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(t -> t.toString()).collect(Collectors.joining("\n * ")));
assertEquals(3, myResourceIndexedSearchParamTokenDao.countForResourceId(patientId));
// HFJ_RES_VER
@ -1031,7 +1035,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "SP_MISSING='true'"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'true'"));
}
// :missing=false
@ -1048,7 +1052,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "SP_MISSING='false'"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'false'"));
}
}
@ -1072,8 +1076,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myparamsto1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='true'"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'true'"), searchSql);
}
// :missing=false
@ -1089,8 +1093,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myparamsst1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='false'"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'false'"));
}
}
@ -1113,8 +1117,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='true'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'true'"));
}
// :missing=false
@ -1130,8 +1134,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='false'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'false'"));
}
}
@ -1158,7 +1162,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"));
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'"));
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'"));
}
}
@ -1183,10 +1187,10 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "mysearchpa1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"));
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='-3438137196820602023'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '-3438137196820602023'"), searchSql);
}
}
@ -1211,10 +1215,10 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "mysearchpa1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"));
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'"), searchSql);
}
}
@ -1237,10 +1241,10 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "mysearchpa1_.PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"));
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IS NULL"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'"), searchSql);
}
}
@ -1390,8 +1394,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "SP_VALUE_LOW"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"), searchSql);
// Date OR param
@ -1521,6 +1525,45 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
}
@Test
public void testSearch_HasParam_SearchOnePartition() {
addReadPartition(1);
addCreatePartition(1, null);
Organization org = new Organization();
org.setId("ORG");
org.setName("ORG");
myOrganizationDao.update(org);
addReadPartition(1);
addCreatePartition(1, null);
Practitioner practitioner = new Practitioner();
practitioner.setId("PRACT");
practitioner.addName().setFamily("PRACT");
myPractitionerDao.update(practitioner);
addReadPartition(1);
addCreatePartition(1, null);
PractitionerRole role = new PractitionerRole();
role.setId("ROLE");
role.getPractitioner().setReference("Practitioner/PRACT");
role.getOrganization().setReference("Organization/ORG");
myPractitionerRoleDao.update(role);
addReadPartition(1);
SearchParameterMap params = SearchParameterMap.newSynchronous();
HasAndListParam value = new HasAndListParam();
value.addAnd(new HasOrListParam().addOr(new HasParam("PractitionerRole", "practitioner", "_id", "ROLE")));
params.add("_has", value);
myCaptureQueriesListener.clear();
IBundleProvider outcome = myPractitionerDao.search(params);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(1);
assertEquals(1, outcome.getResources(0, 1).size());
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
@Test
public void testSearch_StringParam_SearchAllPartitions() {
@ -1684,7 +1727,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
// And with another param
@ -1694,15 +1737,15 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT));
map.add(Patient.SP_IDENTIFIER, new TokenParam("http://foo", "bar"));
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
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, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myparamsto1_.HASH_SYS_AND_VALUE in"));
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t1.HASH_SYS_AND_VALUE ="), searchSql);
}
@ -1725,8 +1768,8 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
assertThat(ids.toString(), ids, Matchers.contains(patientIdNull));
}
@ -1753,7 +1796,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@Test
@ -1775,7 +1818,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@Test
@ -1790,16 +1833,18 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
SearchParameterMap map = new SearchParameterMap();
map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code"));
map.setLoadSynchronous(true);
myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
assertThat(ids, Matchers.contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
// If this ever got optimized down to 1 that would be OK too
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
assertEquals(2, StringUtils.countMatches(searchSql, "JOIN"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@Test
@ -1824,7 +1869,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@Test
@ -1849,7 +1894,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@Test
@ -1872,7 +1917,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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'"));
assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING = 'Patient?birthdate=2020-01-01'"));
}
@ -1895,7 +1940,7 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
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'"));
assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING = 'Patient?birthdate=2020-01-01'"));
// Same query, different partition
addReadPartition(2);
@ -1929,10 +1974,10 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH in ('Observation.subject')"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.SRC_PATH = 'Observation.subject'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "t0.TARGET_RESOURCE_ID = '" + patientId.getIdPartAsLong() + "'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
// Same query, different partition
addReadPartition(2);
@ -1962,14 +2007,13 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
map.setLoadSynchronous(true);
IBundleProvider results = myObservationDao.search(map);
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH in ('Observation.subject')"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.SRC_PATH = 'Observation.subject'"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.TARGET_RESOURCE_ID = '" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// Same query, different partition
@ -2004,9 +2048,9 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID='1' "));
assertEquals(1, StringUtils.countMatches(searchSql, "and forcedid0_.RESOURCE_TYPE='Patient'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID='1'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "and forcedid0_.RESOURCE_TYPE='Patient'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
// Same query, different partition
addReadPartition(2);
@ -2040,9 +2084,9 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.RESOURCE_TYPE='Patient' "));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID is null"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.RESOURCE_TYPE='Patient'"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
// Same query, different partition
addReadPartition(2);
@ -2090,24 +2134,26 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Resolve resource
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_ID="));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql);
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_ID"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID"), searchSql);
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_IDAA"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase());
assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase());
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(3).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_IDAA"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(3).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase());
assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase());
}
@Test
@ -2158,17 +2204,17 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_ID"));
assertEquals(0, countMatches(sql, "PARTITION_ID="));
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_IDAA"));
assertEquals(0, countMatches(sql, "PARTITION_ID="));
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(3).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_IDAA"));
assertEquals(0, countMatches(sql, "PARTITION_ID="));
}
@Test
@ -2221,20 +2267,21 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Count
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "count("));
assertEquals(1, countMatches(sql, "PARTITION_ID='1'"));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(1, countMatches(searchSql, "count("));
assertEquals(1, countMatches(searchSql, "PARTITION_ID='1'"));
// Fetch history
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "PARTITION_ID='1'"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(1, countMatches(searchSql, "PARTITION_ID='1'"));
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_IDAA"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase());
assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase());
}
@Test
@ -2257,19 +2304,20 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Count
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "PARTITION_ID is null"));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(1, countMatches(searchSql, "PARTITION_ID is null"), searchSql);
// Fetch history
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(1, countMatches(sql, "PARTITION_ID is null"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(1, countMatches(searchSql, "PARTITION_ID is null"), searchSql);
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", sql);
assertEquals(0, countMatches(sql, "PARTITION_IDzzz"));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
ourLog.info("SQL:{}", searchSql);
assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase());
assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase());
}
@Test
@ -2341,18 +2389,18 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
// Resolve resource
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
assertEquals(1, countMatches(sql, "PARTITION_ID is null"));
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true).toUpperCase();
assertEquals(1, countMatches(sql, "PARTITION_ID IS NULL"));
assertEquals(1, countMatches(sql, "PARTITION_ID"));
// Fetch history resource
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true);
assertEquals(1, countMatches(sql, "PARTITION_ID is null"));
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true).toUpperCase();
assertEquals(1, countMatches(sql, "PARTITION_ID IS NULL"));
// Resolve forced IDs
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true);
assertEquals(1, countMatches(sql, "forcedid0_.RESOURCE_PID in"), sql);
assertEquals(0, countMatches(sql, "PARTITION_ID is null"), sql);
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true).toUpperCase();
assertEquals(1, countMatches(sql, "FORCEDID0_.RESOURCE_PID IN"), sql);
assertEquals(0, countMatches(sql, "PARTITION_ID IS NULL"), sql);
}
@Test

View File

@ -68,7 +68,7 @@ public class SearchWithInterceptorR4Test extends BaseJpaR4Test {
String query = list.get(0).getSql(true, false);
ourLog.info("Query: {}", query);
assertThat(query, containsString("HASH_SYS_AND_VALUE in ('3788488238034018567')"));
assertThat(query, containsString("HASH_SYS_AND_VALUE = '3788488238034018567'"));
} finally {
myInterceptorRegistry.unregisterInterceptor(interceptor);

View File

@ -18,6 +18,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -95,11 +96,17 @@ public class FhirResourceDaoR5SearchNoFtTest extends BaseJpaR5Test {
role.getOrganization().setReference("Organization/ORG");
myPractitionerRoleDao.update(role);
SearchParameterMap params = new SearchParameterMap();
runInTransaction(()->{
ourLog.info("Links:\n * {}", myResourceLinkDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
SearchParameterMap params = SearchParameterMap.newSynchronous();
HasAndListParam value = new HasAndListParam();
value.addAnd(new HasOrListParam().addOr(new HasParam("PractitionerRole", "practitioner", "_id", "ROLE")));
params.add("_has", value);
myCaptureQueriesListener.clear();
IBundleProvider outcome = myPractitionerDao.search(params);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(1);
assertEquals(1, outcome.getResources(0, 1).size());
}

View File

@ -32,6 +32,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
@Autowired
IdHelperService myIdHelperService;
@Override
@BeforeEach
public void before() throws Exception {
super.before();
@ -39,6 +40,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
myDaoConfig.setExpungeEnabled(true);
}
@Override
@AfterEach
public void after() throws Exception {
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());

View File

@ -49,9 +49,16 @@ import static org.apache.commons.lang3.time.DateUtils.MILLIS_PER_SECOND;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Test {
@ -95,11 +102,11 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
Bundle results = myClient.search().forResource(Patient.class).returnBundle(Bundle.class).execute();
verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture());
verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture());
verify(interceptor, times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture());
verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE), myParamsCaptor.capture());
verify(interceptor, times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FAILED), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FAILED), myParamsCaptor.capture());
SearchRuntimeDetails details = myParamsCaptor.getAllValues().get(0).get(SearchRuntimeDetails.class);
assertEquals(SearchStatusEnum.PASSCMPLET, details.getSearchStatus());
@ -108,11 +115,11 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
reset(interceptor);
results = myClient.loadPage().next(results).execute();
assertNotNull(results);
verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture());
verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture());
verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture());
verify(interceptor, times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE), myParamsCaptor.capture());
verify(interceptor, times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FAILED), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE), myParamsCaptor.capture());
verify(interceptor, timeout(10000).times(0)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FAILED), myParamsCaptor.capture());
}
@ -327,32 +334,12 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
}
}
public class ReflexInterceptor extends ServerOperationInterceptorAdapter {
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (theResource instanceof Patient) {
((ServletRequestDetails) theRequest).getServletRequest().setAttribute("CREATED_PATIENT", theResource);
}
}
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
Patient createdPatient = (Patient) theRequestDetails.getServletRequest().getAttribute("CREATED_PATIENT");
if (createdPatient != null) {
Observation observation = new Observation();
observation.setSubject(new Reference(createdPatient.getId()));
myClient.create().resource(observation).execute();
}
}
}
@Test
public void testInterceptorExpandsSearch() {
@Interceptor
class SearchExpandingInterceptor {
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
public void enrich(RequestDetails theRequestDetails) {
@ -368,7 +355,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
}
}
}
Patient p1 = new Patient();
@ -380,7 +367,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
o1.setId("o1");
o1.getSubject().setReference("Patient/p1");
myObservationDao.update(o1);
Patient p2 = new Patient();
p2.setId("p2");
p2.addIdentifier().setValue("p2");
@ -417,11 +404,30 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
} finally {
ourRestServer.unregisterInterceptor(interceptor);
}
}
public class ReflexInterceptor extends ServerOperationInterceptorAdapter {
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (theResource instanceof Patient) {
((ServletRequestDetails) theRequest).getServletRequest().setAttribute("CREATED_PATIENT", theResource);
}
}
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
Patient createdPatient = (Patient) theRequestDetails.getServletRequest().getAttribute("CREATED_PATIENT");
if (createdPatient != null) {
Observation observation = new Observation();
observation.setSubject(new Reference(createdPatient.getId()));
myClient.create().resource(observation).execute();
}
}
}
public static void verifyDaoInterceptor(IServerInterceptor theDaoInterceptor) {
ArgumentCaptor<ActionRequestDetails> ardCaptor;
ArgumentCaptor<RestOperationTypeEnum> opTypeCaptor;

View File

@ -2050,13 +2050,21 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
c.getSubject().setReferenceElement(pId);
IIdType cId = myClient.create().resource(c).execute().getId().toUnqualifiedVersionless();
ourLog.info("Resource IDs:\n * {}\n * {}\n * {}", oId, pId, cId);
runInTransaction(() -> {
ourLog.info("Resource Links:\n * {}", myResourceLinkDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
ourLog.info("Resources:\n * {}", myResourceTableDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
Thread.sleep(10);
long time3 = System.currentTimeMillis();
// %3E=> %3C=<
myCaptureQueriesListener.clear();
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + pId.getIdPart() + "/$everything?_lastUpdated=%3E" + new InstantType(new Date(time1)).getValueAsString());
CloseableHttpResponse response = ourHttpClient.execute(get);
myCaptureQueriesListener.logSelectQueries();
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
@ -2918,6 +2926,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Disabled
@Test
public void testPagingOverEverythingSetWithNoPagingProvider() {
ourRestServer.setPagingProvider(null);
@ -2956,7 +2965,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertEquals(null, response.getTotalElement().getValue());
assertThat(response.getLink("next").getUrl(), not(emptyString()));
myCaptureQueriesListener.clear();
response = myClient.fetchResourceFromUrl(Bundle.class, response.getLink("next").getUrl());
myCaptureQueriesListener.logSelectQueries();
assertEquals(1, response.getEntry().size());
assertEquals(21, response.getTotalElement().getValue().intValue());
@ -4494,6 +4505,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unable to handle number prefix \"eb\" for value: eb100"));
}
try {
myClient
.search()
.forResource(MolecularSequence.class)
.where(MolecularSequence.VARIANT_END.withPrefix(ParamPrefixEnum.STARTS_AFTER).number(100))
.prettyPrint()
.returnBundle(Bundle.class)
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unable to handle number prefix \"sa\" for value: sa100"));
}
}
@Test()

View File

@ -233,11 +233,14 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test {
observation.setEffective(new DateTimeType("1965-08-10"));
myObservationDao.create(observation).getId().toUnqualified();
myCaptureQueriesListener.clear();
Bundle output = myClient
.search()
.byUrl("Observation?_count=0")
.returnBundle(Bundle.class)
.execute();
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
myCaptureQueriesListener.logSelectQueries();
assertEquals(2, output.getTotal());
assertEquals(0, output.getEntry().size());

View File

@ -121,10 +121,12 @@ public class PagingMultinodeProviderDstu3Test extends BaseResourceProviderDstu3T
assertThat(found.getLink().stream().filter(l -> l.getRelation().equals("next")).map(l -> l.getUrl()).findAny()
.orElseThrow(() -> new IllegalStateException("No next page link")).contains("_offset=10"), is(true));
myCaptureQueriesListener.clear();
found = ourClient
.loadPage()
.next(found)
.execute();
myCaptureQueriesListener.logSelectQueries();
assertThat(toUnqualifiedVersionlessIdValues(found), contains("Patient/A010", "Patient/A011", "Patient/A012", "Patient/A013", "Patient/A014", "Patient/A015", "Patient/A016", "Patient/A017", "Patient/A018", "Patient/A019"));
found = ourClient

View File

@ -10,7 +10,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.config.dstu3.BaseDstu3Config;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
@ -94,7 +94,7 @@ public class SearchCoordinatorSvcImplTest {
private EntityManager myEntityManager;
private int myExpectedNumberOfSearchBuildersCreated = 2;
@Mock
private SearchBuilder mySearchBuilder;
private LegacySearchBuilder mySearchBuilder;
@Mock
private ISearchCacheSvc mySearchCacheSvc;
@Mock

View File

@ -0,0 +1,20 @@
package ca.uhn.fhir.jpa.search.builder.sql;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class GeneratedSqlTest {
@Test
public void testBlockInlineNonBoundParameters() {
assertThrows(AssertionError.class, () -> new GeneratedSql(false, "SELECT * FROM FOO WHERE t = '123'", Collections.emptyList()));
assertThrows(AssertionError.class, () -> new GeneratedSql(false, "SELECT * FROM FOO WHERE t='123'", Collections.emptyList()));
assertThrows(AssertionError.class, () -> new GeneratedSql(false, "SELECT * FROM FOO WHERE t in ('123')", Collections.emptyList()));
assertThrows(AssertionError.class, () -> new GeneratedSql(false, "SELECT * FROM FOO WHERE t IN ('123')", Collections.emptyList()));
assertThrows(AssertionError.class, () -> new GeneratedSql(false, "SELECT * FROM FOO WHERE t IN('123')", Collections.emptyList()));
}
}

View File

@ -21,8 +21,20 @@ package ca.uhn.fhir.jpa.model.entity;
*/
import ca.uhn.fhir.rest.api.Constants;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Table(name = "HFJ_RES_VER_PROV", indexes = {
@Index(name = "IDX_RESVERPROV_SOURCEURI", columnList = "SOURCE_URI"),
@ -55,6 +67,15 @@ public class ResourceHistoryProvenanceEntity extends BasePartitionable {
super();
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("resourceId", myResourceTable.getId());
b.append("sourceUri", mySourceUri);
b.append("requestId", myRequestId);
return b.toString();
}
public void setResourceTable(ResourceTable theResourceTable) {
myResourceTable = theResourceTable;
}

View File

@ -169,8 +169,12 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("paramName", getParamName());
b.append("resourceId", getResourcePid());
b.append("lat", getLatitude());
b.append("lon", getLongitude());
if (isMissing()) {
b.append("missing", isMissing());
} else {
b.append("lat", getLatitude());
b.append("lon", getLongitude());
}
return b.build();
}

View File

@ -257,6 +257,8 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
b.append("resourceId", getResourcePid());
b.append("valueLow", new InstantDt(getValueLow()));
b.append("valueHigh", new InstantDt(getValueHigh()));
b.append("ordLow", myValueLowDateOrdinal);
b.append("ordHigh", myValueHighDateOrdinal);
b.append("hashIdentity", myHashIdentity);
b.append("missing", isMissing());
return b.build();

View File

@ -246,9 +246,15 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("resourceType", getResourceType());
b.append("paramName", getParamName());
b.append("system", getSystem());
b.append("value", getValue());
if (isMissing()) {
b.append("missing", true);
} else {
b.append("system", getSystem());
b.append("value", getValue());
}
b.append("hashIdentity", myHashIdentity);
b.append("hashSystem", myHashSystem);
b.append("hashValue", myHashValue);
return b.build();
}

View File

@ -36,11 +36,13 @@ import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import java.util.Collection;
import java.util.Date;
@Entity
@ -90,6 +92,7 @@ public class ResourceLink extends BaseResourceIndex {
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
@Temporal(TemporalType.TIMESTAMP)
private Date myUpdated;
@Transient
private transient String myTargetResourceId;

Some files were not shown because too many files have changed in this diff Show More