Order Location searches using near in closest distance to center of the box (#4687)
* All working * CHangelog tweak * Address review comments
This commit is contained in:
parent
2e70b351a2
commit
05b3bff89f
|
@ -140,6 +140,7 @@ ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidInclude=Invalid {0} param
|
|||
ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
||||
ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
||||
ca.uhn.fhir.jpa.search.builder.QueryStack.sourceParamDisabled=The _source parameter is disabled on this server
|
||||
ca.uhn.fhir.jpa.search.builder.QueryStack.cantSortOnCoordParamWithoutValues=Can not sort on coordinate parameter "{0}" unless this parameter is also specified as a search parameter with a latitude/longitude value
|
||||
ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
|
||||
ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 4650
|
||||
title: "When processing the Location:near Search Parameter, if a distance unit was supplied in the
|
||||
parameter value it was ignored and the distance was always assumed to be km. This has been
|
||||
corrected."
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: add
|
||||
issue: 4650
|
||||
title: "When performing a search using the Location:near search parameter, it is now possible
|
||||
to include this parameter in a `_sort` expression as well. This will result in locations being
|
||||
sorted by their proximity to the coordinates in the parameter value. Thanks to
|
||||
Jens Kristian Villadsen for the suggestion and algorithm design!"
|
|
@ -254,6 +254,14 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
|||
uriTable.dropIndex("20230324.6", "IDX_SP_URI_HASH_URI");
|
||||
uriTable.dropIndex("20230324.7", "IDX_SP_URI_HASH_IDENTITY");
|
||||
}
|
||||
|
||||
version.onTable("HFJ_SPIDX_COORDS")
|
||||
.dropIndex("20230325.1", "IDX_SP_COORDS_HASH");
|
||||
version.onTable("HFJ_SPIDX_COORDS")
|
||||
.addIndex("20230325.2", "IDX_SP_COORDS_HASH_V2")
|
||||
.unique(true)
|
||||
.online(true)
|
||||
.withColumns("HASH_IDENTITY", "SP_LATITUDE", "SP_LONGITUDE", "RES_ID", "PARTITION_ID");
|
||||
}
|
||||
|
||||
protected void init640() {
|
||||
|
|
|
@ -46,6 +46,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.ICanMakeMissingParamPredicate;
|
||||
import ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder;
|
||||
import ca.uhn.fhir.jpa.search.builder.predicate.ParsedLocationParam;
|
||||
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;
|
||||
|
@ -137,6 +138,7 @@ import static org.apache.commons.lang3.StringUtils.split;
|
|||
public class QueryStack {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(QueryStack.class);
|
||||
public static final String LOCATION_POSITION = "Location.position";
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
private final SearchQueryBuilder mySqlBuilder;
|
||||
|
@ -146,6 +148,7 @@ public class QueryStack {
|
|||
private final JpaStorageSettings myStorageSettings;
|
||||
private final EnumSet<PredicateBuilderTypeEnum> myReusePredicateBuilderTypes;
|
||||
private Map<PredicateBuilderCacheKey, BaseJoiningPredicateBuilder> myJoinMap;
|
||||
private Map<String, BaseJoiningPredicateBuilder> myParamNameToPredicateBuilderMap;
|
||||
// used for _offset queries with sort, should be removed once the fix is applied to the async path too.
|
||||
private boolean myUseAggregate;
|
||||
|
||||
|
@ -174,6 +177,32 @@ public class QueryStack {
|
|||
myReusePredicateBuilderTypes = theReusePredicateBuilderTypes;
|
||||
}
|
||||
|
||||
public void addSortOnCoordsNear(String theParamName, boolean theAscending, SearchParameterMap theParams) {
|
||||
boolean handled = false;
|
||||
if (myParamNameToPredicateBuilderMap != null) {
|
||||
BaseJoiningPredicateBuilder builder = myParamNameToPredicateBuilderMap.get(theParamName);
|
||||
if (builder instanceof CoordsPredicateBuilder) {
|
||||
CoordsPredicateBuilder coordsBuilder = (CoordsPredicateBuilder) builder;
|
||||
|
||||
List<List<IQueryParameterType>> params = theParams.get(theParamName);
|
||||
if (params.size() > 0 && params.get(0).size() > 0) {
|
||||
IQueryParameterType param = params.get(0).get(0);
|
||||
ParsedLocationParam location = ParsedLocationParam.from(theParams, param);
|
||||
double latitudeValue = location.getLatitudeValue();
|
||||
double longitudeValue = location.getLongitudeValue();
|
||||
mySqlBuilder.addSortCoordsNear(coordsBuilder, latitudeValue, longitudeValue, theAscending);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
String msg = myFhirContext.getLocalizer().getMessageSanitized(QueryStack.class, "cantSortOnCoordParamWithoutValues", theParamName);
|
||||
throw new InvalidRequestException(Msg.code(2307) + msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSortOnDate(String theResourceName, String theParamName, boolean theAscending) {
|
||||
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||
DatePredicateBuilder datePredicateBuilder = mySqlBuilder.createDatePredicateBuilder();
|
||||
|
@ -302,7 +331,7 @@ public class QueryStack {
|
|||
chainedPredicateBuilder = datePredicateBuilder;
|
||||
break;
|
||||
|
||||
/*
|
||||
/*
|
||||
* Note that many of the options below aren't implemented because they
|
||||
* don't seem useful to me, but they could theoretically be implemented
|
||||
* if someone ever needed them. I'm not sure why you'd want to do a chained
|
||||
|
@ -409,6 +438,14 @@ public class QueryStack {
|
|||
} else {
|
||||
retVal = theFactoryMethod.get();
|
||||
}
|
||||
|
||||
if (theType == PredicateBuilderTypeEnum.COORDS) {
|
||||
if (myParamNameToPredicateBuilderMap == null) {
|
||||
myParamNameToPredicateBuilderMap = new HashMap<>();
|
||||
}
|
||||
myParamNameToPredicateBuilderMap.put(theParamName, retVal);
|
||||
}
|
||||
|
||||
return new PredicateBuilderCacheLookupResult<>(cacheHit, (T) retVal);
|
||||
}
|
||||
|
||||
|
@ -1716,7 +1753,7 @@ public class QueryStack {
|
|||
break;
|
||||
case TOKEN:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
if ("Location.position".equals(nextParamDef.getPath())) {
|
||||
if (LOCATION_POSITION.equals(nextParamDef.getPath())) {
|
||||
andPredicates.add(createPredicateCoords(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, theRequestPartitionId, mySqlBuilder));
|
||||
} else {
|
||||
andPredicates.add(createPredicateToken(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
|
@ -1741,7 +1778,7 @@ public class QueryStack {
|
|||
case HAS:
|
||||
case SPECIAL:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
if ("Location.position".equals(nextParamDef.getPath())) {
|
||||
if (LOCATION_POSITION.equals(nextParamDef.getPath())) {
|
||||
andPredicates.add(createPredicateCoords(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, theRequestPartitionId, mySqlBuilder));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,8 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.search.builder.QueryStack.LOCATION_POSITION;
|
||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -644,7 +646,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
if (sort != null) {
|
||||
assert !theCountOnlyFlag;
|
||||
|
||||
createSort(queryStack3, sort);
|
||||
createSort(queryStack3, sort, theParams);
|
||||
}
|
||||
|
||||
|
||||
|
@ -731,7 +733,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
}
|
||||
|
||||
private void createSort(QueryStack theQueryStack, SortSpec theSort) {
|
||||
private void createSort(QueryStack theQueryStack, SortSpec theSort, SearchParameterMap theParams) {
|
||||
if (theSort == null || isBlank(theSort.getParamName())) {
|
||||
return;
|
||||
}
|
||||
|
@ -864,6 +866,12 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
|
||||
break;
|
||||
case SPECIAL:
|
||||
if (LOCATION_POSITION.equals(param.getPath())) {
|
||||
theQueryStack.addSortOnCoordsNear(paramName, ascending, theParams);
|
||||
break;
|
||||
}
|
||||
throw new InvalidRequestException(Msg.code(2306) + "This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + paramName);
|
||||
|
||||
case HAS:
|
||||
default:
|
||||
throw new InvalidRequestException(Msg.code(1197) + "This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + paramName);
|
||||
|
@ -872,7 +880,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
|
||||
// Recurse
|
||||
createSort(theQueryStack, theSort.getChain());
|
||||
createSort(theQueryStack, theSort.getChain(), theParams);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -27,17 +27,12 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|||
import ca.uhn.fhir.jpa.util.CoordCalculator;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Location;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.SpecialParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
||||
import com.healthmarketscience.sqlbuilder.ComboCondition;
|
||||
import com.healthmarketscience.sqlbuilder.Condition;
|
||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
||||
import org.hibernate.search.engine.spatial.GeoBoundingBox;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
||||
|
||||
private final DbColumn myColumnLatitude;
|
||||
|
@ -53,6 +48,13 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
myColumnLongitude = getTable().addColumn("SP_LONGITUDE");
|
||||
}
|
||||
|
||||
public DbColumn getColumnLatitude() {
|
||||
return myColumnLatitude;
|
||||
}
|
||||
|
||||
public DbColumn getColumnLongitude() {
|
||||
return myColumnLongitude;
|
||||
}
|
||||
|
||||
public Condition createPredicateCoords(SearchParameterMap theParams,
|
||||
IQueryParameterType theParam,
|
||||
|
@ -60,47 +62,11 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
RuntimeSearchParam theSearchParam,
|
||||
CoordsPredicateBuilder theFrom,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
String latitudeValue;
|
||||
String longitudeValue;
|
||||
double distanceKm = 0.0;
|
||||
|
||||
if (theParam instanceof TokenParam) { // DSTU3
|
||||
TokenParam param = (TokenParam) theParam;
|
||||
String value = param.getValue();
|
||||
String[] parts = value.split(":");
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException(Msg.code(1228) + "Invalid position format '" + value + "'. Required format is 'latitude:longitude'");
|
||||
}
|
||||
latitudeValue = parts[0];
|
||||
longitudeValue = parts[1];
|
||||
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
|
||||
throw new IllegalArgumentException(Msg.code(1229) + "Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
|
||||
}
|
||||
QuantityParam distanceParam = theParams.getNearDistanceParam();
|
||||
if (distanceParam != null) {
|
||||
distanceKm = distanceParam.getValue().doubleValue();
|
||||
}
|
||||
} else if (theParam instanceof SpecialParam) { // R4
|
||||
SpecialParam param = (SpecialParam) theParam;
|
||||
String value = param.getValue();
|
||||
String[] parts = value.split("\\|");
|
||||
if (parts.length < 2 || parts.length > 4) {
|
||||
throw new IllegalArgumentException(Msg.code(1230) + "Invalid position format '" + value + "'. Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'");
|
||||
}
|
||||
latitudeValue = parts[0];
|
||||
longitudeValue = parts[1];
|
||||
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
|
||||
throw new IllegalArgumentException(Msg.code(1231) + "Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
String distanceString = parts[2];
|
||||
if (!isBlank(distanceString)) {
|
||||
distanceKm = Double.parseDouble(distanceString);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(Msg.code(1232) + "Invalid position type: " + theParam.getClass());
|
||||
}
|
||||
ParsedLocationParam params = ParsedLocationParam.from(theParams, theParam);
|
||||
double distanceKm = params.getDistanceKm();
|
||||
double latitudeValue = params.getLatitudeValue();
|
||||
double longitudeValue = params.getLongitudeValue();
|
||||
|
||||
Condition latitudePredicate;
|
||||
Condition longitudePredicate;
|
||||
|
@ -112,10 +78,7 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
} else if (distanceKm > CoordCalculator.MAX_SUPPORTED_DISTANCE_KM) {
|
||||
throw new IllegalArgumentException(Msg.code(1234) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be <= " + CoordCalculator.MAX_SUPPORTED_DISTANCE_KM);
|
||||
} else {
|
||||
double latitudeDegrees = Double.parseDouble(latitudeValue);
|
||||
double longitudeDegrees = Double.parseDouble(longitudeValue);
|
||||
|
||||
GeoBoundingBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
|
||||
GeoBoundingBox box = CoordCalculator.getBox(latitudeValue, longitudeValue, distanceKm);
|
||||
latitudePredicate = theFrom.createLatitudePredicateFromBox(box);
|
||||
longitudePredicate = theFrom.createLongitudePredicateFromBox(box);
|
||||
}
|
||||
|
@ -124,11 +87,11 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
}
|
||||
|
||||
|
||||
public Condition createPredicateLatitudeExact(String theLatitudeValue) {
|
||||
public Condition createPredicateLatitudeExact(double theLatitudeValue) {
|
||||
return BinaryCondition.equalTo(myColumnLatitude, generatePlaceholder(theLatitudeValue));
|
||||
}
|
||||
|
||||
public Condition createPredicateLongitudeExact(String theLongitudeValue) {
|
||||
public Condition createPredicateLongitudeExact(double theLongitudeValue) {
|
||||
return BinaryCondition.equalTo(myColumnLongitude, generatePlaceholder(theLongitudeValue));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package ca.uhn.fhir.jpa.search.builder.predicate;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.SpecialParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class ParsedLocationParam {
|
||||
private final double myLatitudeValue;
|
||||
private final double myLongitudeValue;
|
||||
private final double myDistanceKm;
|
||||
|
||||
private ParsedLocationParam(String theLatitudeValue, String theLongitudeValue, double theDistanceKm) {
|
||||
myLatitudeValue = parseLatLonParameter(theLatitudeValue);
|
||||
myLongitudeValue = parseLatLonParameter(theLongitudeValue);
|
||||
myDistanceKm = theDistanceKm;
|
||||
}
|
||||
|
||||
private static double parseLatLonParameter(String theValue) {
|
||||
try {
|
||||
return Double.parseDouble(defaultString(theValue));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidRequestException(Msg.code(2308) + "Invalid lat/lon parameter value: " + UrlUtil.sanitizeUrlPart(theValue));
|
||||
}
|
||||
}
|
||||
|
||||
public double getLatitudeValue() {
|
||||
return myLatitudeValue;
|
||||
}
|
||||
|
||||
public double getLongitudeValue() {
|
||||
return myLongitudeValue;
|
||||
}
|
||||
|
||||
public double getDistanceKm() {
|
||||
return myDistanceKm;
|
||||
}
|
||||
|
||||
public static ParsedLocationParam from(SearchParameterMap theParams, IQueryParameterType theParam) {
|
||||
String latitudeValue;
|
||||
String longitudeValue;
|
||||
double distanceKm = 0.0;
|
||||
|
||||
if (theParam instanceof TokenParam) { // DSTU3
|
||||
TokenParam param = (TokenParam) theParam;
|
||||
String value = param.getValue();
|
||||
String[] parts = value.split(":");
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException(Msg.code(1228) + "Invalid position format '" + value + "'. Required format is 'latitude:longitude'");
|
||||
}
|
||||
latitudeValue = parts[0];
|
||||
longitudeValue = parts[1];
|
||||
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
|
||||
throw new IllegalArgumentException(Msg.code(1229) + "Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
|
||||
}
|
||||
QuantityParam distanceParam = theParams.getNearDistanceParam();
|
||||
if (distanceParam != null) {
|
||||
distanceKm = parseLatLonParameter(distanceParam.getValueAsString());
|
||||
}
|
||||
} else if (theParam instanceof SpecialParam) { // R4
|
||||
SpecialParam param = (SpecialParam) theParam;
|
||||
String value = param.getValue();
|
||||
String[] parts = StringUtils.split(value, '|');
|
||||
if (parts.length < 2 || parts.length > 4) {
|
||||
throw new IllegalArgumentException(Msg.code(1230) + "Invalid position format '" + value + "'. Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'");
|
||||
}
|
||||
latitudeValue = parts[0];
|
||||
longitudeValue = parts[1];
|
||||
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
|
||||
throw new IllegalArgumentException(Msg.code(1231) + "Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
String distanceString = parts[2];
|
||||
if (!isBlank(distanceString)) {
|
||||
distanceKm = parseLatLonParameter(distanceString);
|
||||
}
|
||||
|
||||
if (parts.length >= 4) {
|
||||
String distanceUnits = parts[3];
|
||||
distanceKm = UcumServiceUtil.convert(distanceKm, distanceUnits, "km");
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(Msg.code(1232) + "Invalid position type: " + theParam.getClass());
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new ParsedLocationParam(latitudeValue, longitudeValue, distanceKm);
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
|
|||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
||||
import com.healthmarketscience.sqlbuilder.ComboCondition;
|
||||
import com.healthmarketscience.sqlbuilder.ComboExpression;
|
||||
import com.healthmarketscience.sqlbuilder.Condition;
|
||||
import com.healthmarketscience.sqlbuilder.FunctionCall;
|
||||
import com.healthmarketscience.sqlbuilder.InCondition;
|
||||
|
@ -108,6 +109,7 @@ public class SearchQueryBuilder {
|
|||
private boolean dialectIsMsSql;
|
||||
private boolean dialectIsMySql;
|
||||
private boolean myNeedResourceTableRoot;
|
||||
private int myNextNearnessColumnId = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -722,6 +724,30 @@ public class SearchQueryBuilder {
|
|||
return myHaveAtLeastOnePredicate;
|
||||
}
|
||||
|
||||
public void addSortCoordsNear(CoordsPredicateBuilder theCoordsBuilder, double theLatitudeValue, double theLongitudeValue, boolean theAscending) {
|
||||
FunctionCall absLatitude = new FunctionCall("ABS");
|
||||
String latitudePlaceholder = generatePlaceholder(theLatitudeValue);
|
||||
ComboExpression absLatitudeMiddle = new ComboExpression(ComboExpression.Op.SUBTRACT, theCoordsBuilder.getColumnLatitude(), latitudePlaceholder);
|
||||
absLatitude = absLatitude.addCustomParams(absLatitudeMiddle);
|
||||
|
||||
FunctionCall absLongitude = new FunctionCall("ABS");
|
||||
String longitudePlaceholder = generatePlaceholder(theLongitudeValue);
|
||||
ComboExpression absLongitudeMiddle = new ComboExpression(ComboExpression.Op.SUBTRACT, theCoordsBuilder.getColumnLongitude(), longitudePlaceholder);
|
||||
absLongitude = absLongitude.addCustomParams(absLongitudeMiddle);
|
||||
|
||||
ComboExpression sum = new ComboExpression(ComboExpression.Op.ADD, absLatitude, absLongitude);
|
||||
String ordering;
|
||||
if (theAscending) {
|
||||
ordering = "";
|
||||
} else {
|
||||
ordering = " DESC";
|
||||
}
|
||||
|
||||
String columnName = "MHD" + (myNextNearnessColumnId++);
|
||||
mySelect.addAliasedColumn(sum, columnName);
|
||||
mySelect.addCustomOrderings(columnName + ordering);
|
||||
}
|
||||
|
||||
public void addSortString(DbColumn theColumnValueNormalized, boolean theAscending) {
|
||||
addSortString(theColumnValueNormalized, theAscending, false);
|
||||
}
|
||||
|
@ -787,26 +813,26 @@ public class SearchQueryBuilder {
|
|||
return sortColumnName;
|
||||
}
|
||||
|
||||
public void addSortNumeric(DbColumn theTheColumnValueNormalized, boolean theTheAscending, OrderObject.NullOrder theNullOrder, boolean theUseAggregate) {
|
||||
public void addSortNumeric(DbColumn theTheColumnValueNormalized, boolean theAscending, OrderObject.NullOrder theNullOrder, boolean theUseAggregate) {
|
||||
if ((dialectIsMySql || dialectIsMsSql)) {
|
||||
// MariaDB, MySQL and MSSQL do not support "NULLS FIRST" and "NULLS LAST" syntax.
|
||||
// Null values are always treated as less than non-null values.
|
||||
// As such special handling is required here.
|
||||
String direction;
|
||||
String sortColumnName = theTheColumnValueNormalized.getTable().getAlias() + "." + theTheColumnValueNormalized.getName();
|
||||
if ((theTheAscending && theNullOrder == OrderObject.NullOrder.LAST)
|
||||
|| (!theTheAscending && theNullOrder == OrderObject.NullOrder.FIRST)) {
|
||||
if ((theAscending && theNullOrder == OrderObject.NullOrder.LAST)
|
||||
|| (!theAscending && theNullOrder == OrderObject.NullOrder.FIRST)) {
|
||||
// Negating the numeric column value and reversing the sort order will ensure that the rows appear
|
||||
// in the correct order with nulls appearing first or last as needed.
|
||||
direction = theTheAscending ? " DESC" : " ASC";
|
||||
direction = theAscending ? " DESC" : " ASC";
|
||||
sortColumnName = "-" + sortColumnName;
|
||||
} else {
|
||||
direction = theTheAscending ? " ASC" : " DESC";
|
||||
direction = theAscending ? " ASC" : " DESC";
|
||||
}
|
||||
sortColumnName = formatColumnNameForAggregate(theTheAscending, theUseAggregate, sortColumnName);
|
||||
sortColumnName = formatColumnNameForAggregate(theAscending, theUseAggregate, sortColumnName);
|
||||
mySelect.addCustomOrderings(sortColumnName + direction);
|
||||
} else {
|
||||
addSort(theTheColumnValueNormalized, theTheAscending, theNullOrder, theUseAggregate);
|
||||
addSort(theTheColumnValueNormalized, theAscending, theNullOrder, theUseAggregate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import javax.persistence.Table;
|
|||
@Embeddable
|
||||
@Entity
|
||||
@Table(name = "HFJ_SPIDX_COORDS", indexes = {
|
||||
@Index(name = "IDX_SP_COORDS_HASH", columnList = "HASH_IDENTITY,SP_LATITUDE,SP_LONGITUDE"),
|
||||
@Index(name = "IDX_SP_COORDS_HASH_V2", columnList = "HASH_IDENTITY,SP_LATITUDE,SP_LONGITUDE,RES_ID,PARTITION_ID"),
|
||||
@Index(name = "IDX_SP_COORDS_UPDATED", columnList = "SP_UPDATED"),
|
||||
@Index(name = "IDX_SP_COORDS_RESID", columnList = "RES_ID")
|
||||
})
|
||||
|
|
|
@ -24,7 +24,9 @@ import java.math.BigDecimal;
|
|||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.fhir.ucum.Decimal;
|
||||
import org.fhir.ucum.Pair;
|
||||
import org.fhir.ucum.UcumEssenceService;
|
||||
|
@ -172,4 +174,16 @@ public class UcumServiceUtil {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static double convert(double theDistanceKm, String theSourceUnits, String theTargetUnits) {
|
||||
init();
|
||||
try {
|
||||
Decimal distance = new Decimal(Double.toString(theDistanceKm));
|
||||
Decimal output = myUcumEssenceService.convert(distance, theSourceUnits, theTargetUnits);
|
||||
String decimal = output.asDecimal();
|
||||
return Double.parseDouble(decimal);
|
||||
} catch (UcumException e) {
|
||||
throw new InvalidRequestException(Msg.code(2309) + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,28 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
||||
import ca.uhn.fhir.jpa.util.CoordCalculatorTestUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.Location;
|
||||
import org.hl7.fhir.r4.model.PractitionerRole;
|
||||
import org.hl7.fhir.r4.model.SearchParameter;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
||||
|
||||
|
@ -38,8 +51,6 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
|
@ -56,8 +67,6 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
@ -85,8 +94,6 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
|
@ -102,12 +109,12 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude);
|
||||
loc.setPosition(position);
|
||||
myCaptureQueriesListener.clear();
|
||||
IIdType locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless();
|
||||
IIdType locId = myLocationDao.create(loc, mySrd).getId().toUnqualifiedVersionless();
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
|
||||
PractitionerRole pr = new PractitionerRole();
|
||||
pr.addLocation().setReference(locId.getValue());
|
||||
IIdType prId = myPractitionerRoleDao.create(pr).getId().toUnqualifiedVersionless();
|
||||
IIdType prId = myPractitionerRoleDao.create(pr, mySrd).getId().toUnqualifiedVersionless();
|
||||
{ // In the box
|
||||
double bigEnoughDistance = CoordCalculatorTestUtil.DISTANCE_KM_CHIN_TO_UHN * 2;
|
||||
String url = "PractitionerRole?location." +
|
||||
|
@ -118,8 +125,6 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
@ -138,8 +143,6 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
@ -147,4 +150,209 @@ public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test {
|
|||
assertEquals(0, actual.getEntry().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNearSearchDistanceNotInKm() {
|
||||
createFourCityLocations();
|
||||
|
||||
String url = "Location?" +
|
||||
Location.SP_NEAR +
|
||||
"=" +
|
||||
CoordCalculatorTestUtil.LATITUDE_CHIN +
|
||||
"|" +
|
||||
CoordCalculatorTestUtil.LONGITUDE_CHIN +
|
||||
"|" +
|
||||
"300000" +
|
||||
"|" +
|
||||
"m";
|
||||
|
||||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(actual);
|
||||
assertThat(ids.toString(), ids, contains(
|
||||
"Location/toronto",
|
||||
"Location/belleville",
|
||||
"Location/kingston"
|
||||
));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
public void testSortNear(boolean theAscending) {
|
||||
createFourCityLocations();
|
||||
|
||||
String url = "Location?" +
|
||||
Location.SP_NEAR +
|
||||
"=" +
|
||||
CoordCalculatorTestUtil.LATITUDE_CHIN +
|
||||
"|" +
|
||||
CoordCalculatorTestUtil.LONGITUDE_CHIN +
|
||||
"|" +
|
||||
"300" +
|
||||
"|" +
|
||||
"km" +
|
||||
"&_sort=" +
|
||||
(theAscending ? "" : "-") +
|
||||
Location.SP_NEAR;
|
||||
|
||||
logAllCoordsIndexes();
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(actual);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
if (theAscending) {
|
||||
assertThat(ids.toString(), ids, contains(
|
||||
"Location/toronto",
|
||||
"Location/belleville",
|
||||
"Location/kingston"
|
||||
));
|
||||
} else {
|
||||
assertThat(ids.toString(), ids, contains(
|
||||
"Location/kingston",
|
||||
"Location/belleville",
|
||||
"Location/toronto"
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is kind of a contrived test where we create a second search parameter that
|
||||
* also has the {@literal Location.position} path, so that we can make sure we don't crash with
|
||||
* two nearness search parameters in the sort expression
|
||||
*/
|
||||
@Test
|
||||
public void testSortNearWithTwoParameters() {
|
||||
SearchParameter sp = new SearchParameter();
|
||||
sp.setCode("near2");
|
||||
sp.setName("near2");
|
||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
sp.setExpression(QueryStack.LOCATION_POSITION);
|
||||
sp.setType(Enumerations.SearchParamType.SPECIAL);
|
||||
sp.addBase("Location");
|
||||
mySearchParameterDao.create(sp, mySrd);
|
||||
mySearchParamRegistry.forceRefresh();
|
||||
|
||||
createFourCityLocations();
|
||||
|
||||
String url = "Location?" +
|
||||
"near2=" +
|
||||
CoordCalculatorTestUtil.LATITUDE_CHIN + "|" +
|
||||
CoordCalculatorTestUtil.LONGITUDE_CHIN + "|" +
|
||||
"300" + "|" + "km" +
|
||||
"&near=" +
|
||||
CoordCalculatorTestUtil.LATITUDE_CHIN + "|" +
|
||||
CoordCalculatorTestUtil.LONGITUDE_CHIN + "|" +
|
||||
"300" + "|" + "km" +
|
||||
"&_sort=near2,near";
|
||||
|
||||
logAllCoordsIndexes();
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle actual = myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(actual);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertThat(ids.toString(), ids, contains(
|
||||
"Location/toronto",
|
||||
"Location/belleville",
|
||||
"Location/kingston"
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortNearWithNoNearParameter() {
|
||||
String url = "Location?_sort=near";
|
||||
try {
|
||||
myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString("Can not sort on coordinate parameter \"near\" unless this parameter is also specified as a search parameter"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void createFourCityLocations() {
|
||||
createLocation("Location/toronto", CoordCalculatorTestUtil.LATITUDE_TORONTO, CoordCalculatorTestUtil.LONGITUDE_TORONTO);
|
||||
createLocation("Location/belleville", CoordCalculatorTestUtil.LATITUDE_BELLEVILLE, CoordCalculatorTestUtil.LONGITUDE_BELLEVILLE);
|
||||
createLocation("Location/kingston", CoordCalculatorTestUtil.LATITUDE_KINGSTON, CoordCalculatorTestUtil.LONGITUDE_KINGSTON);
|
||||
createLocation("Location/ottawa", CoordCalculatorTestUtil.LATITUDE_OTTAWA, CoordCalculatorTestUtil.LONGITUDE_OTTAWA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalid_SortWithNoParameter() {
|
||||
|
||||
String url = "Location?" +
|
||||
"_sort=" +
|
||||
Location.SP_NEAR;
|
||||
|
||||
try {
|
||||
myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString("Can not sort on coordinate parameter \"near\" unless this parameter is also specified"));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"foo, -79.4170007, 300, km, Invalid lat/lon parameter value: foo",
|
||||
"43.65513, foo, 300, km, Invalid lat/lon parameter value: foo",
|
||||
"43.65513, -79.4170007, foo, km, Invalid lat/lon parameter value: foo",
|
||||
"43.65513, -79.4170007, 300, foo, The unit 'foo' is unknown"
|
||||
})
|
||||
public void testInvalid_InvalidLatitude(String theLatitude, String theLongitude, String theDistance, String theDistanceUnits, String theExpectedErrorMessageContains) {
|
||||
|
||||
String url = "Location?" +
|
||||
Location.SP_NEAR +
|
||||
"=" +
|
||||
theLatitude +
|
||||
"|" +
|
||||
theLongitude +
|
||||
"|" +
|
||||
theDistance +
|
||||
"|" +
|
||||
theDistanceUnits;
|
||||
|
||||
try {
|
||||
myClient
|
||||
.search()
|
||||
.byUrl(myServerBase + "/" + url)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString(theExpectedErrorMessageContains));
|
||||
}
|
||||
}
|
||||
|
||||
private void createLocation(String id, double latitude, double longitude) {
|
||||
Location loc = new Location();
|
||||
loc.setId(id);
|
||||
loc.getPosition()
|
||||
.setLatitude(latitude)
|
||||
.setLongitude(longitude);
|
||||
myLocationDao.update(loc, mySrd);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport;
|
|||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao;
|
||||
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.IResourceIndexedSearchParamStringDao;
|
||||
|
@ -60,6 +61,13 @@ import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
|
|||
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
|
||||
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.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.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
||||
|
@ -211,6 +219,8 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
@Autowired
|
||||
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
|
||||
@Autowired
|
||||
protected IResourceIndexedSearchParamCoordsDao myResourceIndexedSearchParamCoordsDao;
|
||||
@Autowired
|
||||
protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao;
|
||||
@Autowired(required = false)
|
||||
protected IFulltextSearchSvc myFulltestSearchSvc;
|
||||
|
@ -351,14 +361,14 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
|
||||
protected void logAllResourceLinks() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Resource Links:\n * {}", myResourceLinkDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Resource Links:\n * {}", myResourceLinkDao.findAll().stream().map(ResourceLink::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
protected int logAllResources() {
|
||||
return runInTransaction(() -> {
|
||||
List<ResourceTable> resources = myResourceTableDao.findAll();
|
||||
ourLog.info("Resources:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Resources:\n * {}", resources.stream().map(ResourceTable::toString).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
@ -366,7 +376,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected int logAllConceptDesignations() {
|
||||
return runInTransaction(() -> {
|
||||
List<TermConceptDesignation> resources = myTermConceptDesignationDao.findAll();
|
||||
ourLog.info("Concept Designations:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Concept Designations:\n * {}", resources.stream().map(TermConceptDesignation::toString).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
@ -374,7 +384,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected int logAllConceptProperties() {
|
||||
return runInTransaction(() -> {
|
||||
List<TermConceptProperty> resources = myTermConceptPropertyDao.findAll();
|
||||
ourLog.info("Concept Designations:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Concept Designations:\n * {}", resources.stream().map(TermConceptProperty::toString).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
@ -382,7 +392,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected int logAllConcepts() {
|
||||
return runInTransaction(() -> {
|
||||
List<TermConcept> resources = myTermConceptDao.findAll();
|
||||
ourLog.info("Concepts:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Concepts:\n * {}", resources.stream().map(TermConcept::toString).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
@ -390,7 +400,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected int logAllValueSetConcepts() {
|
||||
return runInTransaction(() -> {
|
||||
List<TermValueSetConcept> resources = myTermValueSetConceptDao.findAll();
|
||||
ourLog.info("Concepts:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Concepts:\n * {}", resources.stream().map(TermValueSetConcept::toString).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
@ -398,7 +408,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected int logAllValueSets() {
|
||||
return runInTransaction(() -> {
|
||||
List<TermValueSet> valueSets = myTermValueSetDao.findAll();
|
||||
ourLog.info("ValueSets:\n * {}", valueSets.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("ValueSets:\n * {}", valueSets.stream().map(TermValueSet::toString).collect(Collectors.joining("\n * ")));
|
||||
return valueSets.size();
|
||||
});
|
||||
}
|
||||
|
@ -406,38 +416,44 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected int logAllForcedIds() {
|
||||
return runInTransaction(() -> {
|
||||
List<ForcedId> forcedIds = myForcedIdDao.findAll();
|
||||
ourLog.info("Resources:\n * {}", forcedIds.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Resources:\n * {}", forcedIds.stream().map(ForcedId::toString).collect(Collectors.joining("\n * ")));
|
||||
return forcedIds.size();
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllDateIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllNonUniqueIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Non unique indexes:\n * {}", myResourceIndexedComboTokensNonUniqueDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Non unique indexes:\n * {}", myResourceIndexedComboTokensNonUniqueDao.findAll().stream().map(ResourceIndexedComboTokenNonUnique::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllTokenIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(ResourceIndexedSearchParamToken::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllCoordsIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Coords indexes:\n * {}", myResourceIndexedSearchParamCoordsDao.findAll().stream().map(ResourceIndexedSearchParamCoords::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllNumberIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Number indexes:\n * {}", myResourceIndexedSearchParamNumberDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("Number indexes:\n * {}", myResourceIndexedSearchParamNumberDao.findAll().stream().map(ResourceIndexedSearchParamNumber::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllUriIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("URI indexes:\n * {}", myResourceIndexedSearchParamUriDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
ourLog.info("URI indexes:\n * {}", myResourceIndexedSearchParamUriDao.findAll().stream().map(ResourceIndexedSearchParamUri::toString).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,14 @@ public final class CoordCalculatorTestUtil {
|
|||
public static final double LONGITIDE_TAVEUNI = 179.889793;
|
||||
// enough distance from point to cross anti-meridian
|
||||
public static final double DISTANCE_TAVEUNI = 100.0;
|
||||
public static final double LATITUDE_TORONTO = 43.741667;
|
||||
public static final double LONGITUDE_TORONTO = -79.373333;
|
||||
public static final double LATITUDE_BELLEVILLE = 44.166667;
|
||||
public static final double LONGITUDE_BELLEVILLE = -77.383333;
|
||||
public static final double LATITUDE_KINGSTON = 44.234722;
|
||||
public static final double LONGITUDE_KINGSTON = -76.510833;
|
||||
public static final double LATITUDE_OTTAWA = 45.424722;
|
||||
public static final double LONGITUDE_OTTAWA = -75.695;
|
||||
|
||||
private CoordCalculatorTestUtil() {}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue