More work on performance

This commit is contained in:
James Agnew 2017-04-06 12:50:36 -04:00
parent 1581cdf9a8
commit f48d0d677b
11 changed files with 462 additions and 205 deletions

View File

@ -40,6 +40,8 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
@Configuration @Configuration
@EnableScheduling @EnableScheduling
@ -64,11 +66,6 @@ public class BaseConfig implements SchedulingConfigurer {
return retVal; return retVal;
} }
@Bean(autowire=Autowire.BY_TYPE)
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvcImpl();
}
@Bean() @Bean()
public ScheduledExecutorFactoryBean scheduledExecutorService() { public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean(); ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
@ -76,6 +73,16 @@ public class BaseConfig implements SchedulingConfigurer {
return b; return b;
} }
@Bean
public ISearchParamPresenceSvc searchParamPresenceSvc() {
return new SearchParamPresenceSvcImpl();
}
@Bean(autowire=Autowire.BY_TYPE)
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvcImpl();
}
@Bean @Bean
public TaskScheduler taskScheduler() { public TaskScheduler taskScheduler() {
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler(); ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();

View File

@ -85,28 +85,9 @@ import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.BaseTag;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterAnd;
@ -197,7 +178,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
protected EntityManager myEntityManager; protected EntityManager myEntityManager;
@Autowired @Autowired
protected IForcedIdDao myForcedIdDao; protected IForcedIdDao myForcedIdDao;
@Autowired(required = false) @Autowired(required = false)
protected IFulltextSearchSvc myFulltextSearchSvc; protected IFulltextSearchSvc myFulltextSearchSvc;
@Autowired @Autowired
@ -220,6 +200,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@Autowired @Autowired
private ISearchParamExtractor mySearchParamExtractor; private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
@ -258,14 +241,19 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return InstantDt.withCurrentTime(); return InstantDt.withCurrentTime();
} }
/**
* @return Returns a set containing all of the parameter names that
* were found to have a value
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) { protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) {
HashSet<String> retVal = new HashSet<String>();
/* /*
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing.. * For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
*/ */
if (theResource instanceof IBaseBundle) { if (theResource instanceof IBaseBundle) {
return; return Collections.emptySet();
} }
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
@ -318,6 +306,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
} }
retVal.add(nextSpDef.getName());
if (isLogicalReference(nextId)) { if (isLogicalReference(nextId)) {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime); ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theLinks.add(resourceLink)) { if (theLinks.add(resourceLink)) {
@ -401,6 +391,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setHasLinks(theLinks.size() > 0); theEntity.setHasLinks(theLinks.size() > 0);
return retVal;
} }
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) { protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
@ -978,9 +969,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time. * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
* *
* @param theEntity * @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag * @param theTag
* The tag * The tag
*/ */
protected void postPersist(ResourceTable theEntity, T theResource) { protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing // nothing
@ -990,9 +981,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
* *
* @param theEntity * @param theEntity
* The resource * The resource
* @param theResource * @param theResource
* The resource being persisted * The resource being persisted
*/ */
protected void postUpdate(ResourceTable theEntity, T theResource) { protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing // nothing
@ -1053,9 +1044,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* </p> * </p>
* *
* @param theEntity * @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag * @param theTag
* The tag * The tag
* @return Retturns <code>true</code> if the tag should be removed * @return Retturns <code>true</code> if the tag should be removed
*/ */
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@ -1343,7 +1334,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
links = new HashSet<ResourceLink>(); links = new HashSet<ResourceLink>();
extractResourceLinks(theEntity, theResource, links, theUpdateTime); Set<String> populatedResourceLinkParameters = extractResourceLinks(theEntity, theResource, links, theUpdateTime);
/* /*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
@ -1383,6 +1374,29 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setIndexStatus(INDEX_STATUS_INDEXED); theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
populateFullTextFields(theResource, theEntity); populateFullTextFields(theResource, theEntity);
/*
* Update the "search param present" table which is used for the
* ?foo:missing=true queries
*/
Map<String, Boolean> presentSearchParams = new HashMap<String, Boolean>();
for (String nextKey : populatedResourceLinkParameters) {
presentSearchParams.put(nextKey, Boolean.TRUE);
}
updateSearchParamPresent(presentSearchParams, stringParams);
updateSearchParamPresent(presentSearchParams, tokenParams);
updateSearchParamPresent(presentSearchParams, numberParams);
updateSearchParamPresent(presentSearchParams, quantityParams);
updateSearchParamPresent(presentSearchParams, dateParams);
updateSearchParamPresent(presentSearchParams, uriParams);
updateSearchParamPresent(presentSearchParams, coordsParams);
Set<String> activeSearchParamNames = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).keySet();
for (String nextSpName : activeSearchParamNames) {
if (!presentSearchParams.containsKey(nextSpName)) {
presentSearchParams.put(nextSpName, Boolean.FALSE);
}
}
mySearchParamPresenceSvc.updatePresence(theEntity, presentSearchParams);
} else { } else {
populateResourceIntoEntity(theResource, theEntity); populateResourceIntoEntity(theResource, theEntity);
@ -1502,6 +1516,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return theEntity; return theEntity;
} }
private void updateSearchParamPresent(Map<String, Boolean> presentSearchParams, Set<? extends BaseResourceIndexedSearchParam> params) {
for (BaseResourceIndexedSearchParam nextSearchParam : params) {
presentSearchParams.put(nextSearchParam.getParamName(), Boolean.TRUE);
}
}
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable entity, Date theDeletedTimestampOrNull, Date theUpdateTime) { protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime); return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime);
} }
@ -1594,9 +1614,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check. * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
* *
* @param theResource * @param theResource
* The resource that is about to be persisted * The resource that is about to be persisted
* @param theEntityToSave * @param theEntityToSave
* TODO * TODO
*/ */
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null; Object tag = null;

View File

@ -106,6 +106,8 @@ public class SearchBuilder {
private CriteriaBuilder myBuilder; private CriteriaBuilder myBuilder;
private CriteriaQuery<Tuple> myResourceTableQuery;
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theFulltextSearchSvc, public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theFulltextSearchSvc,
ISearchResultDao theSearchResultDao, BaseHapiFhirDao<?> theDao, ISearchResultDao theSearchResultDao, BaseHapiFhirDao<?> theDao,
IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) { IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) {
@ -317,7 +319,7 @@ public class SearchBuilder {
return missingFalse; return missingFalse;
} }
private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, Root<? extends ResourceLink> from, List<Predicate> codePredicates, private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, From<?,? extends ResourceLink> from, List<Predicate> codePredicates,
IQueryParameterType nextOr) { IQueryParameterType nextOr) {
boolean missingFalse = false; boolean missingFalse = false;
if (nextOr.getMissing() != null) { if (nextOr.getMissing() != null) {
@ -456,17 +458,14 @@ public class SearchBuilder {
return; return;
} }
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceLink> from = cq.from(ResourceLink.class);
cq.select(from.get("mySourceResourcePid").as(Long.class));
List<Predicate> codePredicates = new ArrayList<Predicate>(); List<Predicate> codePredicates = new ArrayList<Predicate>();
for (IQueryParameterType nextOr : theList) { for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr; IQueryParameterType params = nextOr;
if (addPredicateMissingFalseIfPresentForResourceLink(builder, theParamName, from, codePredicates, nextOr)) { if (addPredicateMissingFalseIfPresentForResourceLink(myBuilder, theParamName, join, codePredicates, nextOr)) {
continue; continue;
} }
@ -481,7 +480,7 @@ public class SearchBuilder {
dt = dt.toUnqualified(); dt = dt.toUnqualified();
} else { } else {
ourLog.debug("Searching for resource link with target URL: {}", dt.getValue()); ourLog.debug("Searching for resource link with target URL: {}", dt.getValue());
Predicate eq = builder.equal(from.get("myTargetResourceUrl"), dt.getValue()); Predicate eq = myBuilder.equal(join.get("myTargetResourceUrl"), dt.getValue());
codePredicates.add(eq); codePredicates.add(eq);
continue; continue;
} }
@ -491,12 +490,12 @@ public class SearchBuilder {
try { try {
targetPid = myCallingDao.translateForcedIdToPids(dt); targetPid = myCallingDao.translateForcedIdToPids(dt);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
doSetPids(new ArrayList<Long>()); // Use a PID that will never exist
return; targetPid = Collections.singletonList(-1L);
} }
for (Long next : targetPid) { for (Long next : targetPid) {
ourLog.debug("Searching for resource link with target PID: {}", next); ourLog.debug("Searching for resource link with target PID: {}", next);
Predicate eq = builder.equal(from.get("myTargetResourcePid"), next); Predicate eq = myBuilder.equal(join.get("myTargetResourcePid"), next);
codePredicates.add(eq); codePredicates.add(eq);
} }
} else { } else {
@ -586,13 +585,35 @@ public class SearchBuilder {
} }
foundChainMatch = true; foundChainMatch = true;
// Set<Long> pids = dao.searchForIds(chain, chainValue);
Set<Long> pids = dao.searchForIds(chain, chainValue); Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
if (pids.isEmpty()) { Root<ResourceTable> subQfrom = subQ.from(ResourceTable.class);
continue; subQ.select(subQfrom.get("myId").as(Long.class));
}
Predicate eq = from.get("myTargetResourcePid").in(pids); List<List<? extends IQueryParameterType>> andOrParams = new ArrayList<List<? extends IQueryParameterType>>();
andOrParams.add(Collections.singletonList(chainValue));
/*
* We're doing a chain call, so push the current query root
* and predicate list down and put new ones at the top of the
* stack and run a subuery
*/
Root<ResourceTable> stackRoot = myResourceTableRoot;
ArrayList<Predicate> stackPredicates = myPredicates;
myResourceTableRoot = subQfrom;
myPredicates = new ArrayList<Predicate>();
searchForIdsWithAndOr(chain, andOrParams);
subQ.where(toArray(myPredicates));
/*
* Pop the old query root and predicate list back
*/
myResourceTableRoot = stackRoot;
myPredicates = stackPredicates;
Predicate eq = join.get("myTargetResourcePid").in(subQ);
codePredicates.add(eq); codePredicates.add(eq);
} }
@ -608,16 +629,7 @@ public class SearchBuilder {
} }
List<Predicate> predicates = new ArrayList<Predicate>(); myPredicates.add(myBuilder.or(toArray(codePredicates)));
predicates.add(createResourceLinkPathPredicate(theParamName, from));
predicates.add(builder.or(toArray(codePredicates)));
createPredicateResourceId(builder, cq, predicates, from.get("mySourceResourcePid").as(Long.class));
createPredicateLastUpdatedForResourceLink(builder, from, predicates);
cq.where(builder.and(toArray(predicates)));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
doSetPids(new HashSet<Long>(q.getResultList()));
} }
private void addPredicateString(String theParamName, List<? extends IQueryParameterType> theList) { private void addPredicateString(String theParamName, List<? extends IQueryParameterType> theList) {
@ -713,8 +725,6 @@ public class SearchBuilder {
continue; continue;
} }
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
boolean paramInverted = false; boolean paramInverted = false;
List<Pair<String, String>> tokens = Lists.newArrayList(); List<Pair<String, String>> tokens = Lists.newArrayList();
for (IQueryParameterType nextOrParams : nextAndParams) { for (IQueryParameterType nextOrParams : nextAndParams) {
@ -745,19 +755,11 @@ public class SearchBuilder {
if (paramInverted) { if (paramInverted) {
ourLog.debug("Searching for _tag:not"); ourLog.debug("Searching for _tag:not");
CriteriaQuery<Long> cq = builder.createQuery(Long.class); Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
Root<ResourceTable> newFrom = cq.from(ResourceTable.class);
Subquery<Long> subQ = cq.subquery(Long.class);
Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class); Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
subQ.select(subQfrom.get("myResourceId").as(Long.class)); subQ.select(subQfrom.get("myResourceId").as(Long.class));
cq.select(newFrom.get("myId").as(Long.class)); myPredicates.add(myBuilder.not(myBuilder.in(myResourceTableRoot.get("myId")).value(subQ)));
List<Predicate> andPredicates = new ArrayList<Predicate>();
andPredicates = new ArrayList<Predicate>();
andPredicates.add(builder.equal(newFrom.get("myResourceType"), myResourceName));
andPredicates.add(builder.not(builder.in(newFrom.get("myId")).value(subQ)));
Subquery<Long> defJoin = subQ.subquery(Long.class); Subquery<Long> defJoin = subQ.subquery(Long.class);
Root<TagDefinition> defJoinFrom = defJoin.from(TagDefinition.class); Root<TagDefinition> defJoinFrom = defJoin.from(TagDefinition.class);
@ -765,44 +767,18 @@ public class SearchBuilder {
subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin)); subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin));
List<Predicate> orPredicates = createPredicateTagList(defJoinFrom, builder, tagType, tokens); List<Predicate> orPredicates = createPredicateTagList(defJoinFrom, myBuilder, tagType, tokens);
defJoin.where(toArray(orPredicates)); defJoin.where(toArray(orPredicates));
cq.where(toArray(andPredicates));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
Set<Long> pids = new HashSet<Long>(q.getResultList());
doSetPids(pids);
continue; continue;
} }
CriteriaQuery<Long> cq = builder.createQuery(Long.class); Join<ResourceTable, ResourceTag> tagJoin = myResourceTableRoot.join("myTags", JoinType.LEFT);
Root<ResourceTag> from = cq.from(ResourceTag.class); From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
List<Predicate> andPredicates = new ArrayList<Predicate>();
andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName));
From<ResourceTag, TagDefinition> defJoin = from.join("myTag");
Join<?, ResourceTable> defJoin2 = from.join("myResource"); List<Predicate> orPredicates = createPredicateTagList(defJoin, myBuilder, tagType, tokens);
myPredicates.add(myBuilder.or(toArray(orPredicates)));
Predicate notDeletedPredicatePrediate = builder.isNull(defJoin2.get("myDeleted"));
andPredicates.add(notDeletedPredicatePrediate);
List<Predicate> orPredicates = createPredicateTagList(defJoin, builder, tagType, tokens);
andPredicates.add(builder.or(toArray(orPredicates)));
if (theLastUpdated != null) {
andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin2));
}
createPredicateResourceId(builder, cq, andPredicates, from.get("myResourceId").as(Long.class));
Predicate masterCodePredicate = builder.and(toArray(andPredicates));
cq.select(from.get("myResourceId").as(Long.class));
cq.where(masterCodePredicate);
TypedQuery<Long> q = myEntityManager.createQuery(cq);
Set<Long> pids = new HashSet<Long>(q.getResultList());
doSetPids(pids);
} }
} }
@ -851,16 +827,13 @@ public class SearchBuilder {
return; return;
} }
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); Join<ResourceTable, ResourceIndexedSearchParamUri> join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceIndexedSearchParamUri> from = cq.from(ResourceIndexedSearchParamUri.class);
cq.select(from.get("myResourcePid").as(Long.class));
List<Predicate> codePredicates = new ArrayList<Predicate>(); List<Predicate> codePredicates = new ArrayList<Predicate>();
for (IQueryParameterType nextOr : theList) { for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr; IQueryParameterType params = nextOr;
if (addPredicateMissingFalseIfPresent(builder, theParamName, from, codePredicates, nextOr)) { if (addPredicateMissingFalseIfPresent(myBuilder, theParamName, join, codePredicates, nextOr)) {
continue; continue;
} }
@ -872,7 +845,7 @@ public class SearchBuilder {
continue; continue;
} }
Path<Object> fromObj = from.get("myUri"); Path<Object> fromObj = join.get("myUri");
Predicate predicate; Predicate predicate;
if (param.getQualifier() == UriParamQualifierEnum.ABOVE) { if (param.getQualifier() == UriParamQualifierEnum.ABOVE) {
@ -905,9 +878,9 @@ public class SearchBuilder {
predicate = fromObj.as(String.class).in(toFind); predicate = fromObj.as(String.class).in(toFind);
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) { } else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
predicate = builder.like(fromObj.as(String.class), createLeftMatchLikeExpression(value)); predicate = myBuilder.like(fromObj.as(String.class), createLeftMatchLikeExpression(value));
} else { } else {
predicate = builder.equal(fromObj.as(String.class), value); predicate = myBuilder.equal(fromObj.as(String.class), value);
} }
codePredicates.add(predicate); codePredicates.add(predicate);
} else { } else {
@ -921,16 +894,8 @@ public class SearchBuilder {
return; return;
} }
List<Predicate> predicates = new ArrayList<Predicate>(); Predicate orPredicate = myBuilder.or(toArray(codePredicates));
predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); myPredicates.add(orPredicate);
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
doSetPids(new HashSet<Long>(q.getResultList()));
} }
private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) { private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) {
@ -1300,7 +1265,7 @@ public class SearchBuilder {
return singleCode; return singleCode;
} }
private Predicate createResourceLinkPathPredicate(String theParamName, Root<? extends ResourceLink> from) { private Predicate createResourceLinkPathPredicate(String theParamName, From<?,? extends ResourceLink> from) {
return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, myResourceType); return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, myResourceType);
} }
@ -1642,9 +1607,9 @@ public class SearchBuilder {
} }
myBuilder = myEntityManager.getCriteriaBuilder(); myBuilder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = myBuilder.createTupleQuery(); myResourceTableQuery = myBuilder.createTupleQuery();
cq.distinct(true); myResourceTableQuery.distinct(true);
myResourceTableRoot = cq.from(ResourceTable.class); myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class);
myPredicates = new ArrayList<Predicate>(); myPredicates = new ArrayList<Predicate>();
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName)); myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName));
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted"))); myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
@ -1655,10 +1620,10 @@ public class SearchBuilder {
searchForIdsWithAndOr(theParams); searchForIdsWithAndOr(theParams);
cq.where(myBuilder.and(SearchBuilder.toArray(myPredicates))); myResourceTableQuery.where(myBuilder.and(SearchBuilder.toArray(myPredicates)));
cq.multiselect(myResourceTableRoot.get("myId").as(Long.class)); myResourceTableQuery.multiselect(myResourceTableRoot.get("myId").as(Long.class));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq); TypedQuery<Tuple> query = myEntityManager.createQuery(myResourceTableQuery);
query.setFirstResult(theFromIndex); query.setFirstResult(theFromIndex);
query.setMaxResults(theToIndex - theFromIndex); query.setMaxResults(theToIndex - theFromIndex);
@ -1774,80 +1739,85 @@ public class SearchBuilder {
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) { for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) {
String nextParamName = nextParamEntry.getKey(); String nextParamName = nextParamEntry.getKey();
if (nextParamName.equals(BaseResource.SP_RES_ID)) { List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
searchForIdsWithAndOr(nextParamName, andOrParams);
addPredicateResourceId(nextParamEntry.getValue());
} else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) {
addPredicateLanguage(nextParamEntry.getValue());
} else if (nextParamName.equals(Constants.PARAM_HAS)) {
// FIXME
addPredicateHas(nextParamEntry.getValue(), null);
} else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) {
// FIXME
addPredicateTag(nextParamEntry.getValue(), nextParamName, null);
} else {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName);
if (nextParamDef != null) {
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateDate(nextParamName, nextAnd);
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateQuantity(nextParamName, nextAnd);
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateReference(nextParamName, nextAnd);
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateString(nextParamName, nextAnd);
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateToken(nextParamName, nextAnd);
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateNumber(nextParamName, nextAnd);
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateComposite(nextParamDef, nextAnd);
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
addPredicateUri(nextParamName, nextAnd);
}
break;
case HAS:
// should not happen
break;
}
}
}
} }
} }
private void searchForIdsWithAndOr(String nextParamName, List<List<? extends IQueryParameterType>> andOrParams) {
if (nextParamName.equals(BaseResource.SP_RES_ID)) {
addPredicateResourceId(andOrParams);
} else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) {
addPredicateLanguage(andOrParams);
} else if (nextParamName.equals(Constants.PARAM_HAS)) {
// FIXME
addPredicateHas(andOrParams, null);
} else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) {
// FIXME
addPredicateTag(andOrParams, nextParamName, null);
} else {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName);
if (nextParamDef != null) {
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateDate(nextParamName, nextAnd);
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateQuantity(nextParamName, nextAnd);
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateReference(nextParamName, nextAnd);
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateString(nextParamName, nextAnd);
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateToken(nextParamName, nextAnd);
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateNumber(nextParamName, nextAnd);
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateComposite(nextParamDef, nextAnd);
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : andOrParams) {
addPredicateUri(nextParamName, nextAnd);
}
break;
case HAS:
// should not happen
break;
}
}
}
}
private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) { private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) {
for (List<? extends IQueryParameterType> nextValue : theValues) { for (List<? extends IQueryParameterType> nextValue : theValues) {
Set<Long> orPids = new HashSet<Long>(); Set<Long> orPids = new HashSet<Long>();
@ -1972,7 +1942,7 @@ public class SearchBuilder {
return likeExpression.replace("%", "[%]") + "%"; return likeExpression.replace("%", "[%]") + "%";
} }
private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, Root<? extends ResourceLink> from, private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From<?,? extends ResourceLink> from,
Class<? extends IBaseResource> resourceType) { Class<? extends IBaseResource> resourceType) {
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(resourceType); RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(resourceType);
RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName); RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName);

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.SearchParam;
public interface ISearchParamDao extends JpaRepository<SearchParam, Long> {
@Query("SELECT s FROM SearchParam s WHERE s.myResourceName = :resname AND s.myParamName = :parmname")
public SearchParam findForResource(@Param("resname") String theResourceType, @Param("parmname") String theParamName);
}

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.jpa.dao.data;
import java.util.Collection;
import java.util.Date;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
public interface ISearchParamPresentDao extends JpaRepository<SearchParamPresent, Long> {
@Query("SELECT s FROM SearchParamPresent s WHERE s.myResourceTable = :res")
public Collection<SearchParamPresent> findAllForResource(@Param("res") ResourceTable theResource);
}

View File

@ -42,7 +42,6 @@ import org.hibernate.search.annotations.Field;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
//@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@Table(name = "HFJ_SPIDX_DATE", indexes= { @Table(name = "HFJ_SPIDX_DATE", indexes= {
@ -50,7 +49,6 @@ import ca.uhn.fhir.model.primitive.InstantDt;
@Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID") @Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID")
}) })
//@formatter:on
public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.jpa.entity;
import javax.persistence.*;
@Entity
@Table(name = "HFJ_SEARCH_PARM", uniqueConstraints= {
@UniqueConstraint(name="IDX_SEARCHPARM_RESTYPE_SPNAME", columnNames= {"RES_TYPE", "PARAM_NAME"})
})
public class SearchParam {
@Id
@SequenceGenerator(name = "SEQ_SEARCHPARM_ID", sequenceName = "SEQ_SEARCHPARM_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SEARCHPARM_ID")
@Column(name = "RES_ID")
private Long myId;
@Column(name="PARAM_NAME", length=BaseResourceIndexedSearchParam.MAX_SP_NAME, nullable=false, updatable=false)
private String myParamName;
@Column(name="RES_TYPE", length=ResourceTable.RESTYPE_LEN, nullable=false, updatable=false)
private String myResourceName;
public String getParamName() {
return myParamName;
}
public void setParamName(String theParamName) {
myParamName = theParamName;
}
public void setResourceName(String theResourceName) {
myResourceName = theResourceName;
}
}

View File

@ -0,0 +1,58 @@
package ca.uhn.fhir.jpa.entity;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "HFJ_RES_PARAM_PRESENT", indexes = {
@Index(name = "IDX_RESPARMPRESENT_RESID", columnList = "RES_ID")
}, uniqueConstraints = {
@UniqueConstraint(name = "IDX_RESPARMPRESENT_SPID_RESID", columnNames = { "SP_ID", "RES_ID" })
})
public class SearchParamPresent implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_RESPARMPRESENT_ID", sequenceName = "SEQ_RESPARMPRESENT_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESPARMPRESENT_ID")
@Column(name = "PID")
private Long myId;
@Column(name = "SP_PRESENT", nullable = false)
private boolean myPresent;
@ManyToOne()
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_RESPARMPRES_RESID"))
private ResourceTable myResourceTable;
@ManyToOne()
@JoinColumn(name = "SP_ID", referencedColumnName = "PID", nullable = false, foreignKey = @ForeignKey(name = "FK_RESPARMPRES_SPID"))
private SearchParam mySearchParam;
public ResourceTable getResourceTable() {
return myResourceTable;
}
public SearchParam getSearchParam() {
return mySearchParam;
}
public boolean isPresent() {
return myPresent;
}
public void setPresent(boolean thePresent) {
myPresent = thePresent;
}
public void setResourceTable(ResourceTable theResourceTable) {
myResourceTable = theResourceTable;
}
public void setSearchParam(SearchParam theSearchParam) {
mySearchParam = theSearchParam;
}
}

