UCUM Service Support (#2261)

* Added UcumServiceUtil

* Removed ucum-essense.xml

* Added ucum service existing test cases passed

* Added more test cases

* Merge branch 'master' into ft-ucum-support

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

* Added back extractReferenceParamsAsQueryTokens

* Added changelog and migration script

* Added length for the string type

* Moved UCUM util to hapi-fhir-jpaserver-model

* Renamed UCUM support to Normalized Quantity Search Support

* Moved setNormalizedQuantitySearchNotSupported to AfterEach

* Changed migrate task to 530

* Removed comments

* Fixed in memory search issue and added more test cases

* Fixed the version order

* License header updates

* Updated based on review comments

* Updated the migration task based on review comments

* Fixed RES_TYPE with type String and length 100

* Changed myParamsQuantityNormalizedPopulated type to Boolean class
This commit is contained in:
Frank Tao 2021-01-10 21:05:24 -05:00 committed by GitHub
parent 88b01a3bb4
commit f6d4a40388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2789 additions and 513 deletions

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2261
title: "Optionally supports storage and search in canonical form of the quantity value which is defined by 'http://unitsofmeasure.org';
please check ModelConfig for the configuration. No changes were made to the existing behaviour."

View File

@ -92,6 +92,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityNormalizedPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
@ -551,6 +552,12 @@ public abstract class BaseConfig {
return new QuantityPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public QuantityNormalizedPredicateBuilder newQuantityNormalizedPredicateBuilder(SearchQueryBuilder theSearchBuilder) {
return new QuantityNormalizedPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public ResourceLinkPredicateBuilder newResourceLinkPredicateBuilder(QueryStack theQueryStack, SearchQueryBuilder theSearchBuilder, boolean theReversed) {

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface IResourceIndexedSearchParamQuantityNormalizedDao extends JpaRepository<ResourceIndexedSearchParamQuantity, Long> {
@Modifying
@Query("delete from ResourceIndexedSearchParamQuantityNormalized t WHERE t.myResourcePid = :resid")
void deleteByResourceId(@Param("resid") Long theResourcePid);
}

View File

@ -53,6 +53,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
@ -126,6 +127,7 @@ public class ExpungeEverythingService {
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamDate.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamNumber.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamQuantity.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamQuantityNormalized.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamString.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamToken.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamUri.class));

View File

@ -33,6 +33,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamNumberDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityNormalizedDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
@ -90,6 +91,8 @@ public class ResourceExpungeService implements IResourceExpungeService {
@Autowired
private IResourceIndexedSearchParamQuantityDao myResourceIndexedSearchParamQuantityDao;
@Autowired
private IResourceIndexedSearchParamQuantityNormalizedDao myResourceIndexedSearchParamQuantityNormalizedDao;
@Autowired
private IResourceIndexedSearchParamCoordsDao myResourceIndexedSearchParamCoordsDao;
@Autowired
private IResourceIndexedSearchParamNumberDao myResourceIndexedSearchParamNumberDao;
@ -279,6 +282,9 @@ public class ResourceExpungeService implements IResourceExpungeService {
if (resource == null || resource.isParamsQuantityPopulated()) {
myResourceIndexedSearchParamQuantityDao.deleteByResourceId(theResourceId);
}
if (resource == null || resource.isParamsQuantityNormalizedPopulated()) {
myResourceIndexedSearchParamQuantityNormalizedDao.deleteByResourceId(theResourceId);
}
if (resource == null || resource.isParamsStringPopulated()) {
myResourceIndexedSearchParamStringDao.deleteByResourceId(theResourceId);
}

View File

@ -50,6 +50,7 @@ public class ResourceTableFKProvider {
retval.add(new ResourceForeignKey("HFJ_SPIDX_DATE", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_NUMBER", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_QUANTITY", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_QUANTITY_NRML", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_STRING", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_TOKEN", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_URI", "RES_ID"));

View File

@ -56,6 +56,7 @@ public class DaoSearchParamSynchronizer {
synchronize(theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
synchronize(theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
synchronize(theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
synchronize(theEntity, retVal, theParams.myQuantityNormalizedParams, existingParams.myQuantityNormalizedParams);
synchronize(theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
synchronize(theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
synchronize(theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
@ -87,6 +88,7 @@ public class DaoSearchParamSynchronizer {
for (T next : paramsToRemove) {
myEntityManager.remove(next);
theEntity.getParamsQuantity().remove(next);
theEntity.getParamsQuantityNormalized().remove(next);
}
for (T next : paramsToAdd) {
myEntityManager.merge(next);

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.search.builder;
/*-
/*
* #%L
* HAPI FHIR JPA Server
* %%
@ -38,7 +38,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityBasePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
@ -185,7 +185,14 @@ public class QueryStack {
public void addSortOnQuantity(String theResourceName, String theParamName, boolean theAscending) {
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
QuantityPredicateBuilder sortPredicateBuilder = mySqlBuilder.addQuantityPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
QuantityBasePredicateBuilder sortPredicateBuilder = null;
if (myModelConfig.isNormalizedQuantitySearchSupported()) {
sortPredicateBuilder = mySqlBuilder.addQuantityNormalizedPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
} else {
sortPredicateBuilder = mySqlBuilder.addQuantityPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
}
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate(theResourceName, theParamName);
mySqlBuilder.addPredicate(hashIdentityPredicate);
@ -635,9 +642,14 @@ public class QueryStack {
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
QuantityPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
QuantityBasePredicateBuilder join = null;
if (myModelConfig.isNormalizedQuantitySearchSupported()) {
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
} else {
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
}
if (theList.get(0).getMissing() != null) {
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
}
@ -911,7 +923,7 @@ public class QueryStack {
@Nullable
public Condition searchForIdsWithAndOr(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
if (theAndOrParams.isEmpty()) {
return null;
}
@ -1042,6 +1054,7 @@ public class QueryStack {
return toAndPredicate(andPredicates);
}
}
public void addPredicateCompositeUnique(String theIndexString, RequestPartitionId theRequestPartitionId) {

View File

@ -0,0 +1,131 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.math.BigDecimal;
import javax.persistence.criteria.CriteriaBuilder;
import org.fhir.ucum.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamBaseQuantity;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
public abstract class QuantityBasePredicateBuilder extends BaseSearchParamPredicateBuilder {
protected DbColumn myColumnHashIdentitySystemUnits;
protected DbColumn myColumnHashIdentityUnits;
protected DbColumn myColumnValue;
@Autowired
private FhirContext myFhirContext;
@Autowired
private ModelConfig myModelConfig;
/**
* Constructor
*/
public QuantityBasePredicateBuilder(SearchQueryBuilder theSearchSqlBuilder, DbTable theTable) {
super(theSearchSqlBuilder, theTable);
}
public Condition createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, QuantityBasePredicateBuilder theFrom, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
String systemValue;
String unitsValue;
ParamPrefixEnum cmpValue;
BigDecimal valueValue;
if (theParam instanceof BaseQuantityDt) {
BaseQuantityDt param = (BaseQuantityDt) theParam;
systemValue = param.getSystemElement().getValueAsString();
unitsValue = param.getUnitsElement().getValueAsString();
cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
valueValue = param.getValueElement().getValue();
} else if (theParam instanceof QuantityParam) {
QuantityParam param = (QuantityParam) theParam;
systemValue = param.getSystem();
unitsValue = param.getUnits();
cmpValue = param.getPrefix();
valueValue = param.getValue();
} else {
throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
}
if (myModelConfig.isNormalizedQuantitySearchSupported()) {
//-- convert the value/unit to the canonical form if any to use by the search
Pair canonicalForm = UcumServiceUtil.getCanonicalForm(systemValue, valueValue, unitsValue);
if (canonicalForm != null) {
valueValue = new BigDecimal(canonicalForm.getValue().asDecimal());
unitsValue = canonicalForm.getCode();
}
}
Condition hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamBaseQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = BinaryCondition.equalTo(myColumnHashIdentitySystemUnits, generatePlaceholder(hash));
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamBaseQuantity.calculateHashUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, unitsValue);
hashPredicate = BinaryCondition.equalTo(myColumnHashIdentityUnits, generatePlaceholder(hash));
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName);
hashPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hash));
}
SearchFilterParser.CompareOperation operation = theOperation;
if (operation == null && cmpValue != null) {
operation = QueryStack.toOperation(cmpValue);
}
operation = defaultIfNull(operation, SearchFilterParser.CompareOperation.eq);
Condition numericPredicate = NumberPredicateBuilder.createPredicateNumeric(this, operation, valueValue, myColumnValue, "invalidQuantityPrefix", myFhirContext, theParam);
return ComboCondition.and(hashPredicate, numericPredicate);
}
public DbColumn getColumnValue() {
return myColumnValue;
}
}

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.jpa.search.builder.predicate;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
public class QuantityNormalizedPredicateBuilder extends QuantityBasePredicateBuilder {
/**
* Constructor
*/
public QuantityNormalizedPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_QUANTITY_NRML"));
myColumnHashIdentitySystemUnits = getTable().addColumn("HASH_IDENTITY_SYS_UNITS");
myColumnHashIdentityUnits = getTable().addColumn("HASH_IDENTITY_AND_UNITS");
myColumnValue = getTable().addColumn("SP_VALUE");
}
}

View File

@ -20,96 +20,20 @@ package ca.uhn.fhir.jpa.search.builder.predicate;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.criteria.CriteriaBuilder;
import java.math.BigDecimal;
public class QuantityPredicateBuilder extends QuantityBasePredicateBuilder {
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class QuantityPredicateBuilder extends BaseSearchParamPredicateBuilder {
private final DbColumn myColumnHashIdentitySystemUnits;
private final DbColumn myColumnHashIdentityUnits;
private final DbColumn myColumnValue;
@Autowired
private FhirContext myFhirContext;
/**
* Constructor
*/
public QuantityPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_QUANTITY"));
myColumnHashIdentitySystemUnits = getTable().addColumn("HASH_IDENTITY_SYS_UNITS");
myColumnHashIdentityUnits = getTable().addColumn("HASH_IDENTITY_AND_UNITS");
myColumnValue = getTable().addColumn("SP_VALUE");
}
public Condition createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, QuantityPredicateBuilder theFrom, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
String systemValue;
String unitsValue;
ParamPrefixEnum cmpValue;
BigDecimal valueValue;
if (theParam instanceof BaseQuantityDt) {
BaseQuantityDt param = (BaseQuantityDt) theParam;
systemValue = param.getSystemElement().getValueAsString();
unitsValue = param.getUnitsElement().getValueAsString();
cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
valueValue = param.getValueElement().getValue();
} else if (theParam instanceof QuantityParam) {
QuantityParam param = (QuantityParam) theParam;
systemValue = param.getSystem();
unitsValue = param.getUnits();
cmpValue = param.getPrefix();
valueValue = param.getValue();
} else {
throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
}
Condition hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = BinaryCondition.equalTo(myColumnHashIdentitySystemUnits, generatePlaceholder(hash));
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, unitsValue);
hashPredicate = BinaryCondition.equalTo(myColumnHashIdentityUnits, generatePlaceholder(hash));
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName);
hashPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hash));
}
SearchFilterParser.CompareOperation operation = theOperation;
if (operation == null && cmpValue != null) {
operation = QueryStack.toOperation(cmpValue);
}
operation = defaultIfNull(operation, SearchFilterParser.CompareOperation.eq);
Condition numericPredicate = NumberPredicateBuilder.createPredicateNumeric(this, operation, valueValue, myColumnValue, "invalidQuantityPrefix", myFhirContext, theParam);
return ComboCondition.and(hashPredicate, numericPredicate);
}
public DbColumn getColumnValue() {
return myColumnValue;
}
}

View File

@ -33,6 +33,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityNormalizedPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
@ -201,11 +202,21 @@ public class SearchQueryBuilder {
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a QUANTITY search parameter
*/
public QuantityPredicateBuilder addQuantityPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
QuantityPredicateBuilder retVal = mySqlBuilderFactory.quantityIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
public QuantityNormalizedPredicateBuilder addQuantityNormalizedPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
QuantityNormalizedPredicateBuilder retVal = mySqlBuilderFactory.quantityNormalizedIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
}
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a <code>_source</code> search parameter
*/

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ForcedIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.QuantityNormalizedPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
@ -68,6 +69,10 @@ public class SqlObjectFactory {
return myApplicationContext.getBean(QuantityPredicateBuilder.class, theSearchSqlBuilder);
}
public QuantityNormalizedPredicateBuilder quantityNormalizedIndexTable(SearchQueryBuilder theSearchSqlBuilder) {
return myApplicationContext.getBean(QuantityNormalizedPredicateBuilder.class, theSearchSqlBuilder);
}
public ResourceLinkPredicateBuilder referenceIndexTable(QueryStack theQueryStack, SearchQueryBuilder theSearchSqlBuilder, boolean theReversed) {
return myApplicationContext.getBean(ResourceLinkPredicateBuilder.class, theQueryStack, theSearchSqlBuilder, theReversed);
}

View File

@ -488,6 +488,10 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamQuantityNormalized t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();
q = myEntityManager.createQuery("DELETE FROM ResourceIndexedSearchParamString t WHERE t.myResourcePid = :id");
q.setParameter("id", theId);
q.executeUpdate();

View File

@ -4,7 +4,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityNormalizedDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
@ -222,6 +223,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected IResourceIndexedSearchParamQuantityDao myResourceIndexedSearchParamQuantityDao;
@Autowired
protected IResourceIndexedSearchParamQuantityNormalizedDao myResourceIndexedSearchParamQuantityNormalizedDao;
@Autowired
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@Autowired
protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;

View File

@ -4,7 +4,7 @@ package ca.uhn.fhir.jpa.dao.r4;
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,33 +1,5 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SampledData;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import java.io.IOException;
import java.util.Date;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
@ -36,6 +8,41 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.SampledData;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class);
@ -44,6 +51,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@Test
@ -71,7 +79,6 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
map.setLoadSynchronous(true);
map.add(Patient.SP_GIVEN, new StringParam("")); // rightmost character only
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
}
@Test
@ -329,6 +336,150 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
}
@Test
public void testCreateWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
q.setValueElement(new DecimalType(1.2));
q.setUnit("CM");
q.setSystem("http://unitsofmeasure.org");
q.setCode("cm");
obs.setValue(q);
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
assertTrue(myObservationDao.create(obs).getCreated());
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
QuantityParam qp = new QuantityParam();
qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
qp.setValue(new BigDecimal("0.012"));
qp.setUnits("m");
map.add(Observation.SP_VALUE_QUANTITY, qp);
IBundleProvider found = myObservationDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
assertEquals(1, ids.size());
ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
}
@Test
public void testCreateWithNormalizedQuantitySearchSupportedWithVerySmallNumber() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
q.setValueElement(new DecimalType(0.0000012));
q.setUnit("MM");
q.setSystem("http://unitsofmeasure.org");
q.setCode("mm");
obs.setValue(q);
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
assertTrue(myObservationDao.create(obs).getCreated());
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
QuantityParam qp = new QuantityParam();
qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
qp.setValue(new BigDecimal("0.0000000012"));
qp.setUnits("m");
map.add(Observation.SP_VALUE_QUANTITY, qp);
IBundleProvider found = myObservationDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
assertEquals(1, ids.size());
ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
}
@Test
public void testCreateWithNormalizedQuantitySearchSupportedWithVerySmallNumber2() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
q.setValueElement(new DecimalType(149597.870691));
q.setUnit("MM");
q.setSystem("http://unitsofmeasure.org");
q.setCode("mm");
obs.setValue(q);
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
assertTrue(myObservationDao.create(obs).getCreated());
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
QuantityParam qp = new QuantityParam();
qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
qp.setValue(new BigDecimal("149.597870691"));
qp.setUnits("m");
map.add(Observation.SP_VALUE_QUANTITY, qp);
IBundleProvider found = myObservationDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
assertEquals(1, ids.size());
ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
}
@Test
public void testCreateWithNormalizedQuantitySearchSupportedWithLargeNumber() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
q.setValueElement(new DecimalType(95.7412345));
q.setUnit("kg/dL");
q.setSystem("http://unitsofmeasure.org");
q.setCode("kg/dL");
obs.setValue(q);
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
assertTrue(myObservationDao.create(obs).getCreated());
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
QuantityParam qp = new QuantityParam();
qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
qp.setValue(new BigDecimal("957412345"));
qp.setUnits("g.m-3");
map.add(Observation.SP_VALUE_QUANTITY, qp);
IBundleProvider found = myObservationDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
assertEquals(1, ids.size());
ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
}
}

