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 8eea659ea99..81dde526a60 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 @@ -1459,12 +1459,14 @@ public abstract class BaseHapiFhirDao implements IDao { } Set> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet(); - findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.STRING, stringParams); - findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams); - findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams); - findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.DATE, dateParams); - findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.URI, uriParams); - findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams); + if (myConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) { + findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.STRING, stringParams); + findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams); + findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams); + findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.DATE, dateParams); + findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.URI, uriParams); + findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams); + } setUpdatedTime(stringParams, theUpdateTime); setUpdatedTime(numberParams, theUpdateTime); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 51c4d600ad1..5d69fd2081f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -34,10 +34,7 @@ import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; -import ca.uhn.fhir.model.api.IQueryParameterAnd; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.QualifiedParamList; @@ -900,6 +897,19 @@ public abstract class BaseHapiFhirResourceDao extends B @Transactional(propagation = Propagation.SUPPORTS) @Override public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequestDetails) { + + if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) { + for (List> nextAnds : theParams.values()) { + for (List nextOrs : nextAnds) { + for (IQueryParameterType next : nextOrs) { + if (next.getMissing() != null) { + throw new MethodNotAllowedException(":missing modifier is disabled on this server"); + } + } + } + } + } + // Notify interceptors if (theRequestDetails != null) { ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), getResourceName(), null); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 22f8934bc64..8a30e78730b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -30,15 +30,6 @@ import java.util.*; public class DaoConfig { - /** - * Constructor - */ - public DaoConfig() { - setSubscriptionEnabled(true); - setSubscriptionPollDelay(0); - setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE); - } - /** * Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following * values: @@ -64,25 +55,22 @@ public class DaoConfig { * @see #setMaximumSearchResultCountInTransaction(Integer) */ private static final Integer DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = null; + private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.ENABLED; /** * update setter javadoc if default changes */ private boolean myAllowExternalReferences = false; - /** * update setter javadoc if default changes */ private boolean myAllowInlineMatchUrlReferences = true; private boolean myAllowMultipleDelete; private boolean myDefaultSearchParamsCanBeOverridden = false; - /** * update setter javadoc if default changes */ private int myDeferIndexingForCodesystemsOfSize = 2000; - private boolean myDeleteStaleSearches = true; - private boolean myEnforceReferentialIntegrityOnDelete = true; private boolean myUniqueIndexesEnabled = true; private boolean myUniqueIndexesCheckedBeforeSave = true; @@ -119,6 +107,14 @@ public class DaoConfig { private Set myTreatBaseUrlsAsLocal = new HashSet(); private Set myTreatReferencesAsLogical = new HashSet(DEFAULT_LOGICAL_BASE_URLS); private boolean myAutoCreatePlaceholderReferenceTargets; + /** + * Constructor + */ + public DaoConfig() { + setSubscriptionEnabled(true); + setSubscriptionPollDelay(0); + setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE); + } /** * Add a value to the {@link #setTreatReferencesAsLogical(Set) logical references list}. @@ -287,6 +283,35 @@ public class DaoConfig { myIncludeLimit = theIncludeLimit; } + /** + * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#ENABLED}) + * the server will not create search indexes for search parameters with no values in resources. + *

+ * Disabling this feature means that the :missing search modifier will not be + * supported on the server, but also means that storage and indexing (i.e. writes to the + * database) may be much faster on servers which have lots of search parameters and need + * to write quickly. + *

+ */ + public IndexEnabledEnum getIndexMissingFields() { + return myIndexMissingFieldsEnabled; + } + + /** + * If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#ENABLED}) + * the server will not create search indexes for search parameters with no values in resources. + *

+ * Disabling this feature means that the :missing search modifier will not be + * supported on the server, but also means that storage and indexing (i.e. writes to the + * database) may be much faster on servers which have lots of search parameters and need + * to write quickly. + *

+ */ + public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) { + Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null"); + myIndexMissingFieldsEnabled = theIndexMissingFields; + } + /** * Returns the interceptors which will be notified of operations. * @@ -420,24 +445,6 @@ public class DaoConfig { myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis; } - /** - * @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for - * detecting changes, so this setting has no effect - */ - @Deprecated - public void setSubscriptionPollDelay(long theSubscriptionPollDelay) { - // ignore - } - - /** - * @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for - * detecting changes, so this setting has no effect - */ - @Deprecated - public void setSubscriptionPurgeInactiveAfterMillis(Long theMillis) { - // ignore - } - /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be replaced with @@ -814,16 +821,6 @@ public class DaoConfig { mySchedulingDisabled = theSchedulingDisabled; } - - /** - * @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for - * detecting changes, so this setting has no effect - */ - @Deprecated - public void setSubscriptionEnabled(boolean theSubscriptionEnabled) { - // nothing - } - /** * If set to {@literal true} (default is true), if a client performs an update which does not actually * result in any chance to a given resource (e.g. an update where the resource body matches the @@ -926,6 +923,33 @@ public class DaoConfig { } } + /** + * @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for + * detecting changes, so this setting has no effect + */ + @Deprecated + public void setSubscriptionEnabled(boolean theSubscriptionEnabled) { + // nothing + } + + /** + * @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for + * detecting changes, so this setting has no effect + */ + @Deprecated + public void setSubscriptionPollDelay(long theSubscriptionPollDelay) { + // ignore + } + + /** + * @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for + * detecting changes, so this setting has no effect + */ + @Deprecated + public void setSubscriptionPurgeInactiveAfterMillis(Long theMillis) { + // ignore + } + public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) { setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND); } @@ -942,4 +966,9 @@ public class DaoConfig { } + public enum IndexEnabledEnum { + ENABLED, + DISABLED + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java index f804749897c..4d11ee17452 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java @@ -136,7 +136,7 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("paramName", getParamName()); - b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("resourceId", getResourcePid()); b.append("lat", getLatitude()); b.append("lon", getLongitude()); return b.build(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java index aafc52dbc26..283d10aaf95 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java @@ -146,7 +146,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("paramName", getParamName()); - b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("resourceId", getResourcePid()); b.append("system", getSystem()); b.append("units", getUnits()); b.append("value", getValue()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java index e7e345bf26c..4eaeb71957a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java @@ -118,6 +118,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP private String myValueNormalized; public ResourceIndexedSearchParamString() { + super(); } @@ -191,7 +192,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("paramName", getParamName()); - b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("resourceId", getResourcePid()); b.append("value", getValueNormalized()); return b.build(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java index 9ac5db3c141..8fdd3cc9a82 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java @@ -127,7 +127,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("paramName", getParamName()); - b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("resourceId", getResourcePid()); b.append("system", getSystem()); b.append("value", getValue()); return b.build(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java index e6ae403ff8e..75cd35949bd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java @@ -113,11 +113,12 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara @Override public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - builder.append("id", getId()); - builder.append("paramName", getParamName()); - builder.append("uri", myUri); - return builder.toString(); + ToStringBuilder b = new ToStringBuilder(this); + b.append("id", getId()); + b.append("resourceId", getResourcePid()); + b.append("paramName", getParamName()); + b.append("uri", myUri); + return b.toString(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java index 04dc881203a..41b9df3e3ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java @@ -24,6 +24,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; +import ca.uhn.fhir.jpa.dao.DaoConfig; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; @@ -45,8 +46,14 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { @Autowired private ISearchParamPresentDao mySearchParamPresentDao; + @Autowired + private DaoConfig myDaoConfig; + @Override public void updatePresence(ResourceTable theResource, Map theParamNameToPresence) { + if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) { + return; + } Map presenceMap = new HashMap(theParamNameToPresence); List entitiesToSave = new ArrayList(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 8ad1f1bc76b..f76d2508520 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -56,9 +56,19 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4; private static IFhirResourceDaoValueSet ourValueSetDao; + @Autowired + protected ISearchParamDao mySearchParamDao; + @Autowired + protected ISearchParamPresentDao mySearchParamPresentDao; @Autowired protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao; @Autowired + protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao; + @Autowired + protected IResourceIndexedSearchParamQuantityDao myResourceIndexedSearchParamQuantityDao; + @Autowired + protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao; + @Autowired protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; @Autowired @Qualifier("myAllergyIntoleranceDaoR4") diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java new file mode 100644 index 00000000000..476d49abd9c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java @@ -0,0 +1,340 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4SearchMissingTest.class); + + @Before + public void beforeResetMissing() { + myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); + } + + @Test + public void testIndexMissingFieldsDisabledDontAllowInSearch() { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + + SearchParameterMap params = new SearchParameterMap(); + params.add("foo", new StringParam().setMissing(true)); + try { + myPatientDao.search(params); + } catch (MethodNotAllowedException e) { + assertEquals(":missing modifier is disabled on this server", e.getMessage()); + } + } + + @Test + public void testIndexMissingFieldsDisabledDontCreateIndexes() { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + Organization org = new Organization(); + org.setActive(true); + myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + assertThat(mySearchParamDao.findAll(), empty()); + assertThat(mySearchParamPresentDao.findAll(), empty()); + assertThat(myResourceIndexedSearchParamStringDao.findAll(), empty()); + assertThat(myResourceIndexedSearchParamDateDao.findAll(), empty()); + assertThat(myResourceIndexedSearchParamTokenDao.findAll(), hasSize(1)); + assertThat(myResourceIndexedSearchParamQuantityDao.findAll(), empty()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchResourceReferenceMissingChain() { + IIdType oid1; + { + Organization org = new Organization(); + org.setActive(true); + oid1 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid1; + { + Task task = new Task(); + task.getRequester().setOnBehalfOf(new Reference(oid1)); + tid1 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid2; + { + Task task = new Task(); + task.setOwner(new Reference(oid1)); + tid2 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + + IIdType oid2; + { + Organization org = new Organization(); + org.setActive(true); + org.setName("NAME"); + oid2 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + } + IIdType tid3; + { + Task task = new Task(); + task.getRequester().setOnBehalfOf(new Reference(oid2)); + tid3 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap map; + List ids; + + map = new SearchParameterMap(); + map.add(Organization.SP_NAME, new StringParam().setMissing(true)); + ids = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); + assertThat(ids, contains(oid1)); + + ourLog.info("Starting Search 2"); + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "true")); + ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); + assertThat(ids, contains(tid1)); // NOT tid2 + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "false")); + ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); + assertThat(ids, contains(tid3)); + + map = new SearchParameterMap(); + map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "true")); + ids = toUnqualifiedVersionlessIds(myPatientDao.search(map)); + assertThat(ids, empty()); + + } + + @Test + public void testSearchWithMissingDate() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); + IIdType notMissing; + IIdType missing; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + // Date Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + DateParam param = new DateParam(); + param.setMissing(false); + params.add(Patient.SP_BIRTHDATE, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(notMissing)); + assertThat(patients, not(containsInRelativeOrder(missing))); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + DateParam param = new DateParam(); + param.setMissing(true); + params.add(Patient.SP_BIRTHDATE, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + @Test + public void testSearchWithMissingDate2() { + MedicationRequest mr1 = new MedicationRequest(); + mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); + IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); + + MedicationRequest mr2 = new MedicationRequest(); + mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(MedicationRequest.SP_DATE, new DateParam().setMissing(true)); + IBundleProvider results = myMedicationRequestDao.search(map); + List ids = toUnqualifiedVersionlessIdValues(results); + + assertThat(ids, contains(id2.getValue())); + + } + + @Test + public void testSearchWithMissingQuantity() { + IIdType notMissing; + IIdType missing; + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("002"); + obs.setValue(new Quantity(123)); + notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + // Quantity Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + QuantityParam param = new QuantityParam(); + param.setMissing(false); + params.add(Observation.SP_VALUE_QUANTITY, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + QuantityParam param = new QuantityParam(); + param.setMissing(true); + params.add(Observation.SP_VALUE_QUANTITY, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + @Test + public void testSearchWithMissingReference() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId().toUnqualifiedVersionless(); + IIdType notMissing; + IIdType missing; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + // Reference Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + ReferenceParam param = new ReferenceParam(); + param.setMissing(false); + params.add(Patient.SP_ORGANIZATION, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + ReferenceParam param = new ReferenceParam(); + param.setMissing(true); + params.add(Patient.SP_ORGANIZATION, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + assertThat(patients, not(containsInRelativeOrder(orgId))); + } + } + + @Test + public void testSearchWithMissingString() { + IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); + IIdType notMissing; + IIdType missing; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); + patient.setBirthDateElement(new DateType("2011-01-01")); + patient.getManagingOrganization().setReferenceElement(orgId); + notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + // String Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + StringParam param = new StringParam(); + param.setMissing(false); + params.add(Patient.SP_FAMILY, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + StringParam param = new StringParam(); + param.setMissing(true); + params.add(Patient.SP_FAMILY, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + + @Test + public void testSearchWithToken() { + IIdType notMissing; + IIdType missing; + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("001"); + missing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + { + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("002"); + obs.getCode().addCoding().setSystem("urn:system").setCode("002"); + notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + } + // Token Param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + TokenParam param = new TokenParam(); + param.setMissing(false); + params.add(Observation.SP_CODE, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + TokenParam param = new TokenParam(); + param.setMissing(true); + params.add(Observation.SP_CODE, param); + List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); + assertThat(patients, containsInRelativeOrder(missing)); + assertThat(patients, not(containsInRelativeOrder(notMissing))); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index a74e9d19cb6..42819bb0345 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -1782,68 +1782,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } - @SuppressWarnings("unused") - @Test - public void testSearchResourceReferenceMissingChain() { - IIdType oid1; - { - Organization org = new Organization(); - org.setActive(true); - oid1 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); - } - IIdType tid1; - { - Task task = new Task(); - task.getRequester().setOnBehalfOf(new Reference(oid1)); - tid1 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); - } - IIdType tid2; - { - Task task = new Task(); - task.setOwner(new Reference(oid1)); - tid2 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); - } - - IIdType oid2; - { - Organization org = new Organization(); - org.setActive(true); - org.setName("NAME"); - oid2 = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); - } - IIdType tid3; - { - Task task = new Task(); - task.getRequester().setOnBehalfOf(new Reference(oid2)); - tid3 = myTaskDao.create(task, mySrd).getId().toUnqualifiedVersionless(); - } - - SearchParameterMap map; - List ids; - - map = new SearchParameterMap(); - map.add(Organization.SP_NAME, new StringParam().setMissing(true)); - ids = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); - assertThat(ids, contains(oid1)); - - ourLog.info("Starting Search 2"); - - map = new SearchParameterMap(); - map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "true")); - ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); - assertThat(ids, contains(tid1)); // NOT tid2 - - map = new SearchParameterMap(); - map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "false")); - ids = toUnqualifiedVersionlessIds(myTaskDao.search(map)); - assertThat(ids, contains(tid3)); - - map = new SearchParameterMap(); - map.add(Task.SP_ORGANIZATION, new ReferenceParam("Organization", "name:missing", "true")); - ids = toUnqualifiedVersionlessIds(myPatientDao.search(map)); - assertThat(ids, empty()); - - } @SuppressWarnings("unused") @Test @@ -2665,187 +2603,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } - @Test - public void testSearchWithMissingDate() { - IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); - IIdType notMissing; - IIdType missing; - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("001"); - missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); - } - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("002"); - patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); - patient.setBirthDateElement(new DateType("2011-01-01")); - patient.getManagingOrganization().setReferenceElement(orgId); - notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); - } - // Date Param - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - DateParam param = new DateParam(); - param.setMissing(false); - params.add(Patient.SP_BIRTHDATE, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, containsInRelativeOrder(notMissing)); - assertThat(patients, not(containsInRelativeOrder(missing))); - } - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - DateParam param = new DateParam(); - param.setMissing(true); - params.add(Patient.SP_BIRTHDATE, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, containsInRelativeOrder(missing)); - assertThat(patients, not(containsInRelativeOrder(notMissing))); - } - } - - @Test - public void testSearchWithMissingDate2() { - MedicationRequest mr1 = new MedicationRequest(); - mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); - mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); - IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); - - MedicationRequest mr2 = new MedicationRequest(); - mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); - IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); - - SearchParameterMap map = new SearchParameterMap(); - map.add(MedicationRequest.SP_DATE, new DateParam().setMissing(true)); - IBundleProvider results = myMedicationRequestDao.search(map); - List ids = toUnqualifiedVersionlessIdValues(results); - - assertThat(ids, contains(id2.getValue())); - - } - - @Test - public void testSearchWithMissingQuantity() { - IIdType notMissing; - IIdType missing; - { - Observation obs = new Observation(); - obs.addIdentifier().setSystem("urn:system").setValue("001"); - missing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - } - { - Observation obs = new Observation(); - obs.addIdentifier().setSystem("urn:system").setValue("002"); - obs.setValue(new Quantity(123)); - notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - } - // Quantity Param - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - QuantityParam param = new QuantityParam(); - param.setMissing(false); - params.add(Observation.SP_VALUE_QUANTITY, param); - List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); - assertThat(patients, not(containsInRelativeOrder(missing))); - assertThat(patients, containsInRelativeOrder(notMissing)); - } - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - QuantityParam param = new QuantityParam(); - param.setMissing(true); - params.add(Observation.SP_VALUE_QUANTITY, param); - List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); - assertThat(patients, containsInRelativeOrder(missing)); - assertThat(patients, not(containsInRelativeOrder(notMissing))); - } - } - - @Test - public void testSearchWithMissingReference() { - IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId().toUnqualifiedVersionless(); - IIdType notMissing; - IIdType missing; - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("001"); - missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); - } - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("002"); - patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); - patient.setBirthDateElement(new DateType("2011-01-01")); - patient.getManagingOrganization().setReferenceElement(orgId); - notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); - } - // Reference Param - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - ReferenceParam param = new ReferenceParam(); - param.setMissing(false); - params.add(Patient.SP_ORGANIZATION, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, not(containsInRelativeOrder(missing))); - assertThat(patients, containsInRelativeOrder(notMissing)); - } - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - ReferenceParam param = new ReferenceParam(); - param.setMissing(true); - params.add(Patient.SP_ORGANIZATION, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, containsInRelativeOrder(missing)); - assertThat(patients, not(containsInRelativeOrder(notMissing))); - assertThat(patients, not(containsInRelativeOrder(orgId))); - } - } - - @Test - public void testSearchWithMissingString() { - IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId(); - IIdType notMissing; - IIdType missing; - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("001"); - missing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); - } - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("002"); - patient.addName().setFamily("Tester_testSearchStringParam").addGiven("John"); - patient.setBirthDateElement(new DateType("2011-01-01")); - patient.getManagingOrganization().setReferenceElement(orgId); - notMissing = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); - } - // String Param - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - StringParam param = new StringParam(); - param.setMissing(false); - params.add(Patient.SP_FAMILY, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, not(containsInRelativeOrder(missing))); - assertThat(patients, containsInRelativeOrder(notMissing)); - } - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - StringParam param = new StringParam(); - param.setMissing(true); - params.add(Patient.SP_FAMILY, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, containsInRelativeOrder(missing)); - assertThat(patients, not(containsInRelativeOrder(notMissing))); - } - } @Test public void testSearchWithNoResults() { @@ -3063,43 +2820,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } } - @Test - public void testSearchWithToken() { - IIdType notMissing; - IIdType missing; - { - Observation obs = new Observation(); - obs.addIdentifier().setSystem("urn:system").setValue("001"); - missing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - } - { - Observation obs = new Observation(); - obs.addIdentifier().setSystem("urn:system").setValue("002"); - obs.getCode().addCoding().setSystem("urn:system").setCode("002"); - notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - } - // Token Param - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - TokenParam param = new TokenParam(); - param.setMissing(false); - params.add(Observation.SP_CODE, param); - List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); - assertThat(patients, not(containsInRelativeOrder(missing))); - assertThat(patients, containsInRelativeOrder(notMissing)); - } - { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - TokenParam param = new TokenParam(); - param.setMissing(true); - params.add(Observation.SP_CODE, param); - List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params)); - assertThat(patients, containsInRelativeOrder(missing)); - assertThat(patients, not(containsInRelativeOrder(notMissing))); - } - } /** * https://chat.fhir.org/#narrow/stream/implementers/topic/Understanding.20_include diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 7f5322dc827..f7e589447a9 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -379,6 +379,11 @@ Subscriptions in JPA server now support "email" delivery type through the use of a new interceptor which handles that type + + JPA server can now be configured to not support + :missing]]> modifiers, which + increases write performance since fewer indexes are written +