View File

@ -0,0 +1,11 @@
package ca.uhn.fhir.jpa.sp;
import java.util.Map;
import ca.uhn.fhir.jpa.entity.ResourceTable;
public interface ISearchParamPresenceSvc {
void updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence);
}

View File

@ -0,0 +1,81 @@
package ca.uhn.fhir.jpa.sp;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.data.ISearchParamDao;
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SearchParam;
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamPresenceSvcImpl.class);
private Map<Pair<String, String>, SearchParam> myResourceTypeToSearchParamToEntity = new ConcurrentHashMap<Pair<String,String>, SearchParam>();
@Autowired
private ISearchParamDao mySearchParamDao;
@Autowired
private ISearchParamPresentDao mySearchParamPresentDao;
@Override
public void updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
Map<String, Boolean> presenceMap = new HashMap<String, Boolean>(theParamNameToPresence);
List<SearchParamPresent> entitiesToSave = new ArrayList<SearchParamPresent>();
List<SearchParamPresent> entitiesToDelete = new ArrayList<SearchParamPresent>();
Collection<SearchParamPresent> existing = mySearchParamPresentDao.findAllForResource(theResource);
for (SearchParamPresent nextExistingEntity : existing) {
String nextSearchParamName = nextExistingEntity.getSearchParam().getParamName();
Boolean existingValue = presenceMap.remove(nextSearchParamName);
if (existingValue == null) {
entitiesToDelete.add(nextExistingEntity);
} else if (existingValue.booleanValue() == nextExistingEntity.isPresent()) {
ourLog.trace("No change for search param {}", nextSearchParamName);
} else {
nextExistingEntity.setPresent(existingValue);
entitiesToSave.add(nextExistingEntity);
}
}
for (Entry<String, Boolean> next : presenceMap.entrySet()) {
String resourceType = theResource.getResourceType();
String paramName = next.getKey();
Pair<String, String> key = Pair.of(resourceType, paramName);
SearchParam searchParam = myResourceTypeToSearchParamToEntity.get(key);
if (searchParam == null) {
searchParam = mySearchParamDao.findForResource(resourceType, paramName);
if (searchParam != null) {
myResourceTypeToSearchParamToEntity.put(key, searchParam);
} else {
searchParam = new SearchParam();
searchParam.setResourceName(resourceType);
searchParam.setParamName(paramName);
mySearchParamDao.save(searchParam);
// Don't add the newly saved entity to the map in case the save fails
}
SearchParamPresent present = new SearchParamPresent();
present.setResourceTable(theResource);
present.setSearchParam(searchParam);
present.setPresent(next.getValue());
entitiesToSave.add(present);
}
mySearchParamPresentDao.deleteInBatch(entitiesToDelete);
mySearchParamPresentDao.save(entitiesToSave);
}
}
}

View File

@ -2620,6 +2620,10 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
result = myValueSetDao.search(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/").setQualifier(UriParamQualifierEnum.BELOW)); result = myValueSetDao.search(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/").setQualifier(UriParamQualifierEnum.BELOW));
assertThat(toUnqualifiedVersionlessIds(result), contains(id1)); assertThat(toUnqualifiedVersionlessIds(result), contains(id1));
result = myValueSetDao.search(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/FOOOOOO"));
assertThat(toUnqualifiedVersionlessIds(result), empty());
} }
@Test @Test