View File

@ -86,6 +86,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
@AfterEach
public void after() {
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@BeforeEach
@ -97,6 +98,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
@Test
public void testStoreSearchParamWithBracketsInExpression() {
myDaoConfig.setMarkResourcesForReindexingUponSearchParameterChange(true);
SearchParameter fooSp = new SearchParameter();
@ -113,6 +115,47 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
mySearchParamRegistry.forceRefresh();
}
@Test
public void testStoreSearchParamWithBracketsInExpressionNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
myDaoConfig.setMarkResourcesForReindexingUponSearchParameterChange(true);
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.addBase("ActivityDefinition");
fooSp.setType(Enumerations.SearchParamType.REFERENCE);
fooSp.setTitle("FOO SP");
fooSp.setExpression("(ActivityDefinition.useContext.value as Quantity) | (ActivityDefinition.useContext.value as Range)");
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
// Ensure that no exceptions are thrown
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegistry.forceRefresh();
}
@Test
public void testStoreSearchParamWithBracketsInExpressionNormalizedQuantityStorageSupported() {
myModelConfig.setNormalizedQuantityStorageSupported();
myDaoConfig.setMarkResourcesForReindexingUponSearchParameterChange(true);
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.addBase("ActivityDefinition");
fooSp.setType(Enumerations.SearchParamType.REFERENCE);
fooSp.setTitle("FOO SP");
fooSp.setExpression("(ActivityDefinition.useContext.value as Quantity) | (ActivityDefinition.useContext.value as Range)");
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
// Ensure that no exceptions are thrown
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegistry.forceRefresh();
}
/**
* See #2023
*/

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.rest.param.TokenParam;
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.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@ -30,6 +31,11 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
public void beforeResetMissing() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
}
@AfterEach
public void afterResetSearch() {
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@Test
public void testIndexMissingFieldsDisabledDontAllowInSearch_NonReference() {
@ -72,6 +78,40 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
}
@Test
public void testIndexMissingFieldsDisabledDontCreateIndexesWithNormalizedQuantitySearchSupported() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
myModelConfig.setNormalizedQuantitySearchSupported();
Organization org = new Organization();
org.setActive(true);
myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
assertThat(mySearchParamPresentDao.findAll(), empty());
assertThat(myResourceIndexedSearchParamStringDao.findAll(), empty());
assertThat(myResourceIndexedSearchParamDateDao.findAll(), empty());
assertThat(myResourceIndexedSearchParamTokenDao.findAll(), hasSize(1));
assertThat(myResourceIndexedSearchParamQuantityDao.findAll(), empty());
}
@Test
public void testIndexMissingFieldsDisabledDontCreateIndexesWithNormalizedQuantityStorageSupported() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
myModelConfig.setNormalizedQuantityStorageSupported();
Organization org = new Organization();
org.setActive(true);
myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
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() {
@ -267,6 +307,89 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
}
}
@Test
public void testSearchWithMissingQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
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<IIdType> 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<IIdType> patients = toUnqualifiedVersionlessIds(myObservationDao.search(params));
assertThat(patients, containsInRelativeOrder(missing));
assertThat(patients, not(containsInRelativeOrder(notMissing)));
}
}
@Test
public void testSearchWithMissingQuantityWithNormalizedQuantityStorageSupported() {
myModelConfig.setNormalizedQuantityStorageSupported();
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<IIdType> 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<IIdType> 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();

View File

@ -1,58 +1,39 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.collect.Lists;
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
@ -134,38 +115,61 @@ import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
@SuppressWarnings({"unchecked", "Duplicates"})
public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@ -181,6 +185,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@BeforeEach
@ -1217,11 +1222,139 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertEquals(2, results.size());
});
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(0, results.size());
});
List<IIdType> actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, "http://foo", "UNIT"))));
assertThat(actual, contains(id));
}
@Test
public void testIndexNoDuplicatesQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Substance res = new Substance();
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232);
res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232);
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantity> type = ResourceIndexedSearchParamQuantity.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(2, results.size());
});
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(2, results.size());
});
List<IIdType> actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 12300, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm"))));
assertThat(actual, contains(id));
}
@Test
public void testQuantityWithNormalizedQuantitySearchSupported_InvalidUCUMCode() {
myModelConfig.setNormalizedQuantitySearchSupported();
Substance res = new Substance();
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("FOO").setValue(123);
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantity> type = ResourceIndexedSearchParamQuantity.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(1, results.size());
});
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(1, results.size());
});
List<IIdType> actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, UcumServiceUtil.UCUM_CODESYSTEM_URL, "FOO"))));
assertThat(actual, contains(id));
}
@Test
public void testQuantityWithNormalizedQuantitySearchSupported_NotUCUM() {
myModelConfig.setNormalizedQuantitySearchSupported();
Substance res = new Substance();
res.addInstance().getQuantity().setSystem("http://bar").setCode("FOO").setValue(123);
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantity> type = ResourceIndexedSearchParamQuantity.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(1, results.size());
});
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(1, results.size());
});
List<IIdType> actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, "http://bar", "FOO"))));
assertThat(actual, contains(id));
}
@Test
public void testIndexNoDuplicatesQuantityWithNormalizedQuantityStorageSupported() {
myModelConfig.setNormalizedQuantityStorageSupported();
Substance res = new Substance();
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232);
res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232);
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantity> type = ResourceIndexedSearchParamQuantity.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(2, results.size());
});
runInTransaction(() -> {
Class<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(2, results.size());
});
List<IIdType> actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, UcumServiceUtil.UCUM_CODESYSTEM_URL, "m"))));
assertThat(actual, contains(id));
}
@Test
public void testIndexNoDuplicatesReference() {
ServiceRequest pr = new ServiceRequest();
@ -2618,7 +2751,32 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testSearchByMoneyParamWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
ChargeItem ci = new ChargeItem();
ci.getPriceOverride().setValue(123).setCurrency("$");
myChargeItemDao.create(ci);
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(ChargeItem.SP_PRICE_OVERRIDE, new QuantityParam().setValue(123));
assertEquals(1, myChargeItemDao.search(map).size().intValue());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(ChargeItem.SP_PRICE_OVERRIDE, new QuantityParam().setValue(123).setUnits("$"));
assertEquals(1, myChargeItemDao.search(map).size().intValue());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(ChargeItem.SP_PRICE_OVERRIDE, new QuantityParam().setValue(123).setUnits("$").setSystem("urn:iso:std:iso:4217"));
assertEquals(1, myChargeItemDao.search(map).size().intValue());
}
@Test
public void testSearchNameParam() {
IIdType id1;
@ -2959,6 +3117,30 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testSearchQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Condition c1 = new Condition();
c1.setAbatement(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(new SimpleQuantity().setValue(1L)));
String id1 = myConditionDao.create(c1).getId().toUnqualifiedVersionless().getValue();
Condition c2 = new Condition();
c2.setOnset(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(new SimpleQuantity().setValue(1L)));
String id2 = myConditionDao.create(c2).getId().toUnqualifiedVersionless().getValue();
{
IBundleProvider found = myConditionDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ABATEMENT_AGE, new QuantityParam("1")));
assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id1));
assertEquals(1, found.size().intValue());
}
{
IBundleProvider found = myConditionDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ONSET_AGE, new QuantityParam("1")));
assertThat(toUnqualifiedVersionlessIdValues(found), containsInAnyOrder(id2));
assertEquals(1, found.size().intValue());
}
}
@Test
public void testSearchResourceLinkOnCanonical() {

View File

@ -42,6 +42,8 @@ import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -141,6 +143,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setDisableHashBasedSearches(false);
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@BeforeEach
@ -1188,6 +1191,52 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
}
}
@Test
public void testComponentQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(1.2));
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("mm").setValue(2));
IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless();
String param = Observation.SP_COMPONENT_VALUE_QUANTITY;
{
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 0.012, UcumServiceUtil.UCUM_CODESYSTEM_URL, "m");
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
IBundleProvider result = myObservationDao.search(map);
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue()));
}
}
@Test
public void testComponentQuantityWithNormalizedQuantityStorageSupported() {
myModelConfig.setNormalizedQuantityStorageSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(1.2));
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("mm").setValue(2));
IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless();
String param = Observation.SP_COMPONENT_VALUE_QUANTITY;
{
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 1.2, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm");
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
IBundleProvider result = myObservationDao.search(map);
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue()));
}
}
@Test
public void testSearchCompositeParamQuantity() {
Observation o1 = new Observation();
@ -1241,6 +1290,62 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
}
}
@Test
public void testSearchCompositeParamQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm").setValue(10)); // 0.1m
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(12));// 0.012m
IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless();
Observation o2 = new Observation();
o2.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm").setValue(20)); //0.2m
o2.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code3")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm").setValue(22)); //0.022m
IIdType id2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless();
String param = Observation.SP_COMPONENT_CODE_VALUE_QUANTITY;
{
TokenParam v0 = new TokenParam("http://foo", "code1");
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 15, UcumServiceUtil.UCUM_CODESYSTEM_URL, "dm"); // 0.15m
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, val);
IBundleProvider result = myObservationDao.search(map);
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue()));
}
{
TokenParam v0 = new TokenParam("http://foo", "code1");
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 5, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm"); //0.05m
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val));
assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue(), id2.getValue()));
}
{
TokenParam v0 = new TokenParam("http://foo", "code4");
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 5, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm"); //0.05m
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val));
assertThat(toUnqualifiedVersionlessIdValues(result), empty());
}
{
TokenParam v0 = new TokenParam("http://foo", "code1");
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 5, UcumServiceUtil.UCUM_CODESYSTEM_URL, "m"); //5m
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
IBundleProvider result = myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(param, val));
assertThat(toUnqualifiedVersionlessIdValues(result), empty());
}
}
@Test
public void testSearchDateWrongParam() {
Patient p1 = new Patient();

View File

@ -42,6 +42,8 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
@ -159,6 +161,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete());
myDaoConfig.setEnforceReferenceTargetTypes(new DaoConfig().isEnforceReferenceTargetTypes());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@BeforeEach
@ -576,45 +579,149 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
}
}
@Test
public void testChoiceParamQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o3 = new Observation();
o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03");
o3.setValue(new Quantity(QuantityComparator.GREATER_THAN, 123.0, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm", "cm")); // 0.0123m
IIdType id3 = myObservationDao.create(o3, mySrd).getId();
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam(">100", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(1, found.size().intValue());
assertEquals(id3, found.getResources(0, 1).get(0).getIdElement());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("gt100", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(1, found.size().intValue());
assertEquals(id3, found.getResources(0, 1).get(0).getIdElement());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("<100", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(0, found.size().intValue());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("lt100", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(0, found.size().intValue());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(0, found.size().intValue());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("~120", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(1, found.size().intValue());
assertEquals(id3, found.getResources(0, 1).get(0).getIdElement());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("ap120", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(1, found.size().intValue());
assertEquals(id3, found.getResources(0, 1).get(0).getIdElement());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("eq123", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(1, found.size().intValue());
assertEquals(id3, found.getResources(0, 1).get(0).getIdElement());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("eq120", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(0, found.size().intValue());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("ne120", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(1, found.size().intValue());
assertEquals(id3, found.getResources(0, 1).get(0).getIdElement());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("ne123", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
assertEquals(0, found.size().intValue());
}
}
@Test
public void testChoiceParamQuantityPrecision() {
Observation o3 = new Observation();
o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03");
o3.setValue(new Quantity(null, 123.01, "foo", "bar", "bar"));
o3.setValue(new Quantity(null, 123.01, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm", "cm")); // 0.012301 m
IIdType id3 = myObservationDao.create(o3, mySrd).getId().toUnqualifiedVersionless();
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", "foo", "bar")).setLoadSynchronous(true));
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", "foo", "bar")).setLoadSynchronous(true));
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", "foo", "bar")).setLoadSynchronous(true));
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3));
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.010", "foo", "bar")).setLoadSynchronous(true));
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.010", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3));
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", "foo", "bar")).setLoadSynchronous(true));
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", "foo", "bar")).setLoadSynchronous(true));
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
}
@Test
public void testChoiceParamQuantityPrecisionWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o3 = new Observation();
o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03");
o3.setValue(new Quantity(null, 123.01, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm", "cm")); // 0.012301 m
IIdType id3 = myObservationDao.create(o3, mySrd).getId().toUnqualifiedVersionless();
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3));
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.010", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3));
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
{
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, Matchers.empty());
}
}
@Test
public void testChoiceParamString() {
@ -1417,7 +1524,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
myObservationDao.read(obs1id);
myObservationDao.read(obs2id);
}
@Test
public void testDeleteWithMatchUrl() {
String methodName = "testDeleteWithMatchUrl";
@ -3391,6 +3498,47 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
assertThat(actual, contains(id4, id3, id2, id1));
}
@Test
public void testSortByQuantityWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation res;
res = new Observation();
res.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(2)); // 0.02m
IIdType id2 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless();
res = new Observation();
res.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm").setValue(0.1)); // 0.01m
IIdType id1 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless();
res = new Observation();
res.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(0.03)); // 0.03m
IIdType id3 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless();
res = new Observation();
res.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(4)); // 0.04m
IIdType id4 = myObservationDao.create(res, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap pm = new SearchParameterMap();
pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY));
List<IIdType> actual = toUnqualifiedVersionlessIds(myObservationDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY, SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myObservationDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.setSort(new SortSpec(Observation.SP_VALUE_QUANTITY, SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myObservationDao.search(pm));
assertEquals(4, actual.size());
assertThat(actual, contains(id4, id3, id2, id1));
}
@Test
public void testSortByReference() {

View File

@ -110,6 +110,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
public void after() {
myDaoConfig.setAllowInlineMatchUrlReferences(false);
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@BeforeEach
@ -3127,6 +3128,80 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
assertEquals(id.getValue(), id2.getValue());
}
@Test
public void testTransactionWithConditionalUpdateDoesntUpdateIfNoChangeWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation obs = new Observation();
obs.addIdentifier()
.setSystem("http://acme.org")
.setValue("ID1");
obs
.getCode()
.addCoding()
.setSystem("http://loinc.org")
.setCode("29463-7");
obs.setEffective(new DateTimeType("2011-09-03T11:13:00-04:00"));
obs.setValue(new Quantity()
.setValue(new BigDecimal("123.4"))
.setCode("kg")
.setSystem("http://unitsofmeasure.org")
.setUnit("kg"));
Bundle input = new Bundle();
input.setType(BundleType.TRANSACTION);
input
.addEntry()
.setFullUrl("urn:uuid:0001")
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Observation?identifier=http%3A%2F%2Facme.org|ID1");
Bundle output = mySystemDao.transaction(mySrd, input);
assertEquals(1, output.getEntry().size());
IdType id = new IdType(output.getEntry().get(0).getResponse().getLocation());
assertEquals("Observation", id.getResourceType());
assertEquals("1", id.getVersionIdPart());
/*
* Try again with same contents
*/
Observation obs2 = new Observation();
obs2.addIdentifier()
.setSystem("http://acme.org")
.setValue("ID1");
obs2
.getCode()
.addCoding()
.setSystem("http://loinc.org")
.setCode("29463-7");
obs2.setEffective(new DateTimeType("2011-09-03T11:13:00-04:00"));
obs2.setValue(new Quantity()
.setValue(new BigDecimal("123.4"))
.setCode("kg")
.setSystem("http://unitsofmeasure.org")
.setUnit("kg"));
Bundle input2 = new Bundle();
input2.setType(BundleType.TRANSACTION);
input2
.addEntry()
.setFullUrl("urn:uuid:0001")
.setResource(obs2)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Observation?identifier=http%3A%2F%2Facme.org|ID1");
Bundle output2 = mySystemDao.transaction(mySrd, input2);
assertEquals(1, output2.getEntry().size());
IdType id2 = new IdType(output2.getEntry().get(0).getResponse().getLocation());
assertEquals("Observation", id2.getResourceType());
assertEquals("1", id2.getVersionIdPart());
assertEquals(id.getValue(), id2.getValue());
}
@Test
public void testTransactionWithIfMatch() {
Patient p = new Patient();

View File

@ -1,5 +1,40 @@
package ca.uhn.fhir.jpa.dao.r4;
import static java.util.Comparator.comparing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.hl7.fhir.dstu2.model.SimpleQuantity;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Range;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -12,6 +47,7 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
@ -22,36 +58,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.ReadOnlySearchParamCache;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.collect.Sets;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
public class SearchParamExtractorR4Test {
@ -334,6 +341,46 @@ public class SearchParamExtractorR4Test {
assertEquals(4, links.size());
}
@Test
public void testExtractComponentQuantityWithNormalizedQuantitySearchSupported() {
ModelConfig modelConfig = new ModelConfig();
modelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(200));
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
Set<ResourceIndexedSearchParamQuantityNormalized> links = extractor.extractSearchParamQuantityNormalized(o1);
ourLog.info("Links:\n {}", links.stream().map(t -> t.toString()).collect(Collectors.joining("\n ")));
assertEquals(2, links.size());
}
@Test
public void testExtractComponentQuantityValueWithNormalizedQuantitySearchSupported() {
ModelConfig modelConfig = new ModelConfig();
modelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(200));
RuntimeSearchParam existingCodeSp = mySearchParamRegistry.getActiveSearchParams("Observation").get("component-value-quantity");
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
List<String> list = extractor.extractParamValuesAsStrings(existingCodeSp, o1);
assertEquals(1, list.size());
}
private static class MySearchParamRegistry implements ISearchParamRegistry {

View File

@ -14,14 +14,19 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -57,6 +62,7 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
@AfterEach
public void afterDisableExpunge() {
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
@BeforeEach
@ -150,11 +156,18 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
myTwoVersionObservationId = myObservationDao.create(o).getId();
o.setStatus(Observation.ObservationStatus.AMENDED);
myTwoVersionObservationId = myObservationDao.update(o).getId();
CodeableConcept cc = o.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
o.setValue(new Quantity().setValueElement(new DecimalType(125.12)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
o = new Observation();
o.setStatus(Observation.ObservationStatus.FINAL);
myDeletedObservationId = myObservationDao.create(o).getId();
myDeletedObservationId = myObservationDao.delete(myDeletedObservationId).getId();
cc = o.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
o.setValue(new Quantity().setValueElement(new DecimalType(13.45)).setUnit("DM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm"));
}
private IFhirResourceDao<?> getDao(IIdType theId) {
@ -387,6 +400,50 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
assertExpunged(myDeletedObservationId);
}
@Test
public void testExpungeSystemEverythingWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
createStandardPatients();
mySystemDao.expunge(new ExpungeOptions()
.setExpungeEverything(true), null);
// Everything deleted
assertExpunged(myOneVersionPatientId);
assertExpunged(myTwoVersionPatientId.withVersion("1"));
assertExpunged(myTwoVersionPatientId.withVersion("2"));
assertExpunged(myDeletedPatientId.withVersion("1"));
assertExpunged(myDeletedPatientId);
// Everything deleted
assertExpunged(myOneVersionObservationId);
assertExpunged(myTwoVersionObservationId.withVersion("1"));
assertExpunged(myTwoVersionObservationId.withVersion("2"));
assertExpunged(myDeletedObservationId);
}
@Test
public void testExpungeSystemEverythingWithNormalizedQuantityStorageSupported() {
myModelConfig.setNormalizedQuantityStorageSupported();
createStandardPatients();
mySystemDao.expunge(new ExpungeOptions()
.setExpungeEverything(true), null);
// Everything deleted
assertExpunged(myOneVersionPatientId);
assertExpunged(myTwoVersionPatientId.withVersion("1"));
assertExpunged(myTwoVersionPatientId.withVersion("2"));
assertExpunged(myDeletedPatientId.withVersion("1"));
assertExpunged(myDeletedPatientId);
// Everything deleted
assertExpunged(myOneVersionObservationId);
assertExpunged(myTwoVersionObservationId.withVersion("1"));
assertExpunged(myTwoVersionObservationId.withVersion("2"));
assertExpunged(myDeletedObservationId);
}
@Test
public void testExpungeTypeOldVersionsAndDeleted() {
createStandardPatients();

View File

@ -163,7 +163,6 @@ public class ResourceProviderHasParamR4Test extends BaseResourceProviderR4Test {
ourLog.info("uri = " + uri);
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
System.out.println("ids.size() = " + ids.size());
assertThat(ids, contains(pid0.getValue()));
}

View File

@ -1,45 +1,52 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsInRelativeOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
@ -80,6 +87,7 @@ import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Device;
import org.hl7.fhir.r4.model.DiagnosticReport;
import org.hl7.fhir.r4.model.DocumentManifest;
@ -129,36 +137,58 @@ import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.r4.model.UnsignedIntType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.jupiter.api.*; import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.AopTestUtils;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.util.UrlUtil;
@SuppressWarnings("Duplicates")
public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
@ -187,6 +217,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE);
mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false);
mySearchCoordinatorSvcRaw.cancelAllActiveSearches();
myDaoConfig.getModelConfig().setNormalizedQuantitySearchNotSupported();
myClient.unregisterInterceptor(myCapturingInterceptor);
}
@ -727,7 +758,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
ourLog.info(resp);
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, resp);
ids = toUnqualifiedVersionlessIdValues(bundle);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
}
return ids;
}
@ -4050,6 +4083,154 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertTrue(value.before(after), new InstantDt(value) + " should be before " + new InstantDt(after));
}
@Test
public void testSearchWithNormalizedQuantitySearchSupported() throws Exception {
myDaoConfig.getModelConfig().setNormalizedQuantitySearchSupported();
IIdType pid0;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester").addGiven("Joe");
pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(125.12)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(13.45)).setUnit("DM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(1.45)).setUnit("M").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(25)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
// > 1m
String uri = ourServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt1|http://unitsofmeasure.org|m");
ourLog.info("uri = " + uri);
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(3, ids.size());
//>= 100cm
uri = ourServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt100|http://unitsofmeasure.org|cm");
ourLog.info("uri = " + uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(3, ids.size());
//>= 10dm
uri = ourServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt10|http://unitsofmeasure.org|dm");
ourLog.info("uri = " + uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(3, ids.size());
}
@Test
public void testSearchWithNormalizedQuantitySearchSupported_CombineUCUMOrNonUCUM() throws Exception {
myDaoConfig.getModelConfig().setNormalizedQuantitySearchSupported();
IIdType pid0;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester").addGiven("Joe");
pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
obs.setValue(new Quantity().setValueElement(new DecimalType(1)).setUnit("M").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
obs.setValue(new Quantity().setValueElement(new DecimalType(13.45)).setUnit("DM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
obs.setValue(new Quantity().setValueElement(new DecimalType(1.45)).setUnit("M").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
obs.setValue(new Quantity().setValueElement(new DecimalType(100)).setUnit("CM").setSystem("http://foo").setCode("cm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
// > 1m
String uri = ourServerBase + "/Observation?value-quantity=" + UrlUtil.escapeUrlParam("100|http://unitsofmeasure.org|cm,100|http://foo|cm");
ourLog.info("uri = " + uri);
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2, ids.size());
}
@Test
public void testSearchReusesNoParams() {
List<IBaseResource> resources = new ArrayList<>();
@ -5861,6 +6042,109 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testUpdateWithNormalizedQuantitySearchSupported() throws Exception {
myDaoConfig.getModelConfig().setNormalizedQuantitySearchSupported();
IIdType pid0;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester").addGiven("Joe");
pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester").addGiven("Joe");
myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(125.12)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
IIdType opid1 = myObservationDao.create(obs, mySrd).getId();
//-- update quantity
obs = new Observation();
obs.setId(opid1);
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(24.12)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
myObservationDao.update(obs, mySrd);
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(13.45)).setUnit("DM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("dm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(1.45)).setUnit("M").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReferenceElement(pid0);
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(25)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
// > 1m
String uri = ourServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt1|http://unitsofmeasure.org|m");
ourLog.info("uri = " + uri);
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2, ids.size());
//>= 100cm
uri = ourServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt100|http://unitsofmeasure.org|cm");
ourLog.info("uri = " + uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2, ids.size());
//>= 10dm
uri = ourServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt10|http://unitsofmeasure.org|dm");
ourLog.info("uri = " + uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2, ids.size());
}
private String toStr(Date theDate) {
return new InstantDt(theDate).getValueAsString();

View File

@ -1,8 +1,11 @@
package ca.uhn.fhir.jpa.subscription.module.matcher;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
@ -61,6 +64,7 @@ import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Task;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
@ -71,7 +75,6 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -95,6 +98,14 @@ public class InMemorySubscriptionMatcherR4Test {
@Autowired
MatchUrlService myMatchUrlService;
@Autowired
ModelConfig myModelConfig;
@AfterEach
public void after() throws Exception {
myModelConfig.setNormalizedQuantitySearchNotSupported();
}
private void assertMatched(Resource resource, SearchParameterMap params) {
InMemoryMatchResult result = match(resource, params);
assertTrue(result.supported(), result.getUnsupportedReason());
@ -235,7 +246,89 @@ public class InMemorySubscriptionMatcherR4Test {
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
assertMatched(o1, params);
}
@Test
public void testSearchWithNormalizedQuantitySearchSupported() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("cm")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(150));
String param1 = Observation.SP_COMPONENT_VALUE_QUANTITY;
QuantityParam v1 = new QuantityParam(null, 1.5, UcumServiceUtil.UCUM_CODESYSTEM_URL, "m");
SearchParameterMap params1 = new SearchParameterMap().setLoadSynchronous(true).add(param1, v1);
assertMatched(o1, params1);
Observation o2 = new Observation();
o2.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("cm")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(150));
String param2 = Observation.SP_COMPONENT_VALUE_QUANTITY;
QuantityParam v2 = new QuantityParam(null, 15, UcumServiceUtil.UCUM_CODESYSTEM_URL, "dm");
SearchParameterMap params2 = new SearchParameterMap().setLoadSynchronous(true).add(param2, v2);
assertMatched(o2, params2);
v2 = new QuantityParam(null, 150, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm");
params2 = new SearchParameterMap().setLoadSynchronous(true).add(param2, v2);
assertMatched(o2, params2);
}
@Test
public void testSearchWithNormalizedQuantitySearchSupported_InvalidUCUMUnit() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://bar").setCode("foo")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("foo").setValue(150));
String param1 = Observation.SP_COMPONENT_VALUE_QUANTITY;
QuantityParam v1 = new QuantityParam(null, 150, UcumServiceUtil.UCUM_CODESYSTEM_URL, "foo");
SearchParameterMap params1 = new SearchParameterMap().setLoadSynchronous(true).add(param1, v1);
assertMatched(o1, params1);
}
@Test
public void testSearchWithNormalizedQuantitySearchSupported_NoSystem() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://bar").setCode("foo")))
.setValue(new Quantity().setCode("foo").setValue(150));
String param1 = Observation.SP_COMPONENT_VALUE_QUANTITY;
QuantityParam v1 = new QuantityParam(null, 150, null, "foo");
SearchParameterMap params1 = new SearchParameterMap().setLoadSynchronous(true).add(param1, v1);
assertMatched(o1, params1);
}
@Test
public void testSearchWithNormalizedQuantitySearchSupported_NotUcumSystem() {
myModelConfig.setNormalizedQuantitySearchSupported();
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("cm")))
.setValue(new Quantity().setSystem("http://bar").setCode("cm").setValue(150));
String param1 = Observation.SP_COMPONENT_VALUE_QUANTITY;
QuantityParam v1 = new QuantityParam(null, 150, "http://bar", "cm");
SearchParameterMap params1 = new SearchParameterMap().setLoadSynchronous(true).add(param1, v1);
assertMatched(o1, params1);
}
@Test
public void testIdSupported() {
Observation o1 = new Observation();

View File

@ -76,12 +76,40 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
private void init530() {
Builder version = forVersion(VersionEnum.V5_3_0);
//-- TRM
version
.onTable("TRM_VALUESET_CONCEPT")
.dropIndex("20210104.1", "IDX_VS_CONCEPT_CS_CODE");
version
.onTable("TRM_VALUESET_CONCEPT")
.addIndex("20210104.2", "IDX_VS_CONCEPT_CSCD").unique(true).withColumns("VALUESET_PID", "SYSTEM_URL", "CODEVAL");
version
.onTable("TRM_VALUESET_CONCEPT")
.addIndex("20210104.2", "IDX_VS_CONCEPT_CSCD").unique(true).withColumns("VALUESET_PID", "SYSTEM_URL", "CODEVAL");
//-- Add new Table, HFJ_SPIDX_QUANTITY_NRML
version.addIdGenerator("20210109.1", "SEQ_SPIDX_QUANTITY_NRML");
Builder.BuilderAddTableByColumns pkg = version.addTableByColumns("20210109.2", "HFJ_SPIDX_QUANTITY_NRML", "SP_ID");
pkg.addColumn("RES_ID").nonNullable().type(ColumnTypeEnum.LONG);
pkg.addColumn("RES_TYPE").nonNullable().type(ColumnTypeEnum.STRING, 100);
pkg.addColumn("SP_UPDATED").nullable().type(ColumnTypeEnum.DATE_TIMESTAMP);
pkg.addColumn("SP_MISSING").nonNullable().type(ColumnTypeEnum.BOOLEAN);
pkg.addColumn("SP_NAME").nonNullable().type(ColumnTypeEnum.STRING, 100);
pkg.addColumn("SP_ID").nonNullable().type(ColumnTypeEnum.LONG);
pkg.addColumn("SP_SYSTEM").nullable().type(ColumnTypeEnum.STRING, 200);
pkg.addColumn("SP_UNITS").nullable().type(ColumnTypeEnum.STRING, 200);
pkg.addColumn("HASH_IDENTITY_AND_UNITS").nullable().type(ColumnTypeEnum.LONG);
pkg.addColumn("HASH_IDENTITY_SYS_UNITS").nullable().type(ColumnTypeEnum.LONG);
pkg.addColumn("HASH_IDENTITY").nullable().type(ColumnTypeEnum.LONG);
pkg.addColumn("SP_VALUE").nullable().type(ColumnTypeEnum.FLOAT);
pkg.addIndex("20210109.3", "IDX_SP_QNTY_NRML_HASH").unique(false).withColumns("HASH_IDENTITY","SP_VALUE");
pkg.addIndex("20210109.4", "IDX_SP_QNTY_NRML_HASH_UN").unique(false).withColumns("HASH_IDENTITY_AND_UNITS","SP_VALUE");
pkg.addIndex("20210109.5", "IDX_SP_QNTY_NRML_HASH_SYSUN").unique(false).withColumns("HASH_IDENTITY_SYS_UNITS","SP_VALUE");
pkg.addIndex("20210109.6", "IDX_SP_QNTY_NRML_UPDATED").unique(false).withColumns("SP_UPDATED");
pkg.addIndex("20210109.7", "IDX_SP_QNTY_NRML_RESID").unique(false).withColumns("RES_ID");
//pkg.addForeignKey("20210109.9", "FK_QNTY_NRML_RESID").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
//-- Link to the resourceTable
version.onTable("HFJ_RESOURCE").addColumn("20210109.10", "SP_QUANTITY_NRML_PRESENT").nullable().type(ColumnTypeEnum.BOOLEAN);
}
@ -96,7 +124,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.toColumn("GOLDEN_RESOURCE_PID")
.references("HFJ_RESOURCE", "RES_ID");
}
protected void init510() {
Builder version = forVersion(VersionEnum.V5_1_0);

View File

@ -15,6 +15,13 @@
<name>HAPI FHIR Model</name>
<dependencies>
<!-- FHIR -->
<dependency>
<groupId>org.fhir</groupId>
<artifactId>ucum</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>

View File

@ -89,12 +89,15 @@ public class ModelConfig {
private IPrimitiveType<Date> myPeriodIndexStartOfTime;
private IPrimitiveType<Date> myPeriodIndexEndOfTime;
private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
/**
* Constructor
*/
public ModelConfig() {
setPeriodIndexStartOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_START_OF_TIME));
setPeriodIndexEndOfTime(new DateTimeType(DEFAULT_PERIOD_INDEX_END_OF_TIME));
setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
}
/**
@ -585,4 +588,44 @@ public class ModelConfig {
}
}
/**
* Set the UCUM service support level
*
* <p>
* The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
* </p>
* <p>
* Here is the UCUM service support level
* <ul>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_FULL_SUPPORTED}, Quantity is stored in only in {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching. NOTE this option is not supported yet.</li>
* </ul>
* </p>
*
* @since 5.3.0
*/
public NormalizedQuantitySearchLevel getNormalizedQuantitySearchLevel() {
return myNormalizedQuantitySearchLevel;
}
public void setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel theNormalizedQuantitySearchLevel) {
myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
}
public boolean isNormalizedQuantitySearchSupported() {
return myNormalizedQuantitySearchLevel.equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
}
public boolean isNormalizedQuantityStorageSupported() {
return myNormalizedQuantitySearchLevel.equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
}
public void setNormalizedQuantitySearchNotSupported() {
myNormalizedQuantitySearchLevel = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED;
}
public void setNormalizedQuantityStorageSupported() {
myNormalizedQuantitySearchLevel = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED;
}
public void setNormalizedQuantitySearchSupported() {
myNormalizedQuantitySearchLevel = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED;
}
}

View File

@ -0,0 +1,37 @@
package ca.uhn.fhir.jpa.model.entity;
/**
* Support different UCUM services level for FHIR Quantity data type.
*
* @since 5.3.0
*/
public enum NormalizedQuantitySearchLevel {
/**
* default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.
*/
NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED,
/**
* Quantity is stored in both {@link ResourceIndexedSearchParamQuantity}
* and {@link ResourceIndexedSearchParamQuantityNormalized},
* but {@link ResourceIndexedSearchParamQuantity} is used by searching.
*/
NORMALIZED_QUANTITY_STORAGE_SUPPORTED,
/**
* Quantity is stored in both {@link ResourceIndexedSearchParamQuantity}
* and {@link ResourceIndexedSearchParamQuantityNormalized},
* {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.
*/
NORMALIZED_QUANTITY_SEARCH_SUPPORTED,
/**
* Quantity is stored in only in {@link ResourceIndexedSearchParamQuantityNormalized},
* {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.
* The existing non normalized quantity will be not supported
* NOTE this option is not supported in this release
*/
//NORMALIZED_QUANTITY_SEARCH_FULL_SUPPORTED,
}

View File

@ -0,0 +1,147 @@
package ca.uhn.fhir.jpa.model.entity;
/*
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
@MappedSuperclass
public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourceIndexedSearchParam {
private static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L;
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
@FullTextField
public String mySystem;
@Column(name = "SP_UNITS", nullable = true, length = MAX_LENGTH)
@FullTextField
public String myUnits;
/**
* @since 3.5.0 - At some point this should be made not-null
*/
@Column(name = "HASH_IDENTITY_AND_UNITS", nullable = true)
private Long myHashIdentityAndUnits;
/**
* @since 3.5.0 - At some point this should be made not-null
*/
@Column(name = "HASH_IDENTITY_SYS_UNITS", nullable = true)
private Long myHashIdentitySystemAndUnits;
/**
* @since 3.5.0 - At some point this should be made not-null
*/
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
public ResourceIndexedSearchParamBaseQuantity() {
super();
}
@Override
public void calculateHashes() {
String resourceType = getResourceType();
String paramName = getParamName();
String units = getUnits();
String system = getSystem();
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
setHashIdentityAndUnits(calculateHashUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, units));
setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, units));
}
public Long getHashIdentity() {
return myHashIdentity;
}
public void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}
public Long getHashIdentityAndUnits() {
return myHashIdentityAndUnits;
}
public void setHashIdentityAndUnits(Long theHashIdentityAndUnits) {
myHashIdentityAndUnits = theHashIdentityAndUnits;
}
public Long getHashIdentitySystemAndUnits() {
return myHashIdentitySystemAndUnits;
}
public void setHashIdentitySystemAndUnits(Long theHashIdentitySystemAndUnits) {
myHashIdentitySystemAndUnits = theHashIdentitySystemAndUnits;
}
public String getSystem() {
return mySystem;
}
public void setSystem(String theSystem) {
mySystem = theSystem;
}
public String getUnits() {
return myUnits;
}
public void setUnits(String theUnits) {
myUnits = theUnits;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
b.append(getResourceType());
b.append(getParamName());
b.append(getHashIdentity());
b.append(getHashIdentityAndUnits());
b.append(getHashIdentitySystemAndUnits());
return b.toHashCode();
}
public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashSystemAndUnits(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashUnits(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashUnits(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theUnits);
}
public static long calculateHashUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUnits);
}
}

