Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* (C) Copyright IBM Corp. 2017, 2022
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.fhir.persistence.jdbc.util;
import static com.ibm.fhir.config.FHIRConfiguration.PROPERTY_SEARCH_ENABLE_LEGACY_WHOLE_SYSTEM_SEARCH_PARAMS;
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.EQ;
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.LIKE;
import static com.ibm.fhir.persistence.jdbc.JDBCConstants.modifierOperatorMap;
import static com.ibm.fhir.search.SearchConstants.ID;
import static com.ibm.fhir.search.SearchConstants.LAST_UPDATED;
import static com.ibm.fhir.search.SearchConstants.PROFILE;
import static com.ibm.fhir.search.SearchConstants.SECURITY;
import static com.ibm.fhir.search.SearchConstants.TAG;
import static com.ibm.fhir.search.SearchConstants.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.ibm.fhir.config.FHIRConfigHelper;
import com.ibm.fhir.database.utils.api.IDatabaseTranslator;
import com.ibm.fhir.database.utils.query.Select;
import com.ibm.fhir.model.resource.Location;
import com.ibm.fhir.model.resource.Resource;
import com.ibm.fhir.model.util.ModelSupport;
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
import com.ibm.fhir.persistence.exception.FHIRPersistenceNotSupportedException;
import com.ibm.fhir.persistence.jdbc.connection.QueryHints;
import com.ibm.fhir.persistence.jdbc.dao.api.JDBCIdentityCache;
import com.ibm.fhir.persistence.jdbc.domain.CanonicalSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.ChainedSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.CompositeSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.DateSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.DomainSortParameter;
import com.ibm.fhir.persistence.jdbc.domain.IdSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.InclusionSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.LastUpdatedSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.LocationSearchExtension;
import com.ibm.fhir.persistence.jdbc.domain.LocationSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.MissingSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.NumberSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.QuantitySearchParam;
import com.ibm.fhir.persistence.jdbc.domain.QueryData;
import com.ibm.fhir.persistence.jdbc.domain.ReferenceSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.SearchCountQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchDataQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchIncludeQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchQueryRenderer;
import com.ibm.fhir.persistence.jdbc.domain.SearchSortQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchWholeSystemDataQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchWholeSystemFilterQuery;
import com.ibm.fhir.persistence.jdbc.domain.SearchWholeSystemQuery;
import com.ibm.fhir.persistence.jdbc.domain.SecuritySearchParam;
import com.ibm.fhir.persistence.jdbc.domain.StringSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.TagSearchParam;
import com.ibm.fhir.persistence.jdbc.domain.TokenSearchParam;
import com.ibm.fhir.search.SearchConstants;
import com.ibm.fhir.search.SearchConstants.Modifier;
import com.ibm.fhir.search.SearchConstants.Prefix;
import com.ibm.fhir.search.SearchConstants.Type;
import com.ibm.fhir.search.context.FHIRSearchContext;
import com.ibm.fhir.search.location.util.LocationUtil;
import com.ibm.fhir.search.parameters.InclusionParameter;
import com.ibm.fhir.search.parameters.QueryParameter;
import com.ibm.fhir.search.parameters.QueryParameterValue;
import com.ibm.fhir.search.parameters.SortParameter;
/**
* This is the JDBC implementation of a query builder for the IBM FHIR Server
* JDBC persistence layer schema.
* The builder constructs an intermediate "domain" model of the query (which isn't
* concerned about the intricacies of how to join two tables). This domain query
* is then translated in a Select statement model which can be built piece by piece.
* The statement is then rendered into a string which is the SQL select statement
* executed by the database.
*
* This approach improves maintainability by separating the logical structure
* of the query from the physical join syntax.
*
*
* Useful table reference:
*
*
* ------------------------
* PARAMETER_NAMES reference table of parameter names
* RESOURCE_TYPES reference table of resource type names
* COMMON_TOKEN_VALUES normalized set of token values
* CODE_SYSTEMS normalized set of code-system values
* LOGICAL_RESOURCES whole-system table of resources
* xx_LOGICAL_RESOURCES resource-specific table of logical resources
* xx_RESOURCES resource-specific table of resource versions
* xx_STR_VALUES string search parameters belonging to a resource
* xx_RESOURCE_TOKEN_REFS map table connecting a given resource to a set of token values
* xx_NUMBER_VALUES number search parameters belonging to a resource
* xx_DATE_VALUES date search parameters belonging to a resource
* xx_LATLNG_VALUES lat/lng search parameters belonging to a resource
* xx_QUANTITY_VALUES quantity search parameters belonging to a resource
* xx_TOKEN_VALUES_V view hiding the join between xx_RESOURCE_TOKEN_REFS and COMMON_TOKEN_VALUES
*
* Useful column reference:
*
*
* ------------------------
* RESOURCE_TYPE_NAME the formal name of the resource type e.g. 'Patient'
* RESOURCE_TYPE_ID FK to the RESOURCE_TYPES table
* LOGICAL_ID the VARCHAR holding the logical-id of the resource. Unique for a given resource type
* LOGICAL_RESOURCE_ID the database BIGINT
* CURRENT_RESOURCE_ID the unique BIGINT id of the latest resource version for the logical resource
* VERSION_ID INT resource version number incrementing by 1
* IS_DELETED CHAR(1) flag indicating the current deletion status of the resource or resource-version.
* RESOURCE_ID the PK of the version-specific resource. Now only used as the target for CURRENT_RESOURCE_ID
*
*/
public class NewQueryBuilder {
private static final Logger log = java.util.logging.Logger.getLogger(NewQueryBuilder.class.getName());
private static final String CLASSNAME = NewQueryBuilder.class.getName();
// Database translator to handle SQL syntax variations among databases
private final IDatabaseTranslator translator;
// For id lookups
private final JDBCIdentityCache identityCache;
// Hints to use for certain queries
private final QueryHints queryHints;
// Enable use of legacy whole-system search parameters for the search request
private final boolean legacyWholeSystemSearchParamsEnabled;
/**
* Public constructor
* @param translator
* @param queryHints
* @param identityCache
*/
public NewQueryBuilder(IDatabaseTranslator translator, QueryHints queryHints, JDBCIdentityCache identityCache) {
this.translator = translator;
this.queryHints = queryHints;
this.identityCache = identityCache;
this.legacyWholeSystemSearchParamsEnabled =
FHIRConfigHelper.getBooleanProperty(PROPERTY_SEARCH_ENABLE_LEGACY_WHOLE_SYSTEM_SEARCH_PARAMS, false);
}
/**
* Builds a query that returns the count of the search results that would be
* found by applying the search parameters
* contained within the passed search context.
*
* The count query is a simpler version of the main search query because
* there's no need to join against the xx_RESOURCES table at the end because
* the DATA column is not needed. This is now possible because the IS_DELETED
* flag is now denormalized and stored at the xx_LOGICAL_RESOURCES level as
* well as per resource version (in the xx_RESOURCES table).
*
* @param resourceType
* - The type of resource being searched for.
* @param searchContext
* - The search context containing the search parameters.
* @return String - A count query SQL statement
* @throws Exception
*/
public Select buildCountQuery(Class> resourceType, FHIRSearchContext searchContext) throws Exception {
final String METHODNAME = "buildCountQuery";
log.entering(CLASSNAME, METHODNAME,
new Object[] { resourceType.getSimpleName(), searchContext.getSearchParameters() });
final SearchQuery domainModel;
if (Resource.class.equals(resourceType)) {
// Whole-system search
if (allSearchParmsAreGlobal(searchContext.getSearchParameters())) {
// Can do query against global tables
domainModel = new SearchCountQuery(resourceType.getSimpleName());
if (searchContext.getSearchResourceTypes() != null) {
// The _type parameter was specified, so we need to filter the
// query by resource type. Add an extension to the domain model
// with the specified resource type IDs.
addResourceTypeExtension(domainModel, searchContext.getSearchResourceTypes());
}
} else {
// Not all search parameters are global (values indexed into global values tables).
// Need to do old-style UNION'd query against all resource types.
List resourceTypes = searchContext.getSearchResourceTypes();
if (resourceTypes == null) {
// The _type parameter was not specified, so we need to generate a list
// of all supported resource types for our UNION query.
resourceTypes = this.identityCache.getResourceTypeNames();
resourceTypes.remove("Resource");
resourceTypes.remove("DomainResource");
}
// Create a domain model for each resource type
List subDomainModels = new ArrayList<>();
for (String domainResourceType : resourceTypes) {
SearchQuery subDomainModel = new SearchCountQuery(domainResourceType);
buildModelCommon(subDomainModel, ModelSupport.getResourceType(domainResourceType), searchContext);
subDomainModels.add(subDomainModel);
}
// Create a wrapper whole-system search domain model
domainModel = new SearchWholeSystemQuery(subDomainModels, true, false);
}
} else {
domainModel = new SearchCountQuery(resourceType.getSimpleName());
}
buildModelCommon(domainModel, resourceType, searchContext);
Select result = renderQuery(domainModel, searchContext);
log.exiting(CLASSNAME, METHODNAME);
return result;
}
/**
* Render the domain model into a Select statement
* @param domainModel
* @return
*/
private Select renderQuery(SearchQuery domainModel, FHIRSearchContext searchContext) throws FHIRPersistenceException {
final int offset = (searchContext.getPageNumber()-1) * searchContext.getPageSize();
final int rowsPerPage = searchContext.getPageSize();
SearchQueryRenderer renderer = new SearchQueryRenderer(this.translator, this.identityCache, offset, rowsPerPage, searchContext.isIncludeResourceData());
QueryData queryData = domainModel.visit(renderer);
return queryData.getQuery().build();
}
/**
* Construct a FHIR search query
* @param resourceType
* @param searchContext
* @return
* @throws Exception
*/
public Select buildQuery(Class> resourceType, FHIRSearchContext searchContext) throws Exception {
final String METHODNAME = "buildQuery";
log.entering(CLASSNAME, METHODNAME,
new Object[] { resourceType.getSimpleName(), searchContext.getSearchParameters() });
final SearchQuery domainModel;
if (Resource.class.equals(resourceType)) {
// Whole-system search
if (allSearchParmsAreGlobal(searchContext.getSearchParameters())) {
// Can do a filter query against global tables
SearchWholeSystemFilterQuery wholeSystemFilterQuery = new SearchWholeSystemFilterQuery();
for (SortParameter sp: searchContext.getSortParameters()) {
wholeSystemFilterQuery.add(new DomainSortParameter(sp));
}
if (searchContext.getSearchResourceTypes() != null) {
// The _type parameter was specified, so we need to filter the
// query by resource type. Add an extension to the domain model
// with the specified resource type IDs.
addResourceTypeExtension(wholeSystemFilterQuery, searchContext.getSearchResourceTypes());
}
domainModel = wholeSystemFilterQuery;
} else {
// Not all search parameters are global (values indexed into global values tables).
// Need to do old-style UNION'd query against all resource types.
List resourceTypes = searchContext.getSearchResourceTypes();
if (resourceTypes == null) {
// The _type parameter was not specified, so we need to generate a list
// of all supported resource types for our UNION query.
resourceTypes = this.identityCache.getResourceTypeNames();
resourceTypes.remove("Resource");
resourceTypes.remove("DomainResource");
}
// Create a domain model for each resource type
List subDomainModels = new ArrayList<>();
for (String domainResourceType : resourceTypes) {
int domainResourceTypeId = identityCache.getResourceTypeId(domainResourceType);
SearchQuery subDomainModel = new SearchDataQuery(domainResourceType, false, false, domainResourceTypeId);
buildModelCommon(subDomainModel, ModelSupport.getResourceType(domainResourceType), searchContext);
subDomainModels.add(subDomainModel);
}
// Create a wrapper whole-system search domain model
SearchWholeSystemQuery wholeSystemAllDataQuery =
new SearchWholeSystemQuery(subDomainModels, false, true);
for (SortParameter sp: searchContext.getSortParameters()) {
wholeSystemAllDataQuery.add(new DomainSortParameter(sp));
}
domainModel = wholeSystemAllDataQuery;
}
} else if (searchContext.hasSortParameters()) {
// Special variant of the query which will sort based on the given sort params
// and return a list of resource-ids which are then used to fetch the actual data
// (matching the old query builder design...for now).
SearchSortQuery sortQuery = new SearchSortQuery(resourceType.getSimpleName());
for (SortParameter sp: searchContext.getSortParameters()) {
sortQuery.add(new DomainSortParameter(sp));
}
domainModel = sortQuery;
} else {
domainModel = new SearchDataQuery(resourceType.getSimpleName());
}
buildModelCommon(domainModel, resourceType, searchContext);
Select result = renderQuery(domainModel, searchContext);
log.exiting(CLASSNAME, METHODNAME);
return result;
}
/**
* Builds a query that returns included resources.
*
* @param resourceType - the type of resource being searched for.
* @param searchContext - the search context containing the search parameters.
* @param inclusionParm - the inclusion parameter for which the query is being built.
* @param ids - the list of logical resource IDs the query will run against.
* @param inclusionType - either INCLUDE or REVINCLUDE.
* @return Select the query to fetch the matching list of included resources
* @throws Exception
*/
public Select buildIncludeQuery(Class> resourceType, FHIRSearchContext searchContext,
InclusionParameter inclusionParm, List logicalResourceIds, String inclusionType) throws Exception {
final String METHODNAME = "buildIncludeQuery";
log.entering(CLASSNAME, METHODNAME,
new Object[] { resourceType.getSimpleName(), inclusionParm });
// Build the special "include" query to fetch additional resources
// that need to be included with the main search result
final String includeResourceType;
if (SearchConstants.INCLUDE.equals(inclusionType)) {
log.fine("Building _include query");
includeResourceType = inclusionParm.getSearchParameterTargetType();
} else {
log.fine("Building _revinclude query");
includeResourceType = inclusionParm.getJoinResourceType();
}
// Start building a query model to fetch resources of the type we want to include
final SearchQuery domainModel = new SearchIncludeQuery(includeResourceType);
buildIncludeModel(domainModel, resourceType, searchContext, inclusionParm, logicalResourceIds, inclusionType);
// Be careful - we need to override the searchContext here, because we don't want
// to be using same pagination as the main query.
int pageSize = searchContext.getPageSize();
int pageNumber = searchContext.getPageNumber();
searchContext.setPageSize(searchContext.getMaxPageIncludeCount()+1); // so we know when we have too many
searchContext.setPageNumber(1); // only need the first page of includes
final Select result;
try {
result = renderQuery(domainModel, searchContext);
} finally {
// reset the context
searchContext.setPageSize(pageSize);
searchContext.setPageNumber(pageNumber);
}
log.exiting(CLASSNAME, METHODNAME);
return result;
}
/**
* Build the include query used to fetch additional resources for _include
* and _revinclude searches
* @param domainModel
* @param resourceType
* @param searchContext
* @param inclusionParm
* @param logicalResourceIds
* @param inclusionType
*/
private void buildIncludeModel(SearchQuery domainModel, Class> resourceType, FHIRSearchContext searchContext, InclusionParameter inclusionParm,
List logicalResourceIds, String inclusionType) {
if (SearchConstants.INCLUDE.equals(inclusionType)) {
domainModel.add(new IncludeExtension(inclusionParm, logicalResourceIds));
} else {
domainModel.add(new RevIncludeExtension(inclusionParm, logicalResourceIds));
}
}
/**
* Builds a query that returns resource data for the specified whole-system search.
*
* @param searchContext - the search context.
* @param resourceTypeIdToLogicalResourceIdMap - map of resource type Ids to logical resource Ids
* @return Select the query to fetch the specified list of resources
* @throws Exception
*/
public Select buildWholeSystemDataQuery(FHIRSearchContext searchContext, Map> resourceTypeIdToLogicalResourceIdMap) throws Exception {
final String METHODNAME = "buildWholeSystemDataQuery";
log.entering(CLASSNAME, METHODNAME);
// Create domain model for each resource type found by the filter query
List subDomainModels = new ArrayList<>();
for (Integer resourceTypeId : resourceTypeIdToLogicalResourceIdMap.keySet()) {
String resourceType = identityCache.getResourceTypeName(resourceTypeId);
List logicalResourceIds = resourceTypeIdToLogicalResourceIdMap.get(resourceTypeId);
SearchQuery subDomainModel = new SearchWholeSystemDataQuery(resourceType, resourceTypeId);
subDomainModel.add(new WholeSystemDataExtension(resourceType, logicalResourceIds));
buildModelCommon(subDomainModel, ModelSupport.getResourceType(resourceType), searchContext);
subDomainModels.add(subDomainModel);
}
// Create whole-system search domain model
SearchWholeSystemQuery wholeSystemAllDataQuery =
new SearchWholeSystemQuery(subDomainModels, false, false);
for (SortParameter sp: searchContext.getSortParameters()) {
wholeSystemAllDataQuery.add(new DomainSortParameter(sp));
}
final SearchQuery domainModel = wholeSystemAllDataQuery;
buildModelCommon(domainModel, Resource.class, searchContext);
Select result = renderQuery(domainModel, searchContext);
log.exiting(CLASSNAME, METHODNAME);
return result;
}
/**
* Contains logic common to the building of both 'count' resource queries and
* 'data' resource queries.
* @param domainModel Domain model of the query we are trying to build
* @param resourceType
* The type of FHIR resource being searched for.
* @param searchContext
* The search context containing search parameters.
* @throws Exception
*/
private void buildModelCommon(SearchQuery domainModel, Class> resourceType, FHIRSearchContext searchContext)
throws Exception {
final String METHODNAME = "buildQueryCommon";
log.entering(CLASSNAME, METHODNAME,
new Object[] { resourceType.getSimpleName(), searchContext.getSearchParameters() });
List searchParameters = searchContext.getSearchParameters();
// Forces _id and _lastUpdated to come before all other parameters, which is good for this bit here
// zero is used to for all other cases.
searchParameters.sort(new Comparator() {
@Override
public int compare(QueryParameter leftParameter, QueryParameter rightParameter) {
int result = 0;
if (ID.equals(leftParameter.getCode())) {
result = -100;
} else if (LAST_UPDATED.equals(leftParameter.getCode())) {
result = -90;
}
return result;
}
});
if (Location.class.equals(resourceType)) {
// Special logic for handling LocationPosition queries. These queries have interdependencies between
// a couple of related input query parameters
domainModel.add(new LocationSearchExtension(searchParameters));
}
// Add each query parameter to our domain model
for (QueryParameter queryParameter : searchParameters) {
// DATE parameters will be processed separately after other parameters
if (!Type.DATE.equals(queryParameter.getType())) {
processQueryParameter(domainModel, resourceType, queryParameter);
}
}
// Special logic for search parameters of type DATE. We may be able to
// consolidate multiple parameters into a single parameter.
consolidateDateParms(domainModel, resourceType, searchParameters);
log.exiting(CLASSNAME, METHODNAME);
}
/**
* Process the given queryParameter and add the relevant artifacts to the
* domain model (precursor to actually building the query)
* @param domainModel
* @param resourceType
* @param queryParm
*/
private void processQueryParameter(SearchQuery domainModel, Class> resourceType, QueryParameter queryParm) throws Exception {
final String METHODNAME = "processQueryParameter";
if (log.isLoggable(Level.FINER)) {
log.entering(CLASSNAME, METHODNAME, queryParm.toString());
}
try {
if (Modifier.MISSING.equals(queryParm.getModifier())) {
domainModel.add(new MissingSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
return;
}
// NOTE: The special logic needed to process NEAR query parms for the Location resource type is
// found in method processLocationPosition(). This method will not handle those.
final String code = queryParm.getCode();
if (LocationUtil.isLocation(resourceType, queryParm)) {
domainModel.add(new LocationSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else if (ID.equals(code)) {
domainModel.add(new IdSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else if (LAST_UPDATED.equals(code)) {
domainModel.add(new LastUpdatedSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else {
final Type type = queryParm.getType();
switch (type) {
case STRING:
domainModel.add(new StringSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
break;
case REFERENCE:
if (queryParm.isReverseChained()) {
domainModel.add(new ChainedSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else if (queryParm.isChained()) {
domainModel.add(new ChainedSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else if (queryParm.isInclusionCriteria()) {
domainModel.add(new InclusionSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else {
domainModel.add(new ReferenceSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
}
break;
case DATE:
domainModel.add(new DateSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
break;
case TOKEN:
if (!this.legacyWholeSystemSearchParamsEnabled && TAG.equals(queryParm.getCode())) {
domainModel.add(new TagSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else if (!this.legacyWholeSystemSearchParamsEnabled && SECURITY.equals(queryParm.getCode())) {
domainModel.add(new SecuritySearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else {
domainModel.add(new TokenSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
}
break;
case NUMBER:
domainModel.add(new NumberSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
break;
case QUANTITY:
domainModel.add(new QuantitySearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
break;
case URI:
if ((!this.legacyWholeSystemSearchParamsEnabled && PROFILE.equals(queryParm.getCode()))
|| URL.equals(queryParm.getCode()) || queryParm.isCanonical()) {
domainModel.add(new CanonicalSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
} else {
domainModel.add(new StringSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
}
break;
case COMPOSITE:
domainModel.add(new CompositeSearchParam(resourceType.getSimpleName(), queryParm.getCode(), queryParm));
break;
default:
throw new FHIRPersistenceNotSupportedException("Parm type not yet supported: " + type.value());
}
}
} finally {
if (log.isLoggable(Level.FINER)) {
log.exiting(CLASSNAME, METHODNAME, new Object[] { queryParm });
}
}
}
protected String getOperator(QueryParameter queryParm) {
final String METHODNAME = "getOperator(QueryParameter)";
log.entering(CLASSNAME, METHODNAME, queryParm.getModifier());
String operator = LIKE;
Modifier modifier = queryParm.getModifier();
// In the case where a URI, we need specific behavior/manipulation
// so that URI defaults to EQ, unless... BELOW
if (Type.URI.equals(queryParm.getType())) {
if (modifier != null && Modifier.BELOW.equals(modifier)) {
operator = LIKE;
} else {
operator = EQ;
}
} else if (modifier != null) {
operator = modifierOperatorMap.get(modifier);
}
if (operator == null) {
operator = LIKE;
}
log.exiting(CLASSNAME, METHODNAME, operator);
return operator;
}
/**
* Map the Modifier in the passed Parameter to a supported query operator. If
* the mapping results in the default
* operator, override the default operator with the passed operator if the
* passed operator is not null.
*
* @param queryParm
* - A valid query Parameter.
* @param defaultOverride
* - An operator that should override the default
* operator.
* @return A supported operator.
*/
protected String getOperator(QueryParameter queryParm, String defaultOverride) {
final String METHODNAME = "getOperator(Parameter, String)";
log.entering(CLASSNAME, METHODNAME, queryParm.getModifier());
String operator = defaultOverride;
Modifier modifier = queryParm.getModifier();
if (modifier != null) {
operator = modifierOperatorMap.get(modifier);
}
if (operator == null) {
if (defaultOverride != null) {
operator = defaultOverride;
} else {
operator = LIKE;
}
}
log.exiting(CLASSNAME, METHODNAME, operator);
return operator;
}
private boolean allSearchParmsAreGlobal(List queryParms) {
for (QueryParameter queryParm : queryParms) {
if (!SearchConstants.SYSTEM_LEVEL_GLOBAL_PARAMETER_NAMES.contains(queryParm.getCode())) {
return false;
}
}
return true;
}
private void addResourceTypeExtension(SearchQuery domainModel, List resourceTypes) throws FHIRPersistenceException {
List resourceTypeIds = new ArrayList<>();
for (String resourceType : resourceTypes) {
resourceTypeIds.add(this.identityCache.getResourceTypeId(resourceType));
}
domainModel.add(new WholeSystemResourceTypeExtension(resourceTypeIds));
}
/**
* Process DATE parameters. If there are multiple query parameters specified for a
* single DATE search parameter, we will attempt to consolidate in two ways:
* 1. We will try to consolidate all query parameters which constrain a lower or upper
* bound of the search into a single query parameter (i.e. if there are query
* parameters with 'le', 'lt', and 'eb' prefixes, we will try to consolidate them
* into a single query parameter.
* 2. We will chain together all consolidated query parameters so that the query
* builder can do a single JOIN against the xx_date_values table rather than one
* JOIN per query parameter. For example, a search of:
* 'Patient?birthdate=gt1980-01-01&birthdate=lt2020-01-01'
* will generate two JOINS to the Patient_DATE_VALUES table if the query parameters
* are not consolidated. However, when consolidated into a chained DATE query parameter,
* the query builder will generate a single JOIN.
* We will not attempt to consolidate query parameters that have a modifier specified,
* or that are chain or inclusion parameters, or that have multiple parameter values.
* Those will be processed as normal DATE query parameters.
*
* @param domainModel
* @param resourceType
* @param searchParameters
* @throws Exception
*/
private void consolidateDateParms(SearchQuery domainModel, Class> resourceType, List searchParameters)
throws Exception {
// We only need to attempt to consolidate if we have multiple query parameters for the
// same search parameter name. Loop through the search parameters, mapping parameter name
// to parameter(s) to determine if this is the case.
Map> consolidationMap = new HashMap<>();
for (QueryParameter searchParameter : searchParameters) {
if (Type.DATE.equals(searchParameter.getType())) {
consolidationMap.computeIfAbsent(searchParameter.getCode(), k -> new ArrayList<>()).add(searchParameter);
}
}
// Now loop through the map to find any cases of same parameter specified multiple times.
// If found, we will attempt to consolidate. If not, we will simply process as a normal
// date parameter.
for (Map.Entry> entry : consolidationMap.entrySet()) {
List queryParameters = entry.getValue();
boolean eligibleToConsolidate = true;
if (queryParameters.size() == 1 || LAST_UPDATED.equals(entry.getKey())) {
eligibleToConsolidate = false;
} else {
// We have multiple parameters. If any of the parameters have a modifier specified, or
// if chain or inclusion parameter, or if multiple values, don't attempt to consolidate.
for (QueryParameter queryParm : queryParameters) {
List queryParmValues = queryParm.getValues();
if (queryParm.getModifier() != null || queryParm.isChained() ||
queryParm.isInclusionCriteria() || queryParmValues.size() > 1) {
eligibleToConsolidate = false;
break;
}
}
}
if (eligibleToConsolidate) {
// Attempt to consolidate the upper and lower bound constraints
List consolidatedParms = new ArrayList<>();
Instant gteBound = null;
Instant lteBound = null;
Instant saBound = null;
Instant ebBound = null;
QueryParameter gteBoundParm = null;
QueryParameter lteBoundParm = null;
QueryParameter saBoundParm = null;
QueryParameter ebBoundParm = null;
for (QueryParameter queryParm : queryParameters) {
QueryParameterValue queryParmValue = queryParm.getValues().get(0);
Prefix prefix = queryParmValue.getPrefix();
Instant valueLowerBound = queryParmValue.getValueDateLowerBound();
Instant valueUpperBound = queryParmValue.getValueDateUpperBound();
switch(prefix) {
case GT:
if (gteBound == null || valueUpperBound.isAfter(gteBound) || valueUpperBound.equals(gteBound)) {
gteBound = valueUpperBound;
gteBoundParm = queryParm;
}
break;
case GE:
if (gteBound == null || valueLowerBound.isAfter(gteBound)) {
gteBound = valueLowerBound;
gteBoundParm = queryParm;
}
break;
case SA:
if (saBound == null || valueUpperBound.isAfter(saBound)) {
saBound = valueUpperBound;
saBoundParm = queryParm;
}
break;
case LT:
if (lteBound == null || valueLowerBound.isBefore(lteBound) || valueLowerBound.equals(lteBound)) {
lteBound = valueLowerBound;
lteBoundParm = queryParm;
}
break;
case LE:
if (lteBound == null || valueUpperBound.isBefore(lteBound)) {
lteBound = valueUpperBound;
lteBoundParm = queryParm;
}
break;
case EB:
if (ebBound == null || valueLowerBound.isBefore(ebBound)) {
ebBound = valueLowerBound;
ebBoundParm = queryParm;
}
break;
default:
// If not a simple bound constraint, add to list to be processed as is
consolidatedParms.add(queryParm);
}
}
// Add the consolidated parms
if (saBound != null) {
// Add the SA queryParm with the most restrictive bound
consolidatedParms.add(saBoundParm);
if (gteBound != null &&
(saBound.isAfter(gteBound) || saBound.equals(gteBound))) {
// ignore the GT/GE queryParm since SA queryParm is more restrictive
gteBound = null;
}
}
if (gteBound != null) {
// Add the GT/GE queryParm with the most restrictive bound
consolidatedParms.add(gteBoundParm);
}
if (ebBound != null) {
// Add the EB queryParm with the most restrictive bound
consolidatedParms.add(ebBoundParm);
if (lteBound != null &&
(ebBound.isBefore(lteBound) || ebBound.equals(lteBound))) {
// ignore the LT/LE queryParm since EB queryParm is more restrictive
lteBound = null;
}
}
if (lteBound != null) {
// Add the LT/LE queryParm with the most restrictive bound
consolidatedParms.add(lteBoundParm);
}
// Chain all the consolidated parms together - need to make copies since we're
// modifying by chaining.
QueryParameter consolidatedDateParm = null;
for (QueryParameter consolidatedParm : consolidatedParms) {
QueryParameter cp = new QueryParameter(consolidatedParm.getType(), consolidatedParm.getCode(),
null, null, consolidatedParm.getValues());
if (consolidatedDateParm == null) {
consolidatedDateParm = cp;
} else {
if (consolidatedDateParm.getChain().isEmpty()) {
consolidatedDateParm.setNextParameter(cp);
} else {
consolidatedDateParm.getChain().getLast().setNextParameter(cp);
}
}
}
// Process new consolidated DATE parameter
domainModel.add(new DateSearchParam(
resourceType.getSimpleName(), consolidatedDateParm.getCode(), consolidatedDateParm));
} else {
// Process as normal date parameters
for (QueryParameter queryParm : queryParameters) {
processQueryParameter(domainModel, resourceType, queryParm);
}
}
}
}
}