Compare commits
3 Commits
c7a6cd9813
...
d0419b731a
Author | SHA1 | Date |
---|---|---|
leif stawnyczy | d0419b731a | |
leif stawnyczy | f7ffe9d62b | |
leif stawnyczy | 99868e1528 |
|
@ -66,6 +66,7 @@ import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import jakarta.annotation.Nonnull;
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
@ -377,6 +378,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
final ISearchBuilder<JpaPid> sb =
|
final ISearchBuilder<JpaPid> sb =
|
||||||
mySearchBuilderFactory.newSearchBuilder(theCallingDao, theResourceType, resourceTypeClass);
|
mySearchBuilderFactory.newSearchBuilder(theCallingDao, theResourceType, resourceTypeClass);
|
||||||
sb.setFetchSize(mySyncSize);
|
sb.setFetchSize(mySyncSize);
|
||||||
|
sb.setRequireTotal(theParams.getCount() != null);
|
||||||
|
|
||||||
final Integer loadSynchronousUpTo = getLoadSynchronousUpToOrNull(theCacheControlDirective);
|
final Integer loadSynchronousUpTo = getLoadSynchronousUpToOrNull(theCacheControlDirective);
|
||||||
boolean isOffsetQuery = theParams.isOffsetQuery();
|
boolean isOffsetQuery = theParams.isOffsetQuery();
|
||||||
|
@ -394,7 +396,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return direct.get();
|
return direct.get();
|
||||||
|
|
||||||
} catch (ResourceNotFoundInIndexException theE) {
|
} catch (ResourceNotFoundInIndexException theE) {
|
||||||
// some resources were not found in index, so we will inform this and resort to JPA search
|
// some resources were not found in index, so we will inform this and resort to JPA search
|
||||||
ourLog.warn(
|
ourLog.warn(
|
||||||
|
@ -402,6 +403,14 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we set a max to fetch from the db for synchronous searches;
|
||||||
|
// otherwise, we would have to load everything into memory (or force the db to do so);
|
||||||
|
// So let's set a max value here
|
||||||
|
Integer maxToLoad = ObjectUtils.defaultIfNull(
|
||||||
|
loadSynchronousUpTo, myStorageSettings.getInternalSynchronousSearchSize());
|
||||||
|
ourLog.debug("Setting a max fetch value of {} for synchronous search", maxToLoad);
|
||||||
|
sb.setMaxResultsToFetch(maxToLoad);
|
||||||
|
|
||||||
ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
|
ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
|
||||||
return mySynchronousSearchSvc.executeQuery(
|
return mySynchronousSearchSvc.executeQuery(
|
||||||
theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo, theRequestPartitionId);
|
theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo, theRequestPartitionId);
|
||||||
|
|
|
@ -246,7 +246,7 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc {
|
||||||
resources, theRequestDetails, myInterceptorBroadcaster);
|
resources, theRequestDetails, myInterceptorBroadcaster);
|
||||||
|
|
||||||
SimpleBundleProvider bundleProvider = new SimpleBundleProvider(resources);
|
SimpleBundleProvider bundleProvider = new SimpleBundleProvider(resources);
|
||||||
if (hasACount) {
|
if (hasACount && theSb.requiresTotal()) {
|
||||||
bundleProvider.setTotalResourcesRequestedReturned(receivedResourceCount);
|
bundleProvider.setTotalResourcesRequestedReturned(receivedResourceCount);
|
||||||
}
|
}
|
||||||
if (theParams.isOffsetQuery()) {
|
if (theParams.isOffsetQuery()) {
|
||||||
|
|
|
@ -60,6 +60,7 @@ import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.search.SearchConstants;
|
import ca.uhn.fhir.jpa.search.SearchConstants;
|
||||||
import ca.uhn.fhir.jpa.search.builder.models.ResolvedSearchQueryExecutor;
|
import ca.uhn.fhir.jpa.search.builder.models.ResolvedSearchQueryExecutor;
|
||||||
|
import ca.uhn.fhir.jpa.search.builder.models.SearchQueryProperties;
|
||||||
import ca.uhn.fhir.jpa.search.builder.sql.GeneratedSql;
|
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.SearchQueryBuilder;
|
||||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryExecutor;
|
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryExecutor;
|
||||||
|
@ -201,7 +202,8 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
private SearchParameterMap myParams;
|
private SearchParameterMap myParams;
|
||||||
private String mySearchUuid;
|
private String mySearchUuid;
|
||||||
private int myFetchSize;
|
private int myFetchSize;
|
||||||
private Integer myMaxResultsToFetch;
|
|
||||||
|
private boolean myRequiresTotal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of PIDs of results that have already been returned in a search.
|
* Set of PIDs of results that have already been returned in a search.
|
||||||
|
@ -227,6 +229,8 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
private boolean myHasNextIteratorQuery = false;
|
private boolean myHasNextIteratorQuery = false;
|
||||||
private RequestPartitionId myRequestPartitionId;
|
private RequestPartitionId myRequestPartitionId;
|
||||||
|
|
||||||
|
private SearchQueryProperties mySearchProperties;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private IFulltextSearchSvc myFulltextSearchSvc;
|
private IFulltextSearchSvc myFulltextSearchSvc;
|
||||||
|
|
||||||
|
@ -272,6 +276,8 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
myResourceSearchViewDao = theResourceSearchViewDao;
|
myResourceSearchViewDao = theResourceSearchViewDao;
|
||||||
myContext = theContext;
|
myContext = theContext;
|
||||||
myIdHelperService = theIdHelperService;
|
myIdHelperService = theIdHelperService;
|
||||||
|
|
||||||
|
mySearchProperties = new SearchQueryProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -281,7 +287,21 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMaxResultsToFetch(Integer theMaxResultsToFetch) {
|
public void setMaxResultsToFetch(Integer theMaxResultsToFetch) {
|
||||||
myMaxResultsToFetch = theMaxResultsToFetch;
|
mySearchProperties.setMaxResultsRequested(theMaxResultsToFetch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShouldDeduplicateInDB(boolean theShouldDeduplicateInDB) {
|
||||||
|
mySearchProperties.setDeduplicateInDBFlag(theShouldDeduplicateInDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequireTotal(boolean theRequireTotal) {
|
||||||
|
myRequiresTotal = theRequireTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresTotal() {
|
||||||
|
return myRequiresTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void searchForIdsWithAndOr(
|
private void searchForIdsWithAndOr(
|
||||||
|
@ -290,6 +310,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
@Nonnull SearchParameterMap theParams,
|
@Nonnull SearchParameterMap theParams,
|
||||||
RequestDetails theRequest) {
|
RequestDetails theRequest) {
|
||||||
myParams = theParams;
|
myParams = theParams;
|
||||||
|
mySearchProperties.setSortSpec(myParams.getSort());
|
||||||
|
|
||||||
// Remove any empty parameters
|
// Remove any empty parameters
|
||||||
theParams.clean();
|
theParams.clean();
|
||||||
|
@ -360,7 +381,12 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
return myFulltextSearchSvc.count(myResourceName, theParams.clone());
|
return myFulltextSearchSvc.count(myResourceName, theParams.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ISearchQueryExecutor> queries = createQuery(theParams.clone(), null, null, null, true, theRequest, null);
|
SearchQueryProperties properties = mySearchProperties.clone();
|
||||||
|
properties.setDoCountOnlyFlag(true);
|
||||||
|
properties.setSortSpec(null); // counts don't require sorts
|
||||||
|
properties.setMaxResultsRequested(null);
|
||||||
|
properties.setOffset(null);
|
||||||
|
List<ISearchQueryExecutor> queries = createQuery(theParams.clone(), properties, theRequest, null);
|
||||||
if (queries.isEmpty()) {
|
if (queries.isEmpty()) {
|
||||||
return 0L;
|
return 0L;
|
||||||
} else {
|
} else {
|
||||||
|
@ -399,19 +425,24 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
|
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
|
||||||
// we mutate the params. Make a private copy.
|
// we mutate the params. Make a private copy.
|
||||||
myParams = theParams.clone();
|
myParams = theParams.clone();
|
||||||
|
mySearchProperties.setSortSpec(myParams.getSort());
|
||||||
mySearchUuid = theSearchUuid;
|
mySearchUuid = theSearchUuid;
|
||||||
myRequestPartitionId = theRequestPartitionId;
|
myRequestPartitionId = theRequestPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query created can be either a count query or the
|
||||||
|
* actual query.
|
||||||
|
* This is why it takes a SearchQueryProperties object
|
||||||
|
* (and doesn't use the local version of it).
|
||||||
|
* The properties may differ slightly for whichever
|
||||||
|
* query this is.
|
||||||
|
*/
|
||||||
private List<ISearchQueryExecutor> createQuery(
|
private List<ISearchQueryExecutor> createQuery(
|
||||||
SearchParameterMap theParams,
|
SearchParameterMap theParams,
|
||||||
SortSpec sort,
|
SearchQueryProperties theSearchProperties,
|
||||||
Integer theOffset,
|
|
||||||
Integer theMaximumResults,
|
|
||||||
boolean theCountOnlyFlag,
|
|
||||||
RequestDetails theRequest,
|
RequestDetails theRequest,
|
||||||
SearchRuntimeDetails theSearchRuntimeDetails) {
|
SearchRuntimeDetails theSearchRuntimeDetails) {
|
||||||
|
|
||||||
ArrayList<ISearchQueryExecutor> queries = new ArrayList<>();
|
ArrayList<ISearchQueryExecutor> queries = new ArrayList<>();
|
||||||
|
|
||||||
if (checkUseHibernateSearch()) {
|
if (checkUseHibernateSearch()) {
|
||||||
|
@ -422,7 +453,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
List<JpaPid> fulltextMatchIds = null;
|
List<JpaPid> fulltextMatchIds = null;
|
||||||
int resultCount = 0;
|
int resultCount = 0;
|
||||||
if (myParams.isLastN()) {
|
if (myParams.isLastN()) {
|
||||||
fulltextMatchIds = executeLastNAgainstIndex(theMaximumResults);
|
fulltextMatchIds = executeLastNAgainstIndex(theSearchProperties.getMaxResultsRequested());
|
||||||
resultCount = fulltextMatchIds.size();
|
resultCount = fulltextMatchIds.size();
|
||||||
} else if (myParams.getEverythingMode() != null) {
|
} else if (myParams.getEverythingMode() != null) {
|
||||||
fulltextMatchIds = queryHibernateSearchForEverythingPids(theRequest);
|
fulltextMatchIds = queryHibernateSearchForEverythingPids(theRequest);
|
||||||
|
@ -479,8 +510,9 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
|
|
||||||
if (canSkipDatabase) {
|
if (canSkipDatabase) {
|
||||||
ourLog.trace("Query finished after HSearch. Skip db query phase");
|
ourLog.trace("Query finished after HSearch. Skip db query phase");
|
||||||
if (theMaximumResults != null) {
|
if (theSearchProperties.hasMaxResultsRequested()) {
|
||||||
fulltextExecutor = SearchQueryExecutors.limited(fulltextExecutor, theMaximumResults);
|
fulltextExecutor = SearchQueryExecutors.limited(
|
||||||
|
fulltextExecutor, theSearchProperties.getMaxResultsRequested());
|
||||||
}
|
}
|
||||||
queries.add(fulltextExecutor);
|
queries.add(fulltextExecutor);
|
||||||
} else {
|
} else {
|
||||||
|
@ -493,13 +525,11 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
SearchBuilder.getMaximumPageSize(),
|
SearchBuilder.getMaximumPageSize(),
|
||||||
// for each list of (SearchBuilder.getMaximumPageSize())
|
// for each list of (SearchBuilder.getMaximumPageSize())
|
||||||
// we create a chunked query and add it to 'queries'
|
// we create a chunked query and add it to 'queries'
|
||||||
t -> doCreateChunkedQueries(
|
t -> doCreateChunkedQueries(theParams, t, theSearchProperties, theRequest, queries));
|
||||||
theParams, t, theOffset, sort, theCountOnlyFlag, theRequest, queries));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// do everything in the database.
|
// do everything in the database.
|
||||||
createChunkedQuery(
|
createChunkedQuery(theParams, theSearchProperties, theRequest, null, queries);
|
||||||
theParams, sort, theOffset, theMaximumResults, theCountOnlyFlag, theRequest, null, queries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return queries;
|
return queries;
|
||||||
|
@ -594,16 +624,16 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
private void doCreateChunkedQueries(
|
private void doCreateChunkedQueries(
|
||||||
SearchParameterMap theParams,
|
SearchParameterMap theParams,
|
||||||
List<Long> thePids,
|
List<Long> thePids,
|
||||||
Integer theOffset,
|
SearchQueryProperties theSearchQueryProperties,
|
||||||
SortSpec sort,
|
|
||||||
boolean theCount,
|
|
||||||
RequestDetails theRequest,
|
RequestDetails theRequest,
|
||||||
ArrayList<ISearchQueryExecutor> theQueries) {
|
ArrayList<ISearchQueryExecutor> theQueries) {
|
||||||
|
|
||||||
if (thePids.size() < getMaximumPageSize()) {
|
if (thePids.size() < getMaximumPageSize()) {
|
||||||
thePids = normalizeIdListForInClause(thePids);
|
thePids = normalizeIdListForInClause(thePids);
|
||||||
}
|
}
|
||||||
createChunkedQuery(theParams, sort, theOffset, thePids.size(), theCount, theRequest, thePids, theQueries);
|
// TODO - thesize was the 4th parameter... what is it supposed to be in createchunkedquery?
|
||||||
|
theSearchQueryProperties.setMaxResultsRequested(theParams.size());
|
||||||
|
createChunkedQuery(theParams, theSearchQueryProperties, theRequest, thePids, theQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -653,27 +683,21 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
|
|
||||||
private void createChunkedQuery(
|
private void createChunkedQuery(
|
||||||
SearchParameterMap theParams,
|
SearchParameterMap theParams,
|
||||||
SortSpec sort,
|
SearchQueryProperties theSearchProperties,
|
||||||
Integer theOffset,
|
|
||||||
Integer theMaximumResults,
|
|
||||||
boolean theCountOnlyFlag,
|
|
||||||
RequestDetails theRequest,
|
RequestDetails theRequest,
|
||||||
List<Long> thePidList,
|
List<Long> thePidList,
|
||||||
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||||
if (myParams.getEverythingMode() != null) {
|
if (myParams.getEverythingMode() != null) {
|
||||||
createChunkedQueryForEverythingSearch(
|
createChunkedQueryForEverythingSearch(theParams, theSearchProperties, thePidList, theSearchQueryExecutors);
|
||||||
theParams, theOffset, theMaximumResults, theCountOnlyFlag, thePidList, theSearchQueryExecutors);
|
|
||||||
} else {
|
} else {
|
||||||
createChunkedQueryNormalSearch(
|
createChunkedQueryNormalSearch(
|
||||||
theParams, sort, theOffset, theCountOnlyFlag, theRequest, thePidList, theSearchQueryExecutors);
|
theParams, theSearchProperties, theRequest, thePidList, theSearchQueryExecutors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createChunkedQueryNormalSearch(
|
private void createChunkedQueryNormalSearch(
|
||||||
SearchParameterMap theParams,
|
SearchParameterMap theParams,
|
||||||
SortSpec sort,
|
SearchQueryProperties theSearchProperties,
|
||||||
Integer theOffset,
|
|
||||||
boolean theCountOnlyFlag,
|
|
||||||
RequestDetails theRequest,
|
RequestDetails theRequest,
|
||||||
List<Long> thePidList,
|
List<Long> thePidList,
|
||||||
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||||
|
@ -685,7 +709,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
myResourceName,
|
myResourceName,
|
||||||
mySqlBuilderFactory,
|
mySqlBuilderFactory,
|
||||||
myDialectProvider,
|
myDialectProvider,
|
||||||
theCountOnlyFlag);
|
theSearchProperties.isDoCountOnlyFlag());
|
||||||
QueryStack queryStack3 = new QueryStack(
|
QueryStack queryStack3 = new QueryStack(
|
||||||
theParams, myStorageSettings, myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
|
theParams, myStorageSettings, myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
|
||||||
|
|
||||||
|
@ -762,7 +786,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
* if the MaxResultsToFetch is null, we are requesting "everything",
|
* if the MaxResultsToFetch is null, we are requesting "everything",
|
||||||
* so we'll let the db do the deduplication (instead of in-memory)
|
* so we'll let the db do the deduplication (instead of in-memory)
|
||||||
*/
|
*/
|
||||||
if (theOffset != null || (myMaxResultsToFetch == null && !theCountOnlyFlag)) {
|
if (theSearchProperties.isDeduplicateInDBFlag()) {
|
||||||
queryStack3.addGrouping();
|
queryStack3.addGrouping();
|
||||||
queryStack3.setUseAggregate(true);
|
queryStack3.setUseAggregate(true);
|
||||||
}
|
}
|
||||||
|
@ -773,33 +797,34 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
* If we have a sort, we wrap the criteria search (the search that actually
|
* If we have a sort, we wrap the criteria search (the search that actually
|
||||||
* finds the appropriate resources) in an outer search which is then sorted
|
* finds the appropriate resources) in an outer search which is then sorted
|
||||||
*/
|
*/
|
||||||
if (sort != null) {
|
if (theSearchProperties.hasSort()) {
|
||||||
assert !theCountOnlyFlag;
|
assert !theSearchProperties.isDoCountOnlyFlag();
|
||||||
|
|
||||||
createSort(queryStack3, sort, theParams);
|
createSort(queryStack3, theSearchProperties.getSortSpec(), theParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now perform the search
|
* Now perform the search
|
||||||
*/
|
*/
|
||||||
executeSearch(theOffset, theSearchQueryExecutors, sqlBuilder);
|
executeSearch(theSearchProperties, theSearchQueryExecutors, sqlBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeSearch(
|
private void executeSearch(
|
||||||
Integer theOffset, List<ISearchQueryExecutor> theSearchQueryExecutors, SearchQueryBuilder sqlBuilder) {
|
SearchQueryProperties theProperties,
|
||||||
GeneratedSql generatedSql = sqlBuilder.generate(theOffset, myMaxResultsToFetch);
|
List<ISearchQueryExecutor> theSearchQueryExecutors,
|
||||||
|
SearchQueryBuilder sqlBuilder) {
|
||||||
|
GeneratedSql generatedSql =
|
||||||
|
sqlBuilder.generate(theProperties.getOffset(), theProperties.getMaxResultsRequested());
|
||||||
if (!generatedSql.isMatchNothing()) {
|
if (!generatedSql.isMatchNothing()) {
|
||||||
SearchQueryExecutor executor =
|
SearchQueryExecutor executor =
|
||||||
mySqlBuilderFactory.newSearchQueryExecutor(generatedSql, myMaxResultsToFetch);
|
mySqlBuilderFactory.newSearchQueryExecutor(generatedSql, theProperties.getMaxResultsRequested());
|
||||||
theSearchQueryExecutors.add(executor);
|
theSearchQueryExecutors.add(executor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createChunkedQueryForEverythingSearch(
|
private void createChunkedQueryForEverythingSearch(
|
||||||
SearchParameterMap theParams,
|
SearchParameterMap theParams,
|
||||||
Integer theOffset,
|
SearchQueryProperties theSearchQueryProperties,
|
||||||
Integer theMaximumResults,
|
|
||||||
boolean theCountOnlyFlag,
|
|
||||||
List<Long> thePidList,
|
List<Long> thePidList,
|
||||||
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||||
|
|
||||||
|
@ -811,12 +836,12 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
null,
|
null,
|
||||||
mySqlBuilderFactory,
|
mySqlBuilderFactory,
|
||||||
myDialectProvider,
|
myDialectProvider,
|
||||||
theCountOnlyFlag);
|
theSearchQueryProperties.isDoCountOnlyFlag());
|
||||||
|
|
||||||
QueryStack queryStack3 = new QueryStack(
|
QueryStack queryStack3 = new QueryStack(
|
||||||
theParams, myStorageSettings, myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
|
theParams, myStorageSettings, myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
|
||||||
|
|
||||||
JdbcTemplate jdbcTemplate = initializeJdbcTemplate(theMaximumResults);
|
JdbcTemplate jdbcTemplate = initializeJdbcTemplate(theSearchQueryProperties.getMaxResultsRequested());
|
||||||
|
|
||||||
Set<Long> targetPids = new HashSet<>();
|
Set<Long> targetPids = new HashSet<>();
|
||||||
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
||||||
|
@ -839,8 +864,9 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
myResourceName,
|
myResourceName,
|
||||||
mySqlBuilderFactory,
|
mySqlBuilderFactory,
|
||||||
myDialectProvider,
|
myDialectProvider,
|
||||||
theCountOnlyFlag);
|
theSearchQueryProperties.isDoCountOnlyFlag());
|
||||||
GeneratedSql allTargetsSql = fetchPidsSqlBuilder.generate(theOffset, myMaxResultsToFetch);
|
GeneratedSql allTargetsSql = fetchPidsSqlBuilder.generate(
|
||||||
|
theSearchQueryProperties.getOffset(), mySearchProperties.getMaxResultsRequested());
|
||||||
String sql = allTargetsSql.getSql();
|
String sql = allTargetsSql.getSql();
|
||||||
Object[] args = allTargetsSql.getBindVariables().toArray(new Object[0]);
|
Object[] args = allTargetsSql.getBindVariables().toArray(new Object[0]);
|
||||||
|
|
||||||
|
@ -874,7 +900,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
* If offset is present, we want deduplicate the results by using GROUP BY
|
* If offset is present, we want deduplicate the results by using GROUP BY
|
||||||
* ORDER BY is required to make sure we return unique results for each page
|
* ORDER BY is required to make sure we return unique results for each page
|
||||||
*/
|
*/
|
||||||
if (theOffset != null) {
|
if (theSearchQueryProperties.hasOffset()) {
|
||||||
queryStack3.addGrouping();
|
queryStack3.addGrouping();
|
||||||
queryStack3.addOrdering();
|
queryStack3.addOrdering();
|
||||||
queryStack3.setUseAggregate(true);
|
queryStack3.setUseAggregate(true);
|
||||||
|
@ -883,7 +909,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
/*
|
/*
|
||||||
* Now perform the search
|
* Now perform the search
|
||||||
*/
|
*/
|
||||||
executeSearch(theOffset, theSearchQueryExecutors, sqlBuilder);
|
executeSearch(theSearchQueryProperties, theSearchQueryExecutors, sqlBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPidListPredicate(List<Long> thePidList, SearchQueryBuilder theSqlBuilder) {
|
private void addPidListPredicate(List<Long> thePidList, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
@ -2412,15 +2438,15 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
|
|
||||||
// If we don't have a query yet, create one
|
// If we don't have a query yet, create one
|
||||||
if (myResultsIterator == null) {
|
if (myResultsIterator == null) {
|
||||||
if (myMaxResultsToFetch == null) {
|
if (!mySearchProperties.hasMaxResultsRequested()) {
|
||||||
myMaxResultsToFetch = calculateMaxResultsToFetch();
|
mySearchProperties.setMaxResultsRequested(calculateMaxResultsToFetch());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* assigns the results iterator
|
* assigns the results iterator
|
||||||
* and populates the myQueryList.
|
* and populates the myQueryList.
|
||||||
*/
|
*/
|
||||||
initializeIteratorQuery(myOffset, myMaxResultsToFetch);
|
initializeIteratorQuery(myOffset, mySearchProperties.getMaxResultsRequested());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myNext == null) {
|
if (myNext == null) {
|
||||||
|
@ -2454,7 +2480,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
JpaPid next = JpaPid.fromId(nextLong);
|
JpaPid next = JpaPid.fromId(nextLong);
|
||||||
|
|
||||||
if (!myPidSet.contains(next)) {
|
if (!myPidSet.contains(next)) {
|
||||||
if (myMaxResultsToFetch != null) {
|
if (mySearchProperties.hasMaxResultsRequested()) {
|
||||||
/*
|
/*
|
||||||
* We only add to the map if we aren't fetching "everything";
|
* We only add to the map if we aren't fetching "everything";
|
||||||
* otherwise, we let the de-duplication happen in the database
|
* otherwise, we let the de-duplication happen in the database
|
||||||
|
@ -2474,13 +2500,13 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!myResultsIterator.hasNext()) {
|
if (!myResultsIterator.hasNext()) {
|
||||||
if (myMaxResultsToFetch != null && (mySkipCount + myNonSkipCount == myMaxResultsToFetch)) {
|
if (mySearchProperties.hasMaxResultsRequested()
|
||||||
|
&& (mySkipCount + myNonSkipCount == mySearchProperties.getMaxResultsRequested())) {
|
||||||
if (mySkipCount > 0 && myNonSkipCount == 0) {
|
if (mySkipCount > 0 && myNonSkipCount == 0) {
|
||||||
|
|
||||||
sendProcessingMsgAndFirePerformanceHook();
|
sendProcessingMsgAndFirePerformanceHook();
|
||||||
|
int maxResults = mySearchProperties.getMaxResultsRequested() + 1000;
|
||||||
myMaxResultsToFetch += 1000;
|
mySearchProperties.setMaxResultsRequested(maxResults);
|
||||||
initializeIteratorQuery(myOffset, myMaxResultsToFetch);
|
initializeIteratorQuery(myOffset, mySearchProperties.getMaxResultsRequested());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2510,7 +2536,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myMaxResultsToFetch == null) {
|
if (!mySearchProperties.hasMaxResultsRequested()) {
|
||||||
mySearchRuntimeDetails.setFoundIndexMatchesCount(myNonSkipCount);
|
mySearchRuntimeDetails.setFoundIndexMatchesCount(myNonSkipCount);
|
||||||
} else {
|
} else {
|
||||||
mySearchRuntimeDetails.setFoundMatchesCount(myPidSet.size());
|
mySearchRuntimeDetails.setFoundMatchesCount(myPidSet.size());
|
||||||
|
@ -2571,7 +2597,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
String msg = "Pass completed with no matching results seeking rows "
|
String msg = "Pass completed with no matching results seeking rows "
|
||||||
+ myPidSet.size() + "-" + mySkipCount
|
+ myPidSet.size() + "-" + mySkipCount
|
||||||
+ ". This indicates an inefficient query! Retrying with new max count of "
|
+ ". This indicates an inefficient query! Retrying with new max count of "
|
||||||
+ myMaxResultsToFetch;
|
+ mySearchProperties.getMaxResultsRequested();
|
||||||
firePerformanceWarning(myRequest, msg);
|
firePerformanceWarning(myRequest, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2586,8 +2612,13 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
||||||
if (myParams.getEverythingMode() != null) {
|
if (myParams.getEverythingMode() != null) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
myQueryList = createQuery(
|
|
||||||
myParams, mySort, offset, theMaxResultsToFetch, false, myRequest, mySearchRuntimeDetails);
|
SearchQueryProperties properties = mySearchProperties.clone();
|
||||||
|
properties
|
||||||
|
.setOffset(offset)
|
||||||
|
.setMaxResultsRequested(theMaxResultsToFetch)
|
||||||
|
.setDoCountOnlyFlag(false);
|
||||||
|
myQueryList = createQuery(myParams, properties, myRequest, mySearchRuntimeDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
|
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package ca.uhn.fhir.jpa.search.builder.models;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.SortSpec;
|
||||||
|
|
||||||
|
public class SearchQueryProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if this query is only to fetch the count (and not any results).
|
||||||
|
*
|
||||||
|
* True means this is a count only query
|
||||||
|
*/
|
||||||
|
private boolean myDoCountOnlyFlag;
|
||||||
|
/**
|
||||||
|
* Whether or not we do deduplication of results in memory
|
||||||
|
* (using a hashset, etc), or push this to the database
|
||||||
|
* (using GROUP BY, etc).
|
||||||
|
*
|
||||||
|
* True means use the database
|
||||||
|
*/
|
||||||
|
private boolean myDeduplicateInDBFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of results to fetch (when we want it limited).
|
||||||
|
* Can be null if we are fetching everything or paging.
|
||||||
|
*/
|
||||||
|
private Integer myMaxResultsRequested;
|
||||||
|
/**
|
||||||
|
* The offset for the results to fetch.
|
||||||
|
*
|
||||||
|
* null if the first page, some number if it's a later page
|
||||||
|
*/
|
||||||
|
private Integer myOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sort spec for this search
|
||||||
|
*/
|
||||||
|
private SortSpec mySortSpec;
|
||||||
|
|
||||||
|
public boolean isDoCountOnlyFlag() {
|
||||||
|
return myDoCountOnlyFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchQueryProperties setDoCountOnlyFlag(boolean theDoCountOnlyFlag) {
|
||||||
|
myDoCountOnlyFlag = theDoCountOnlyFlag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeduplicateInDBFlag() {
|
||||||
|
return myDeduplicateInDBFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchQueryProperties setDeduplicateInDBFlag(boolean theDeduplicateInDBFlag) {
|
||||||
|
myDeduplicateInDBFlag = theDeduplicateInDBFlag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMaxResultsRequested() {
|
||||||
|
return myMaxResultsRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchQueryProperties setMaxResultsRequested(Integer theMaxResultsRequested) {
|
||||||
|
myMaxResultsRequested = theMaxResultsRequested;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMaxResultsRequested() {
|
||||||
|
return myMaxResultsRequested != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOffset() {
|
||||||
|
return myOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOffset() {
|
||||||
|
return myOffset != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchQueryProperties setOffset(Integer theOffset) {
|
||||||
|
myOffset = theOffset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SortSpec getSortSpec() {
|
||||||
|
return mySortSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSort() {
|
||||||
|
return mySortSpec != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchQueryProperties setSortSpec(SortSpec theSortSpec) {
|
||||||
|
mySortSpec = theSortSpec;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchQueryProperties clone() {
|
||||||
|
return new SearchQueryProperties()
|
||||||
|
.setMaxResultsRequested(myMaxResultsRequested)
|
||||||
|
.setSortSpec(mySortSpec)
|
||||||
|
.setOffset(myOffset)
|
||||||
|
.setDoCountOnlyFlag(myDoCountOnlyFlag)
|
||||||
|
.setDeduplicateInDBFlag(myDeduplicateInDBFlag);
|
||||||
|
}
|
||||||
|
}
|
|
@ -510,7 +510,6 @@ public class SearchQueryBuilder {
|
||||||
* Generate and return the SQL generated by this builder
|
* Generate and return the SQL generated by this builder
|
||||||
*/
|
*/
|
||||||
public GeneratedSql generate(@Nullable Integer theOffset, @Nullable Integer theMaxResultsToFetch) {
|
public GeneratedSql generate(@Nullable Integer theOffset, @Nullable Integer theMaxResultsToFetch) {
|
||||||
|
|
||||||
getOrCreateFirstPredicateBuilder();
|
getOrCreateFirstPredicateBuilder();
|
||||||
|
|
||||||
mySelect.validate();
|
mySelect.validate();
|
||||||
|
|
|
@ -597,6 +597,7 @@ public class SearchTask implements Callable<Void> {
|
||||||
|
|
||||||
if (next == -1) {
|
if (next == -1) {
|
||||||
sb.setMaxResultsToFetch(null);
|
sb.setMaxResultsToFetch(null);
|
||||||
|
sb.setShouldDeduplicateInDB(true);
|
||||||
} else {
|
} else {
|
||||||
// we want at least 1 more than our requested amount
|
// we want at least 1 more than our requested amount
|
||||||
// so we know that there are other results
|
// so we know that there are other results
|
||||||
|
|
|
@ -4072,7 +4072,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
ourLog.info(StringUtils.join(names, '\n'));
|
ourLog.info(StringUtils.join(names, '\n'));
|
||||||
|
|
||||||
assertThat(names).containsExactly("Daniel Adams", "Aaron Alexis", "Carol Allen", "Ruth Black", "Brian Brooks", "Amy Clark", "Susan Clark", "Anthony Coleman", "Lisa Coleman", "Steven Coleman", "Ruth Cook", "Betty Davis", "Joshua Diaz", "Brian Gracia", "Sarah Graham", "Stephan Graham");
|
assertThat(names).containsExactly("Daniel Adams", "Aaron Alexis", "Carol Allen", "Ruth Black", "Brian Brooks", "Amy Clark", "Susan Clark", "Anthony Coleman", "Lisa Coleman", "Steven Coleman", "Ruth Cook", "Betty Davis", "Joshua Diaz", "Brian Gracia", "Sarah Graham", "Stephan Graham");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -64,44 +64,44 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
new SqlGenerationTestCase(
|
new SqlGenerationTestCase(
|
||||||
"single string - no hfj_resource root",
|
"single string - no hfj_resource root",
|
||||||
"Patient?name=FOO",
|
"Patient?name=FOO",
|
||||||
"SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))",
|
"SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) fetch first ? rows only",
|
||||||
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.PARTITION_ID = ?) AND ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)))"
|
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.PARTITION_ID = ?) AND ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))) fetch first ? rows only"
|
||||||
)
|
)
|
||||||
, new SqlGenerationTestCase(
|
, new SqlGenerationTestCase(
|
||||||
"two regular params - should use hfj_resource as root",
|
"two regular params - should use hfj_resource as root",
|
||||||
"Patient?name=smith&active=true",
|
"Patient?name=smith&active=true",
|
||||||
"SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_VALUE = ?))",
|
"SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_VALUE = ?)) fetch first ? rows only",
|
||||||
"SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) INNER JOIN HFJ_SPIDX_TOKEN t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID)) WHERE (((t0.PARTITION_ID = ?) AND ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))) AND ((t2.PARTITION_ID = ?) AND (t2.HASH_VALUE = ?)))"
|
"SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) INNER JOIN HFJ_SPIDX_TOKEN t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID)) WHERE (((t0.PARTITION_ID = ?) AND ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))) AND ((t2.PARTITION_ID = ?) AND (t2.HASH_VALUE = ?))) fetch first ? rows only"
|
||||||
)
|
)
|
||||||
, new SqlGenerationTestCase(
|
, new SqlGenerationTestCase(
|
||||||
"token not as a NOT IN subselect",
|
"token not as a NOT IN subselect",
|
||||||
"Encounter?class:not=not-there",
|
"Encounter?class:not=not-there",
|
||||||
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.RES_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))",
|
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.RES_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) )) fetch first ? rows only",
|
||||||
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.PARTITION_ID = ?) AND ((t0.PARTITION_ID,t0.RES_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) )))"
|
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.PARTITION_ID = ?) AND ((t0.PARTITION_ID,t0.RES_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))) fetch first ? rows only"
|
||||||
)
|
)
|
||||||
, new SqlGenerationTestCase(
|
, new SqlGenerationTestCase(
|
||||||
"token not on chain join - NOT IN from hfj_res_link target columns",
|
"token not on chain join - NOT IN from hfj_res_link target columns",
|
||||||
"Observation?encounter.class:not=not-there",
|
"Observation?encounter.class:not=not-there",
|
||||||
"SELECT t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))",
|
"SELECT t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) )) fetch first ? rows only",
|
||||||
"SELECT t0.PARTITION_ID,t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.PARTITION_ID = ?) AND ((t0.TARGET_RES_PARTITION_ID,t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) )))"
|
"SELECT t0.PARTITION_ID,t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.PARTITION_ID = ?) AND ((t0.TARGET_RES_PARTITION_ID,t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))) fetch first ? rows only"
|
||||||
)
|
)
|
||||||
, new SqlGenerationTestCase(
|
, new SqlGenerationTestCase(
|
||||||
"bare sort",
|
"bare sort",
|
||||||
"Patient?_sort=name",
|
"Patient?_sort=name",
|
||||||
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST",
|
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST fetch first ? rows only",
|
||||||
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.PARTITION_ID = ?)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST"
|
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.PARTITION_ID = ?)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST fetch first ? rows only"
|
||||||
)
|
)
|
||||||
, new SqlGenerationTestCase(
|
, new SqlGenerationTestCase(
|
||||||
"sort with predicate",
|
"sort with predicate",
|
||||||
"Patient?active=true&_sort=name",
|
"Patient?active=true&_sort=name",
|
||||||
"SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON (t1.RES_ID = t0.RES_ID) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (t0.HASH_VALUE = ?) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST",
|
"SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON (t1.RES_ID = t0.RES_ID) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (t0.HASH_VALUE = ?) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST fetch first ? rows only",
|
||||||
"SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE ((t0.PARTITION_ID = ?) AND (t0.HASH_VALUE = ?)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST"
|
"SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE ((t0.PARTITION_ID = ?) AND (t0.HASH_VALUE = ?)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST fetch first ? rows only"
|
||||||
)
|
)
|
||||||
, new SqlGenerationTestCase(
|
, new SqlGenerationTestCase(
|
||||||
"chained sort",
|
"chained sort",
|
||||||
"Patient?_sort=Practitioner:general-practitioner.name",
|
"Patient?_sort=Practitioner:general-practitioner.name",
|
||||||
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_RES_LINK t1 ON ((t0.RES_ID = t1.SRC_RESOURCE_ID) AND (t1.SRC_PATH = ?)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.TARGET_RESOURCE_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST",
|
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_RES_LINK t1 ON ((t0.RES_ID = t1.SRC_RESOURCE_ID) AND (t1.SRC_PATH = ?)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.TARGET_RESOURCE_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST fetch first ? rows only",
|
||||||
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_RES_LINK t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.SRC_RESOURCE_ID) AND (t1.SRC_PATH = ?)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.TARGET_RES_PARTITION_ID = t2.PARTITION_ID) AND (t1.TARGET_RESOURCE_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.PARTITION_ID = ?)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST"
|
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_RES_LINK t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.SRC_RESOURCE_ID) AND (t1.SRC_PATH = ?)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.TARGET_RES_PARTITION_ID = t2.PARTITION_ID) AND (t1.TARGET_RESOURCE_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.PARTITION_ID = ?)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST fetch first ? rows only"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
myPatientDao.search(map);
|
myPatientDao.search(map);
|
||||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||||
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))", sql);
|
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) fetch first ? rows only", sql);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,6 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTwoRegularSearchParams() {
|
public void testTwoRegularSearchParams() {
|
||||||
|
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
SearchParameterMap map = SearchParameterMap.newSynchronous()
|
SearchParameterMap map = SearchParameterMap.newSynchronous()
|
||||||
.add(Patient.SP_NAME, new StringParam("FOO"))
|
.add(Patient.SP_NAME, new StringParam("FOO"))
|
||||||
|
@ -170,14 +169,11 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
myPatientDao.search(map);
|
myPatientDao.search(map);
|
||||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||||
assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_SYS_AND_VALUE = ?))", sql);
|
assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_SYS_AND_VALUE = ?)) fetch first ? rows only", sql);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchByProfile_VersionedMode() {
|
public void testSearchByProfile_VersionedMode() {
|
||||||
|
|
||||||
// Put a tag in so we can search for it
|
// Put a tag in so we can search for it
|
||||||
String code = "http://" + UUID.randomUUID();
|
String code = "http://" + UUID.randomUUID();
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
@ -193,7 +189,7 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
assertEquals(3, myCaptureQueriesListener.countSelectQueries());
|
assertEquals(3, myCaptureQueriesListener.countSelectQueries());
|
||||||
// Query 1 - Find resources: Make sure we search for tag type+system+code always
|
// Query 1 - Find resources: Make sure we search for tag type+system+code always
|
||||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||||
assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 INNER JOIN HFJ_RES_TAG t1 ON (t0.RES_ID = t1.RES_ID) INNER JOIN HFJ_TAG_DEF t2 ON (t1.TAG_ID = t2.TAG_ID) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t2.TAG_TYPE = ?) AND (t2.TAG_SYSTEM = ?) AND (t2.TAG_CODE = ?)))", sql);
|
assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 INNER JOIN HFJ_RES_TAG t1 ON (t0.RES_ID = t1.RES_ID) INNER JOIN HFJ_TAG_DEF t2 ON (t1.TAG_ID = t2.TAG_ID) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t2.TAG_TYPE = ?) AND (t2.TAG_SYSTEM = ?) AND (t2.TAG_CODE = ?))) fetch first ? rows only", sql);
|
||||||
// Query 2 - Load resourece contents
|
// Query 2 - Load resourece contents
|
||||||
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(false, false);
|
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(false, false);
|
||||||
assertThat(sql).contains("where rsv1_0.RES_ID in (?)");
|
assertThat(sql).contains("where rsv1_0.RES_ID in (?)");
|
||||||
|
@ -202,7 +198,6 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
assertThat(sql).contains("from HFJ_RES_TAG rt1_0 join HFJ_TAG_DEF");
|
assertThat(sql).contains("from HFJ_RES_TAG rt1_0 join HFJ_TAG_DEF");
|
||||||
|
|
||||||
assertThat(toUnqualifiedVersionlessIds(outcome)).containsExactly(id);
|
assertThat(toUnqualifiedVersionlessIds(outcome)).containsExactly(id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -229,9 +224,11 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
.add(Constants.PARAM_PROFILE, new UriParam(code));
|
.add(Constants.PARAM_PROFILE, new UriParam(code));
|
||||||
IBundleProvider outcome = myPatientDao.search(map, mySrd);
|
IBundleProvider outcome = myPatientDao.search(map, mySrd);
|
||||||
assertEquals(2, myCaptureQueriesListener.countSelectQueries());
|
assertEquals(2, myCaptureQueriesListener.countSelectQueries());
|
||||||
|
|
||||||
// Query 1 - Find resources: Just a standard token search in this mode
|
// Query 1 - Find resources: Just a standard token search in this mode
|
||||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||||
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_URI t0 WHERE (t0.HASH_URI = ?)", sql);
|
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_URI t0 WHERE (t0.HASH_URI = ?) fetch first ? rows only", sql);
|
||||||
|
|
||||||
// Query 2 - Load resourece contents
|
// Query 2 - Load resourece contents
|
||||||
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(false, false);
|
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(false, false);
|
||||||
assertThat(sql).contains("where rsv1_0.RES_ID in (?)");
|
assertThat(sql).contains("where rsv1_0.RES_ID in (?)");
|
||||||
|
@ -255,11 +252,10 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
if (theIncludeHashIdentity) {
|
if (theIncludeHashIdentity) {
|
||||||
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE ((t0.HASH_IDENTITY = '7001889285610424179') AND (t0.HASH_SYS_AND_VALUE = '-2780914544385068076'))", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
|
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE ((t0.HASH_IDENTITY = '7001889285610424179') AND (t0.HASH_SYS_AND_VALUE = '-2780914544385068076')) fetch first '10000' rows only", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
|
||||||
} else {
|
} else {
|
||||||
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_SYS_AND_VALUE = '-2780914544385068076')", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
|
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_SYS_AND_VALUE = '-2780914544385068076') fetch first '10000' rows only", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MyPartitionInterceptor {
|
public static class MyPartitionInterceptor {
|
||||||
|
|
|
@ -77,6 +77,15 @@ public interface ISearchBuilder<T extends IResourcePersistentId<?>> {
|
||||||
|
|
||||||
void setMaxResultsToFetch(Integer theMaxResultsToFetch);
|
void setMaxResultsToFetch(Integer theMaxResultsToFetch);
|
||||||
|
|
||||||
|
void setShouldDeduplicateInDB(boolean theShouldDeduplicateInDB);
|
||||||
|
|
||||||
|
void setRequireTotal(boolean theRequireTotal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the results should have a 'total' value
|
||||||
|
*/
|
||||||
|
boolean requiresTotal();
|
||||||
|
|
||||||
void loadResourcesByPid(
|
void loadResourcesByPid(
|
||||||
Collection<T> thePids,
|
Collection<T> thePids,
|
||||||
Collection<T> theIncludedPids,
|
Collection<T> theIncludedPids,
|
||||||
|
|
Loading…
Reference in New Issue