View File

@ -20,16 +20,11 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.math.BigDecimal;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@ -40,11 +35,16 @@ import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Objects;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
//@formatter:off
@Embeddable
@ -57,49 +57,24 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
@Index(name = "IDX_SP_QUANTITY_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_QUANTITY_RESID", columnList = "RES_ID")
})
public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearchParam {
private static final int MAX_LENGTH = 200;
public class ResourceIndexedSearchParamQuantity extends ResourceIndexedSearchParamBaseQuantity {
private static final long serialVersionUID = 1L;
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
@FullTextField
public String mySystem;
@Column(name = "SP_UNITS", nullable = true, length = MAX_LENGTH)
@FullTextField
public String myUnits;
@Column(name = "SP_VALUE", nullable = true)
@ScaledNumberField
public BigDecimal myValue;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_QUANTITY", sequenceName = "SEQ_SPIDX_QUANTITY")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY")
@Column(name = "SP_ID")
private Long myId;
/**
* @since 3.5.0 - At some point this should be made not-null
*/
@Column(name = "HASH_IDENTITY_AND_UNITS", nullable = true)
private Long myHashIdentityAndUnits;
/**
* @since 3.5.0 - At some point this should be made not-null
*/
@Column(name = "HASH_IDENTITY_SYS_UNITS", nullable = true)
private Long myHashIdentitySystemAndUnits;
/**
* @since 3.5.0 - At some point this should be made not-null
*/
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@Column(name = "SP_VALUE", nullable = true)
@ScaledNumberField
public BigDecimal myValue;
public ResourceIndexedSearchParamQuantity() {
super();
}
public ResourceIndexedSearchParamQuantity(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) {
this();
setPartitionSettings(thePartitionSettings);
@ -118,21 +93,46 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
mySystem = source.mySystem;
myUnits = source.myUnits;
myValue = source.myValue;
myHashIdentity = source.myHashIdentity;
myHashIdentityAndUnits = source.myHashIdentitySystemAndUnits;
myHashIdentitySystemAndUnits = source.myHashIdentitySystemAndUnits;
setHashIdentity(source.getHashIdentity());
setHashIdentityAndUnits(source.getHashIdentityAndUnits());
setHashIdentitySystemAndUnits(source.getHashIdentitySystemAndUnits());
}
public BigDecimal getValue() {
return myValue;
}
public ResourceIndexedSearchParamQuantity setValue(BigDecimal theValue) {
myValue = theValue;
return this;
}
@Override
public Long getId() {
return myId;
}
@Override
public void calculateHashes() {
String resourceType = getResourceType();
String paramName = getParamName();
String units = getUnits();
String system = getSystem();
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
setHashIdentityAndUnits(calculateHashUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, units));
setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, units));
public void setId(Long theId) {
myId = theId;
}
@Override
public IQueryParameterType toQueryParameterType() {
return new QuantityParam(null, getValue(), getSystem(), getUnits());
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("paramName", getParamName());
b.append("resourceId", getResourcePid());
b.append("system", getSystem());
b.append("units", getUnits());
b.append("value", getValue());
b.append("missing", isMissing());
b.append("hashIdentitySystemAndUnits", getHashIdentitySystemAndUnits());
return b.build();
}
@Override
@ -154,99 +154,13 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
b.append(isMissing(), obj.isMissing());
b.append(getValue(), obj.getValue());
return b.isEquals();
}
public Long getHashIdentity() {
return myHashIdentity;
}
public void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}
public Long getHashIdentityAndUnits() {
return myHashIdentityAndUnits;
}
public void setHashIdentityAndUnits(Long theHashIdentityAndUnits) {
myHashIdentityAndUnits = theHashIdentityAndUnits;
}
private Long getHashIdentitySystemAndUnits() {
return myHashIdentitySystemAndUnits;
}
public void setHashIdentitySystemAndUnits(Long theHashIdentitySystemAndUnits) {
myHashIdentitySystemAndUnits = theHashIdentitySystemAndUnits;
}
@Override
public Long getId() {
return myId;
}
@Override
public void setId(Long theId) {
myId = theId;
}
public String getSystem() {
return mySystem;
}
public void setSystem(String theSystem) {
mySystem = theSystem;
}
public String getUnits() {
return myUnits;
}
public void setUnits(String theUnits) {
myUnits = theUnits;
}
public BigDecimal getValue() {
return myValue;
}
public ResourceIndexedSearchParamQuantity setValue(BigDecimal theValue) {
myValue = theValue;
return this;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
b.append(getResourceType());
b.append(getParamName());
b.append(getHashIdentity());
b.append(getHashIdentityAndUnits());
b.append(getHashIdentitySystemAndUnits());
return b.toHashCode();
}
@Override
public IQueryParameterType toQueryParameterType() {
return new QuantityParam(null, getValue(), getSystem(), getUnits());
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("paramName", getParamName());
b.append("resourceId", getResourcePid());
b.append("system", getSystem());
b.append("units", getUnits());
b.append("value", getValue());
b.append("missing", isMissing());
b.append("hashIdentitySystemAndUnits", myHashIdentitySystemAndUnits);
return b.build();
}
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof QuantityParam)) {
return false;
}
@ -279,26 +193,8 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
}
}
return retval;
}
public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashSystemAndUnits(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theSystem, theUnits);
}
public static long calculateHashUnits(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashUnits(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theUnits);
}
public static long calculateHashUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) {
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUnits);
}
}
}

View File

@ -0,0 +1,239 @@
package ca.uhn.fhir.jpa.model.entity;
/*
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.math.BigDecimal;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.fhir.ucum.Pair;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_QUANTITY_NRML", indexes = {
@Index(name = "IDX_SP_QNTY_NRML_HASH", columnList = "HASH_IDENTITY,SP_VALUE"),
@Index(name = "IDX_SP_QNTY_NRML_HASH_UN", columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QNTY_NRML_HASH_SYSUN", columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QNTY_NRML_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_QNTY_NRML_RESID", columnList = "RES_ID")
})
/**
* Support UCUM service
* @since 5.3.0
*
*/
public class ResourceIndexedSearchParamQuantityNormalized extends ResourceIndexedSearchParamBaseQuantity {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_QUANTITY_NRML", sequenceName = "SEQ_SPIDX_QUANTITY_NRML")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY_NRML")
@Column(name = "SP_ID")
private Long myId;
// Changed to double here for storing the value after converted to the CanonicalForm due to BigDecimal maps NUMBER(19,2)
// The precision may lost even to store 1.2cm which is 0.012m in the CanonicalForm
@Column(name = "SP_VALUE", nullable = true)
@ScaledNumberField
public Double myValue;
public ResourceIndexedSearchParamQuantityNormalized() {
super();
}
public ResourceIndexedSearchParamQuantityNormalized(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) {
this();
setPartitionSettings(thePartitionSettings);
setResourceType(theResourceType);
setParamName(theParamName);
setSystem(theSystem);
//-- convert the value/unit to the canonical form if any, otherwise store the original value/units pair
Pair canonicalForm = UcumServiceUtil.getCanonicalForm(theSystem, theValue, theUnits);
if (canonicalForm != null) {
setValue(Double.parseDouble(canonicalForm.getValue().asDecimal()));
setUnits(canonicalForm.getCode());
} else {
setValue(theValue);
setUnits(theUnits);
}
calculateHashes();
}
@Override
public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
super.copyMutableValuesFrom(theSource);
ResourceIndexedSearchParamQuantityNormalized source = (ResourceIndexedSearchParamQuantityNormalized) theSource;
mySystem = source.mySystem;
myUnits = source.myUnits;
myValue = source.myValue;
setHashIdentity(source.getHashIdentity());
setHashIdentityAndUnits(source.getHashIdentityAndUnits());
setHashIdentitySystemAndUnits(source.getHashIdentitySystemAndUnits());
}
//- myValue
public Double getValue() {
return myValue;
}
public ResourceIndexedSearchParamQuantityNormalized setValue(Double theValue) {
myValue = theValue;
return this;
}
public void setValue(BigDecimal theValue) {
if (theValue != null)
myValue = theValue.doubleValue();
}
public BigDecimal getValueBigDecimal() {
if (myValue == null)
return null;
return new BigDecimal(myValue);
}
//-- myId
@Override
public Long getId() {
return myId;
}
@Override
public void setId(Long theId) {
myId = theId;
}
@Override
public IQueryParameterType toQueryParameterType() {
return new QuantityParam(null, getValue(), getSystem(), getUnits());
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("paramName", getParamName());
b.append("resourceId", getResourcePid());
b.append("system", getSystem());
b.append("units", getUnits());
b.append("value", getValue());
b.append("missing", isMissing());
b.append("hashIdentitySystemAndUnits", getHashIdentitySystemAndUnits());
return b.build();
}
@Override
public boolean equals(Object theObj) {
if (this == theObj) {
return true;
}
if (theObj == null) {
return false;
}
if (!(theObj instanceof ResourceIndexedSearchParamQuantityNormalized)) {
return false;
}
ResourceIndexedSearchParamQuantityNormalized obj = (ResourceIndexedSearchParamQuantityNormalized) theObj;
EqualsBuilder b = new EqualsBuilder();
b.append(getResourceType(), obj.getResourceType());
b.append(getParamName(), obj.getParamName());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
b.append(isMissing(), obj.isMissing());
b.append(getValue(), obj.getValue());
return b.isEquals();
}
@Override
public boolean matches(IQueryParameterType theParam) {
if (!(theParam instanceof QuantityParam)) {
return false;
}
QuantityParam quantity = (QuantityParam) theParam;
boolean retval = false;
String quantitySystem = quantity.getSystem();
BigDecimal quantityValue = quantity.getValue();
Double quantityDoubleValue = null;
if (quantityValue != null)
quantityDoubleValue = quantityValue.doubleValue();
String quantityUnits = defaultString(quantity.getUnits());
//-- convert the value/unit to the canonical form if any, otherwise store the original value/units pair
Pair canonicalForm = UcumServiceUtil.getCanonicalForm(quantitySystem, quantityValue, quantityUnits);
if (canonicalForm != null) {
quantityDoubleValue = Double.parseDouble(canonicalForm.getValue().asDecimal());
quantityUnits = canonicalForm.getCode();
}
// Only match on system if it wasn't specified
if (quantitySystem == null && isBlank(quantityUnits)) {
if (Objects.equals(getValue(), quantityDoubleValue)) {
retval = true;
}
} else {
String unitsString = defaultString(getUnits());
if (quantitySystem == null) {
if (unitsString.equalsIgnoreCase(quantityUnits) &&
Objects.equals(getValue(), quantityDoubleValue)) {
retval = true;
}
} else if (isBlank(quantityUnits)) {
if (getSystem().equalsIgnoreCase(quantitySystem) &&
Objects.equals(getValue(), quantityDoubleValue)) {
retval = true;
}
} else {
if (getSystem().equalsIgnoreCase(quantitySystem) &&
unitsString.equalsIgnoreCase(quantityUnits) &&
Objects.equals(getValue(), quantityDoubleValue)) {
retval = true;
}
}
}
return retval;
}
}

View File

@ -32,13 +32,10 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.OptimisticLock;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Searchable;
import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate;
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.RoutingBinderRef;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue;
@ -149,6 +146,23 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
@Column(name = "SP_QUANTITY_PRESENT")
@OptimisticLock(excluded = true)
private boolean myParamsQuantityPopulated;
/**
* Added to support UCUM conversion
* since 5.3.0
*/
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
private Collection<ResourceIndexedSearchParamQuantityNormalized> myParamsQuantityNormalized;
/**
* Added to support UCUM conversion,
* NOTE : use Boolean class instead of boolean primitive, in order to set the existing rows to null
* since 5.3.0
*/
@Column(name = "SP_QUANTITY_NRML_PRESENT")
@OptimisticLock(excluded = true)
private Boolean myParamsQuantityNormalizedPopulated = Boolean.FALSE;
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@OptimisticLock(excluded = true)
@ -361,6 +375,21 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
getParamsQuantity().addAll(theQuantityParams);
}
public Collection<ResourceIndexedSearchParamQuantityNormalized> getParamsQuantityNormalized() {
if (myParamsQuantityNormalized == null) {
myParamsQuantityNormalized = new ArrayList<>();
}
return myParamsQuantityNormalized;
}
public void setParamsQuantityNormalized(Collection<ResourceIndexedSearchParamQuantityNormalized> theQuantityNormalizedParams) {
if (!isParamsQuantityNormalizedPopulated() && theQuantityNormalizedParams.isEmpty()) {
return;
}
getParamsQuantityNormalized().clear();
getParamsQuantityNormalized().addAll(theQuantityNormalizedParams);
}
public Collection<ResourceIndexedSearchParamString> getParamsString() {
if (myParamsString == null) {
myParamsString = new ArrayList<>();
@ -503,6 +532,20 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) {
myParamsQuantityPopulated = theParamsQuantityPopulated;
}
public Boolean isParamsQuantityNormalizedPopulated() {
if (myParamsQuantityNormalizedPopulated == null)
return Boolean.FALSE;
else
return myParamsQuantityNormalizedPopulated;
}
public void setParamsQuantityNormalizedPopulated(Boolean theParamsQuantityNormalizedPopulated) {
if (theParamsQuantityNormalizedPopulated == null)
myParamsQuantityNormalizedPopulated = Boolean.FALSE;
else
myParamsQuantityNormalizedPopulated = theParamsQuantityNormalizedPopulated;
}
public boolean isParamsStringPopulated() {
return myParamsStringPopulated;

View File

@ -0,0 +1,100 @@
package ca.uhn.fhir.jpa.model.util;
/*
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 java.io.InputStream;
import java.math.BigDecimal;
import org.fhir.ucum.Decimal;
import org.fhir.ucum.Pair;
import org.fhir.ucum.UcumEssenceService;
import org.fhir.ucum.UcumException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.util.ClasspathUtil;
/**
* It's a wrapper of UcumEssenceService
*
*/
public class UcumServiceUtil {
private static final Logger ourLog = LoggerFactory.getLogger(UcumServiceUtil.class);
public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org";
private static final String UCUM_SOURCE = "/ucum-essence.xml";
private static UcumEssenceService myUcumEssenceService = null;
private UcumServiceUtil() {
}
// lazy load UCUM_SOURCE only once
private static void init() {
if (myUcumEssenceService != null)
return;
synchronized (UcumServiceUtil.class) {
InputStream input = ClasspathUtil.loadResourceAsStream(UCUM_SOURCE);
try {
myUcumEssenceService = new UcumEssenceService(input);
} catch (UcumException e) {
ourLog.warn("Failed to load ucum code from ", UCUM_SOURCE, e);
} finally {
ClasspathUtil.close(input);
}
}
}
/**
* Get the canonical form of a code, it's define at
* <link>http://unitsofmeasure.org</link>
*
* e.g. 12cm -> 0.12m where m is the canonical form of the length.
*
* @param theSystem must be http://unitsofmeasure.org
* @param theValue the value in the original form e.g. 0.12
* @param theCode the code in the original form e.g. 'cm'
* @return the CanonicalForm if no error, otherwise return null
*/
public static Pair getCanonicalForm(String theSystem, BigDecimal theValue, String theCode) {
// -- only for http://unitsofmeasure.org
if (!UCUM_CODESYSTEM_URL.equals(theSystem) || theValue == null || theCode == null)
return null;
init();
Pair theCanonicalPair = null;
try {
Decimal theDecimal = new Decimal(theValue.toPlainString(), theValue.precision());
theCanonicalPair = myUcumEssenceService.getCanonicalForm(new Pair(theDecimal, theCode));
} catch (UcumException e) {
return null;
}
return theCanonicalPair;
}
}

View File

@ -0,0 +1,82 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamQuantityNormalizedTest {
private ResourceIndexedSearchParamQuantityNormalized createParam(String theParamName, String theValue, String theSystem, String theUnits) {
ResourceIndexedSearchParamQuantityNormalized token = new ResourceIndexedSearchParamQuantityNormalized(new PartitionSettings(), "Observation", theParamName, new BigDecimal(theValue), theSystem, theUnits);
token.setResource(new ResourceTable().setResourceType("Patient"));
return token;
}
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamQuantityNormalized token = createParam("Quanity", "123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm");
token.calculateHashes();
// Make sure our hashing function gives consistent results
assertEquals(5219730978980909111L, token.getHashIdentity().longValue());
assertEquals(-2454931617586657338L, token.getHashIdentityAndUnits().longValue());
assertEquals(878263047209296227L, token.getHashIdentitySystemAndUnits().longValue());
}
@Test
public void testEquals() {
ResourceIndexedSearchParamBaseQuantity val1 = new ResourceIndexedSearchParamQuantityNormalized()
.setValue(Double.parseDouble("123"));
val1.setPartitionSettings(new PartitionSettings());
val1.calculateHashes();
ResourceIndexedSearchParamBaseQuantity val2 = new ResourceIndexedSearchParamQuantityNormalized()
.setValue(Double.parseDouble("123"));
val2.setPartitionSettings(new PartitionSettings());
val2.calculateHashes();
assertEquals(val1, val1);
assertEquals(val1, val2);
assertNotEquals(val1, null);
assertNotEquals(val1, "");
}
@Test
public void testUcum() {
//-- system is ucum
ResourceIndexedSearchParamQuantityNormalized token1 = createParam("Quanity", "123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm");
token1.calculateHashes();
assertEquals("m", token1.getUnits());
assertEquals(Double.parseDouble("1.23001"), token1.getValue());
//-- small number
token1 = createParam("Quanity", "0.000001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "mm");
token1.calculateHashes();
assertEquals("m", token1.getUnits());
assertEquals(Double.parseDouble("0.000000001"), token1.getValue());
// -- non ucum system
ResourceIndexedSearchParamQuantityNormalized token2 = createParam("Quanity", "123.001", "http://abc.org", "cm");
token2.calculateHashes();
assertEquals("cm", token2.getUnits());
assertEquals(Double.parseDouble("123.001"), token2.getValue());
// -- unsupported ucum code
ResourceIndexedSearchParamQuantityNormalized token3 = createParam("Quanity", "123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "unknown");
token3.calculateHashes();
assertEquals("unknown", token3.getUnits());
assertEquals(Double.parseDouble("123.001"), token3.getValue());
}
}

View File

@ -0,0 +1,55 @@
package ca.uhn.fhir.jpa.model.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
public class UcumServiceUtilTest {
@Test
public void testCanonicalForm() {
assertEquals(Double.parseDouble("0.000012"),
Double.parseDouble(UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(0.012), "mm").getValue().asDecimal()));
assertEquals(Double.parseDouble("149.597870691"),
Double.parseDouble(UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(149597.870691), "mm").getValue().asDecimal()));
assertEquals("0.0025 m", UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), "mm").toString());
assertEquals("0.025 m", UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), "cm").toString());
assertEquals("0.25 m", UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), "dm").toString());
assertEquals("2.5 m", UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), "m").toString());
assertEquals("2500 m", UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), "km").toString());
assertEquals(Double.parseDouble("957.4"),
Double.parseDouble(UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(95.74), "mg/dL").getValue().asDecimal()));
assertEquals(Double.parseDouble("957400.0"),
Double.parseDouble(UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(95.74), "g/dL").getValue().asDecimal()));
//-- code g.m-3
assertEquals(Double.parseDouble("957400000"),
Double.parseDouble(UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(95.74), "kg/dL").getValue().asDecimal()));
}
@Test
public void testInvalidCanonicalForm() {
//-- invalid url
assertEquals(null, UcumServiceUtil.getCanonicalForm("url", new BigDecimal(2.5), "cm"));
//-- missing value
assertEquals(null, UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, null, "dm"));
//-- missing code
assertEquals(null, UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), null));
//-- invalid codes
assertEquals(null, UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(2.5), "xyz"));
}
}

