From f48d0d677b1c109433f064b79a9098b7694f1fa5 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Thu, 6 Apr 2017 12:50:36 -0400
Subject: [PATCH] More work on performance
---
.../ca/uhn/fhir/jpa/config/BaseConfig.java | 19 +-
.../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 86 +++--
.../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 298 ++++++++----------
.../fhir/jpa/dao/data/ISearchParamDao.java | 34 ++
.../jpa/dao/data/ISearchParamPresentDao.java | 38 +++
.../ResourceIndexedSearchParamDate.java | 2 -
.../ca/uhn/fhir/jpa/entity/SearchParam.java | 36 +++
.../fhir/jpa/entity/SearchParamPresent.java | 58 ++++
.../fhir/jpa/sp/ISearchParamPresenceSvc.java | 11 +
.../jpa/sp/SearchParamPresenceSvcImpl.java | 81 +++++
.../FhirResourceDaoDstu3SearchNoFtTest.java | 4 +
11 files changed, 462 insertions(+), 205 deletions(-)
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamDao.java
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParam.java
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParamPresent.java
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 03771b9ebfd..90540c05f0c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -40,6 +40,8 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
+import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
+import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
@Configuration
@EnableScheduling
@@ -63,18 +65,23 @@ public class BaseConfig implements SchedulingConfigurer {
DatabaseBackedPagingProvider retVal = new DatabaseBackedPagingProvider();
return retVal;
}
-
- @Bean(autowire=Autowire.BY_TYPE)
- public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
- return new StaleSearchDeletingSvcImpl();
- }
-
+
@Bean()
public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
b.setPoolSize(5);
return b;
}
+
+ @Bean
+ public ISearchParamPresenceSvc searchParamPresenceSvc() {
+ return new SearchParamPresenceSvcImpl();
+ }
+
+ @Bean(autowire=Autowire.BY_TYPE)
+ public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
+ return new StaleSearchDeletingSvcImpl();
+ }
@Bean
public TaskScheduler taskScheduler() {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
index d3ad152b342..0b9f21725aa 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
@@ -85,28 +85,9 @@ import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.data.*;
-import ca.uhn.fhir.jpa.entity.BaseHasResource;
-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.entity.*;
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.util.DeleteConflict;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
@@ -197,7 +178,6 @@ public abstract class BaseHapiFhirDao implements IDao {
protected EntityManager myEntityManager;
@Autowired
protected IForcedIdDao myForcedIdDao;
-
@Autowired(required = false)
protected IFulltextSearchSvc myFulltextSearchSvc;
@Autowired
@@ -220,6 +200,9 @@ public abstract class BaseHapiFhirDao implements IDao {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
+ @Autowired
+ private ISearchParamPresenceSvc mySearchParamPresenceSvc;
+
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@@ -258,14 +241,19 @@ public abstract class BaseHapiFhirDao implements IDao {
return InstantDt.withCurrentTime();
}
+ /**
+ * @return Returns a set containing all of the parameter names that
+ * were found to have a value
+ */
@SuppressWarnings("unchecked")
- protected void extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set theLinks, Date theUpdateTime) {
+ protected Set extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set theLinks, Date theUpdateTime) {
+ HashSet retVal = new HashSet();
/*
* 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) {
- return;
+ return Collections.emptySet();
}
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
@@ -318,6 +306,8 @@ public abstract class BaseHapiFhirDao implements IDao {
}
}
+ retVal.add(nextSpDef.getName());
+
if (isLogicalReference(nextId)) {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theLinks.add(resourceLink)) {
@@ -401,6 +391,7 @@ public abstract class BaseHapiFhirDao implements IDao {
theEntity.setHasLinks(theLinks.size() > 0);
+ return retVal;
}
protected Set extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
@@ -978,9 +969,9 @@ public abstract class BaseHapiFhirDao implements IDao {
* Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
*
* @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
- * The tag
+ * The tag
*/
protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing
@@ -990,9 +981,9 @@ public abstract class BaseHapiFhirDao implements IDao {
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
* @param theEntity
- * The resource
+ * The resource
* @param theResource
- * The resource being persisted
+ * The resource being persisted
*/
protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing
@@ -1053,9 +1044,9 @@ public abstract class BaseHapiFhirDao implements IDao {
*
*
* @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
- * The tag
+ * The tag
* @return Retturns true
if the tag should be removed
*/
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@@ -1343,7 +1334,7 @@ public abstract class BaseHapiFhirDao implements IDao {
}
links = new HashSet();
- extractResourceLinks(theEntity, theResource, links, theUpdateTime);
+ Set 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
@@ -1383,6 +1374,29 @@ public abstract class BaseHapiFhirDao implements IDao {
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
populateFullTextFields(theResource, theEntity);
+ /*
+ * Update the "search param present" table which is used for the
+ * ?foo:missing=true queries
+ */
+ Map presentSearchParams = new HashMap();
+ 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 activeSearchParamNames = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).keySet();
+ for (String nextSpName : activeSearchParamNames) {
+ if (!presentSearchParams.containsKey(nextSpName)) {
+ presentSearchParams.put(nextSpName, Boolean.FALSE);
+ }
+ }
+ mySearchParamPresenceSvc.updatePresence(theEntity, presentSearchParams);
+
} else {
populateResourceIntoEntity(theResource, theEntity);
@@ -1502,6 +1516,12 @@ public abstract class BaseHapiFhirDao implements IDao {
return theEntity;
}
+ private void updateSearchParamPresent(Map 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) {
return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime);
}
@@ -1594,9 +1614,9 @@ public abstract class BaseHapiFhirDao implements IDao {
* "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
*
* @param theResource
- * The resource that is about to be persisted
+ * The resource that is about to be persisted
* @param theEntityToSave
- * TODO
+ * TODO
*/
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index a80311275a8..c7ab8d61158 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -106,6 +106,8 @@ public class SearchBuilder {
private CriteriaBuilder myBuilder;
+ private CriteriaQuery myResourceTableQuery;
+
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theFulltextSearchSvc,
ISearchResultDao theSearchResultDao, BaseHapiFhirDao> theDao,
IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) {
@@ -317,7 +319,7 @@ public class SearchBuilder {
return missingFalse;
}
- private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, Root extends ResourceLink> from, List codePredicates,
+ private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, From,? extends ResourceLink> from, List codePredicates,
IQueryParameterType nextOr) {
boolean missingFalse = false;
if (nextOr.getMissing() != null) {
@@ -456,17 +458,14 @@ public class SearchBuilder {
return;
}
- CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
- CriteriaQuery cq = builder.createQuery(Long.class);
- Root from = cq.from(ResourceLink.class);
- cq.select(from.get("mySourceResourcePid").as(Long.class));
+ Join join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
List codePredicates = new ArrayList();
for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr;
- if (addPredicateMissingFalseIfPresentForResourceLink(builder, theParamName, from, codePredicates, nextOr)) {
+ if (addPredicateMissingFalseIfPresentForResourceLink(myBuilder, theParamName, join, codePredicates, nextOr)) {
continue;
}
@@ -481,7 +480,7 @@ public class SearchBuilder {
dt = dt.toUnqualified();
} else {
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);
continue;
}
@@ -491,12 +490,12 @@ public class SearchBuilder {
try {
targetPid = myCallingDao.translateForcedIdToPids(dt);
} catch (ResourceNotFoundException e) {
- doSetPids(new ArrayList());
- return;
+ // Use a PID that will never exist
+ targetPid = Collections.singletonList(-1L);
}
for (Long next : targetPid) {
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);
}
} else {
@@ -586,13 +585,35 @@ public class SearchBuilder {
}
foundChainMatch = true;
+// Set pids = dao.searchForIds(chain, chainValue);
- Set pids = dao.searchForIds(chain, chainValue);
- if (pids.isEmpty()) {
- continue;
- }
+ Subquery subQ = myResourceTableQuery.subquery(Long.class);
+ Root subQfrom = subQ.from(ResourceTable.class);
+ subQ.select(subQfrom.get("myId").as(Long.class));
+
+ List> andOrParams = new ArrayList>();
+ andOrParams.add(Collections.singletonList(chainValue));
- Predicate eq = from.get("myTargetResourcePid").in(pids);
+ /*
+ * 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 stackRoot = myResourceTableRoot;
+ ArrayList stackPredicates = myPredicates;
+ myResourceTableRoot = subQfrom;
+ myPredicates = new ArrayList();
+
+ 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);
}
@@ -608,16 +629,7 @@ public class SearchBuilder {
}
- List predicates = new ArrayList();
- 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 q = myEntityManager.createQuery(cq);
- doSetPids(new HashSet(q.getResultList()));
+ myPredicates.add(myBuilder.or(toArray(codePredicates)));
}
private void addPredicateString(String theParamName, List extends IQueryParameterType> theList) {
@@ -713,8 +725,6 @@ public class SearchBuilder {
continue;
}
- CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
-
boolean paramInverted = false;
List> tokens = Lists.newArrayList();
for (IQueryParameterType nextOrParams : nextAndParams) {
@@ -745,19 +755,11 @@ public class SearchBuilder {
if (paramInverted) {
ourLog.debug("Searching for _tag:not");
- CriteriaQuery cq = builder.createQuery(Long.class);
- Root newFrom = cq.from(ResourceTable.class);
-
- Subquery subQ = cq.subquery(Long.class);
+ Subquery subQ = myResourceTableQuery.subquery(Long.class);
Root subQfrom = subQ.from(ResourceTag.class);
subQ.select(subQfrom.get("myResourceId").as(Long.class));
- cq.select(newFrom.get("myId").as(Long.class));
-
- List andPredicates = new ArrayList();
- andPredicates = new ArrayList();
- andPredicates.add(builder.equal(newFrom.get("myResourceType"), myResourceName));
- andPredicates.add(builder.not(builder.in(newFrom.get("myId")).value(subQ)));
+ myPredicates.add(myBuilder.not(myBuilder.in(myResourceTableRoot.get("myId")).value(subQ)));
Subquery defJoin = subQ.subquery(Long.class);
Root defJoinFrom = defJoin.from(TagDefinition.class);
@@ -765,44 +767,18 @@ public class SearchBuilder {
subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin));
- List orPredicates = createPredicateTagList(defJoinFrom, builder, tagType, tokens);
+ List orPredicates = createPredicateTagList(defJoinFrom, myBuilder, tagType, tokens);
defJoin.where(toArray(orPredicates));
- cq.where(toArray(andPredicates));
-
- TypedQuery q = myEntityManager.createQuery(cq);
- Set pids = new HashSet(q.getResultList());
- doSetPids(pids);
continue;
}
- CriteriaQuery cq = builder.createQuery(Long.class);
- Root from = cq.from(ResourceTag.class);
- List andPredicates = new ArrayList();
- andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName));
- From defJoin = from.join("myTag");
+ Join tagJoin = myResourceTableRoot.join("myTags", JoinType.LEFT);
+ From defJoin = tagJoin.join("myTag");
+
+ List orPredicates = createPredicateTagList(defJoin, myBuilder, tagType, tokens);
+ myPredicates.add(myBuilder.or(toArray(orPredicates)));
- Join, ResourceTable> defJoin2 = from.join("myResource");
-
- Predicate notDeletedPredicatePrediate = builder.isNull(defJoin2.get("myDeleted"));
- andPredicates.add(notDeletedPredicatePrediate);
-
- List 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 q = myEntityManager.createQuery(cq);
- Set pids = new HashSet(q.getResultList());
- doSetPids(pids);
}
}
@@ -851,16 +827,13 @@ public class SearchBuilder {
return;
}
- CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
- CriteriaQuery cq = builder.createQuery(Long.class);
- Root from = cq.from(ResourceIndexedSearchParamUri.class);
- cq.select(from.get("myResourcePid").as(Long.class));
+ Join join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
List codePredicates = new ArrayList();
for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr;
- if (addPredicateMissingFalseIfPresent(builder, theParamName, from, codePredicates, nextOr)) {
+ if (addPredicateMissingFalseIfPresent(myBuilder, theParamName, join, codePredicates, nextOr)) {
continue;
}
@@ -872,7 +845,7 @@ public class SearchBuilder {
continue;
}
- Path