View File

@ -34,6 +34,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
@ -200,7 +201,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
extractor = createReferenceExtractor();
return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor);
case QUANTITY:
extractor = createQuantityExtractor(theResource);
if (myModelConfig.isNormalizedQuantitySearchSupported())
extractor = createQuantityNormalizedExtractor(theResource);
else
extractor = createQuantityExtractor(theResource);
break;
case URI:
extractor = createUriExtractor(theResource);
@ -373,7 +377,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
}
private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) {
@Override
public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
}
private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
@ -383,7 +394,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String resourceType = toRootTypeName(theResource);
switch (nextType) {
case "Quantity":
addQuantity_Quantity(resourceType, params, searchParam, value);
addQuantity_Quantity(resourceType, params, searchParam, value);
break;
case "Money":
addQuantity_Money(resourceType, params, searchParam, value);
@ -395,9 +406,35 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
addUnexpectedDatatypeWarning(params, searchParam, value);
break;
}
};
}
private IExtractor<ResourceIndexedSearchParamQuantityNormalized> createQuantityNormalizedExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
}
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
switch (nextType) {
case "Quantity":
addQuantity_QuantityNormalized(resourceType, params, searchParam, value);
break;
case "Money":
addQuantity_MoneyNormalized(resourceType, params, searchParam, value);
break;
case "Range":
addQuantity_RangeNormalized(resourceType, params, searchParam, value);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value);
break;
}
};
}
@Override
public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource);
@ -502,7 +539,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private void addQuantity_Quantity(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
@ -510,13 +546,25 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
theParams.add(nextEntity);
}
}
private void addQuantity_QuantityNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
ResourceIndexedSearchParamQuantityNormalized nextEntity = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
theParams.add(nextEntity);
}
}
private void addQuantity_Money(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
@ -524,21 +572,45 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
theParams.add(nextEntity);
}
}
}
private void addQuantity_Range(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
private void addQuantity_MoneyNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
theParams.add(nextEntityNormalized);
}
}
private void addQuantity_Range(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
}
private void addQuantity_RangeNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
}
private void addToken_Identifier(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
String system = extractValueAsString(myIdentifierSystemValueChild, theValue);
String value = extractValueAsString(myIdentifierValueValueChild, theValue);
@ -553,7 +625,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
}
}
}
protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) {

View File

@ -1,21 +1,5 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
/*
* #%L
@ -37,6 +21,24 @@ import java.util.List;
* #L%
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
public interface ISearchParamExtractor {
// SearchParamSet<ResourceIndexedSearchParamCoords> extractSearchParamCoords(IBaseResource theResource);
@ -47,6 +49,8 @@ public interface ISearchParamExtractor {
SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource);
SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource);
SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
/*-
* #%L
* HAPI FHIR Search Parameters
@ -29,6 +30,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
@ -58,6 +60,7 @@ public final class ResourceIndexedSearchParams {
final public Collection<ResourceIndexedSearchParamToken> myTokenParams = new HashSet<>();
final public Collection<ResourceIndexedSearchParamNumber> myNumberParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamQuantity> myQuantityParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamQuantityNormalized> myQuantityNormalizedParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamDate> myDateParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamUri> myUriParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamCoords> myCoordsParams = new ArrayList<>();
@ -82,6 +85,9 @@ public final class ResourceIndexedSearchParams {
if (theEntity.isParamsQuantityPopulated()) {
myQuantityParams.addAll(theEntity.getParamsQuantity());
}
if (theEntity.isParamsQuantityNormalizedPopulated()) {
myQuantityNormalizedParams.addAll(theEntity.getParamsQuantityNormalized());
}
if (theEntity.isParamsDatePopulated()) {
myDateParams.addAll(theEntity.getParamsDate());
}
@ -110,6 +116,7 @@ public final class ResourceIndexedSearchParams {
theEntity.setParamsTokenPopulated(myTokenParams.isEmpty() == false);
theEntity.setParamsNumberPopulated(myNumberParams.isEmpty() == false);
theEntity.setParamsQuantityPopulated(myQuantityParams.isEmpty() == false);
theEntity.setParamsQuantityNormalizedPopulated(myQuantityNormalizedParams.isEmpty() == false);
theEntity.setParamsDatePopulated(myDateParams.isEmpty() == false);
theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false);
theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false);
@ -123,6 +130,7 @@ public final class ResourceIndexedSearchParams {
theEntity.setParamsToken(myTokenParams);
theEntity.setParamsNumber(myNumberParams);
theEntity.setParamsQuantity(myQuantityParams);
theEntity.setParamsQuantityNormalized(myQuantityNormalizedParams);
theEntity.setParamsDate(myDateParams);
theEntity.setParamsUri(myUriParams);
theEntity.setParamsCoords(myCoordsParams);
@ -133,6 +141,7 @@ public final class ResourceIndexedSearchParams {
setUpdatedTime(myStringParams, theUpdateTime);
setUpdatedTime(myNumberParams, theUpdateTime);
setUpdatedTime(myQuantityParams, theUpdateTime);
setUpdatedTime(myQuantityNormalizedParams, theUpdateTime);
setUpdatedTime(myDateParams, theUpdateTime);
setUpdatedTime(myUriParams, theUpdateTime);
setUpdatedTime(myCoordsParams, theUpdateTime);
@ -150,6 +159,7 @@ public final class ResourceIndexedSearchParams {
}
public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
if (theParamDef == null) {
return false;
}
@ -159,7 +169,10 @@ public final class ResourceIndexedSearchParams {
resourceParams = myTokenParams;
break;
case QUANTITY:
resourceParams = myQuantityParams;
if (theModelConfig.isNormalizedQuantitySearchSupported())
resourceParams = myQuantityNormalizedParams;
else
resourceParams = myQuantityParams;
break;
case STRING:
resourceParams = myStringParams;
@ -250,6 +263,7 @@ public final class ResourceIndexedSearchParams {
", tokenParams=" + myTokenParams +
", numberParams=" + myNumberParams +
", quantityParams=" + myQuantityParams +
", quantityNormalizedParams=" + myQuantityNormalizedParams +
", dateParams=" + myDateParams +
", uriParams=" + myUriParams +
", coordsParams=" + myCoordsParams +
@ -261,7 +275,10 @@ public final class ResourceIndexedSearchParams {
public void findMissingSearchParams(PartitionSettings thePartitionSettings, ModelConfig theModelConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) {
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
if (theModelConfig.isNormalizedQuantitySearchSupported())
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityNormalizedParams);
else
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams);
@ -292,7 +309,10 @@ public final class ResourceIndexedSearchParams {
param = new ResourceIndexedSearchParamNumber();
break;
case QUANTITY:
param = new ResourceIndexedSearchParamQuantity();
if (theModelConfig.isNormalizedQuantitySearchSupported())
param = new ResourceIndexedSearchParamQuantityNormalized();
else
param = new ResourceIndexedSearchParamQuantity();
break;
case STRING:
param = new ResourceIndexedSearchParamString()
@ -382,8 +402,7 @@ public final class ResourceIndexedSearchParams {
return queryStringsToPopulate;
}
private static void extractCompositeStringUniquesValueChains(String
theResourceType, List<List<String>> thePartsChoices, List<String> theValues, Set<String> theQueryStringsToPopulate) {
private static void extractCompositeStringUniquesValueChains(String theResourceType, List<List<String>> thePartsChoices, List<String> theValues, Set<String> theQueryStringsToPopulate) {
if (thePartsChoices.size() > 0) {
List<String> nextList = thePartsChoices.get(0);
Collections.sort(nextList);

View File

@ -35,6 +35,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
@ -103,7 +104,7 @@ public class SearchParamExtractorService {
}
private void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
// Strings
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> strings = extractSearchParamStrings(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings);
@ -118,7 +119,13 @@ public class SearchParamExtractorService {
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> quantities = extractSearchParamQuantity(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities);
theParams.myQuantityParams.addAll(quantities);
if (myModelConfig.isNormalizedQuantityStorageSupported()|| myModelConfig.isNormalizedQuantitySearchSupported()) {
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> quantitiesNormalized = extractSearchParamQuantityNormalized(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized);
theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized);
}
// Dates
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> dates = extractSearchParamDates(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates);
@ -151,6 +158,7 @@ public class SearchParamExtractorService {
// Do this after, because we add to strings during both string and token processing
populateResourceTable(theParams.myNumberParams, theEntity);
populateResourceTable(theParams.myQuantityParams, theEntity);
populateResourceTable(theParams.myQuantityNormalizedParams, theEntity);
populateResourceTable(theParams.myDateParams, theEntity);
populateResourceTable(theParams.myUriParams, theEntity);
populateResourceTable(theParams.myTokenParams, theEntity);
@ -378,6 +386,10 @@ public class SearchParamExtractorService {
return mySearchParamExtractor.extractSearchParamQuantity(theResource);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamStrings(theResource);
}

View File

@ -4,7 +4,7 @@ package ca.uhn.fhir.jpa.subscription.module;
* #%L
* HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.