Add partition_id to search joins (#6325)

* Failing test

* cleanup

* revert partition in group by clause

* test update for new joins

* Give up on nullable join

:not joins require sub-selects
and the nullability can't
support an in(select ) clause.

Bring in flag, and set based on partition settings

* spotless

* changelog

* cleanup

* cleanup

* fixmes

* partitioned sql test

* get sort tested

* revert tests expecting nullable partition joins

* Fix result set parsing when coord-sort is present.

* Cleanup sql test

* Change to default select partition_id

Add sort test cases.

* Fix everything sql

* Fix everything sql

* Fix group-by on offset path

* More sql test fixups

* More sql test fixups

* Test case for cache key

* review feedback

* Revert to HFJ_RES_VER_PROV until we get the migration

* fix for everything from james

* fixme

* Revert column name

* Cleanup

* Test fix

---------

Co-authored-by: James Agnew <jamesagnew@gmail.com>
This commit is contained in:
Michael Buckley 2024-10-16 15:19:20 -04:00 committed by GitHub
parent 3acc95d45b
commit d4227baf2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 674 additions and 216 deletions

View File

@ -0,0 +1,5 @@
---
type: add
issue: 6325
title: "A new configuration option, `PartitionSettings#setPartitionIdsInPrimaryKeys(boolean)` configures the query engine
to include the partitioning column in search query joins."

View File

@ -55,6 +55,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TagPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.UriPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.sql.ColumnTupleObject;
import ca.uhn.fhir.jpa.search.builder.sql.PredicateBuilderFactory;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@ -123,6 +124,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.SearchForIdsParams.with;
import static ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder.getResourceIdColumn;
import static ca.uhn.fhir.jpa.util.QueryParameterUtils.fromOperation;
import static ca.uhn.fhir.jpa.util.QueryParameterUtils.getChainedPart;
import static ca.uhn.fhir.jpa.util.QueryParameterUtils.getParamNameWithPrefix;
@ -153,6 +155,7 @@ public class QueryStack {
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;
private boolean myGroupingAdded;
/**
* Constructor
@ -245,7 +248,7 @@ public class QueryStack {
resourceTablePredicateBuilder = (ResourceTablePredicateBuilder) firstPredicateBuilder;
} else {
resourceTablePredicateBuilder =
mySqlBuilder.addResourceTablePredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
mySqlBuilder.addResourceTablePredicateBuilder(firstPredicateBuilder.getJoinColumns());
}
mySqlBuilder.addSortDate(resourceTablePredicateBuilder.getColumnLastUpdated(), theAscending, myUseAggregate);
}
@ -282,7 +285,7 @@ public class QueryStack {
resourceTablePredicateBuilder = (ResourceTablePredicateBuilder) firstPredicateBuilder;
} else {
resourceTablePredicateBuilder =
mySqlBuilder.addResourceTablePredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
mySqlBuilder.addResourceTablePredicateBuilder(firstPredicateBuilder.getJoinColumns());
}
mySqlBuilder.addSortString(resourceTablePredicateBuilder.getColumnFhirId(), theAscending, myUseAggregate);
}
@ -358,7 +361,7 @@ public class QueryStack {
case STRING:
StringPredicateBuilder stringPredicateBuilder = mySqlBuilder.createStringPredicateBuilder();
addSortCustomJoin(
resourceLinkPredicateBuilder.getColumnTargetResourceId(),
resourceLinkPredicateBuilder.getJoinColumnsForTarget(),
stringPredicateBuilder,
stringPredicateBuilder.createHashIdentityPredicate(targetType, theChain));
@ -369,7 +372,7 @@ public class QueryStack {
case TOKEN:
TokenPredicateBuilder tokenPredicateBuilder = mySqlBuilder.createTokenPredicateBuilder();
addSortCustomJoin(
resourceLinkPredicateBuilder.getColumnTargetResourceId(),
resourceLinkPredicateBuilder.getJoinColumnsForTarget(),
tokenPredicateBuilder,
tokenPredicateBuilder.createHashIdentityPredicate(targetType, theChain));
@ -380,7 +383,7 @@ public class QueryStack {
case DATE:
DatePredicateBuilder datePredicateBuilder = mySqlBuilder.createDatePredicateBuilder();
addSortCustomJoin(
resourceLinkPredicateBuilder.getColumnTargetResourceId(),
resourceLinkPredicateBuilder.getJoinColumnsForTarget(),
datePredicateBuilder,
datePredicateBuilder.createHashIdentityPredicate(targetType, theChain));
@ -405,7 +408,7 @@ public class QueryStack {
double latitudeValue = location.getLatitudeValue();
double longitudeValue = location.getLongitudeValue();
final CoordsPredicateBuilder coordsPredicateBuilder = mySqlBuilder.addCoordsPredicateBuilder(
resourceLinkPredicateBuilder.getColumnTargetResourceId());
resourceLinkPredicateBuilder.getJoinColumnsForTarget());
mySqlBuilder.addSortCoordsNear(
coordsPredicateBuilder, latitudeValue, longitudeValue, theAscending);
} else {
@ -418,6 +421,7 @@ public class QueryStack {
return;
}
}
//noinspection fallthrough
case NUMBER:
case REFERENCE:
case COMPOSITE:
@ -473,16 +477,16 @@ public class QueryStack {
BaseJoiningPredicateBuilder theFromJoiningPredicateBuilder,
BaseJoiningPredicateBuilder theToJoiningPredicateBuilder,
Condition theCondition) {
addSortCustomJoin(
theFromJoiningPredicateBuilder.getResourceIdColumn(), theToJoiningPredicateBuilder, theCondition);
addSortCustomJoin(theFromJoiningPredicateBuilder.getJoinColumns(), theToJoiningPredicateBuilder, theCondition);
}
private void addSortCustomJoin(
DbColumn theFromDbColumn,
DbColumn theFromDbColumn[],
BaseJoiningPredicateBuilder theToJoiningPredicateBuilder,
Condition theCondition) {
ComboCondition onCondition =
mySqlBuilder.createOnCondition(theFromDbColumn, theToJoiningPredicateBuilder.getResourceIdColumn());
mySqlBuilder.createOnCondition(theFromDbColumn, theToJoiningPredicateBuilder.getJoinColumns());
if (theCondition != null) {
onCondition.addCondition(theCondition);
@ -490,7 +494,7 @@ public class QueryStack {
mySqlBuilder.addCustomJoin(
SelectQuery.JoinType.LEFT_OUTER,
theFromDbColumn.getTable(),
theFromDbColumn[0].getTable(),
theToJoiningPredicateBuilder.getTable(),
onCondition);
}
@ -502,7 +506,7 @@ public class QueryStack {
@SuppressWarnings("unchecked")
private <T extends BaseJoiningPredicateBuilder> PredicateBuilderCacheLookupResult<T> createOrReusePredicateBuilder(
PredicateBuilderTypeEnum theType,
DbColumn theSourceJoinColumn,
DbColumn[] theSourceJoinColumn,
String theParamName,
Supplier<T> theFactoryMethod) {
boolean cacheHit = false;
@ -534,7 +538,7 @@ public class QueryStack {
}
private Condition createPredicateComposite(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theParamDef,
@ -551,7 +555,7 @@ public class QueryStack {
}
private Condition createPredicateComposite(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theParamDef,
@ -605,7 +609,7 @@ public class QueryStack {
}
private Condition createPredicateCompositePart(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theParam,
@ -813,7 +817,7 @@ public class QueryStack {
}
public Condition createPredicateCoords(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -858,7 +862,7 @@ public class QueryStack {
}
public Condition createPredicateDate(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -877,7 +881,7 @@ public class QueryStack {
}
public Condition createPredicateDate(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -1105,7 +1109,7 @@ public class QueryStack {
}
private Condition createPredicateHas(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceType,
List<List<IQueryParameterType>> theHasParameters,
RequestDetails theRequest,
@ -1225,7 +1229,7 @@ public class QueryStack {
resourceLinkTableJoin.getColumnSourcePath(), mySqlBuilder.generatePlaceholders(paths));
Condition linkedPredicate =
searchForIdsWithAndOr(with().setSourceJoinColumn(resourceLinkTableJoin.getColumnSrcResourceId())
searchForIdsWithAndOr(with().setSourceJoinColumn(resourceLinkTableJoin.getJoinColumnsForSource())
.setResourceName(targetResourceType)
.setParamName(parameterName)
.setAndOrParams(Collections.singletonList(orValues))
@ -1239,7 +1243,7 @@ public class QueryStack {
}
public Condition createPredicateNumber(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -1258,7 +1262,7 @@ public class QueryStack {
}
public Condition createPredicateNumber(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -1318,7 +1322,7 @@ public class QueryStack {
}
public Condition createPredicateQuantity(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -1337,7 +1341,7 @@ public class QueryStack {
}
public Condition createPredicateQuantity(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -1405,7 +1409,7 @@ public class QueryStack {
}
public Condition createPredicateReference(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theParamName,
List<String> theQualifiers,
@ -1426,7 +1430,7 @@ public class QueryStack {
}
public Condition createPredicateReference(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theParamName,
List<String> theQualifiers,
@ -1473,17 +1477,33 @@ public class QueryStack {
}
public void addGrouping() {
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
mySqlBuilder.getSelect().addGroupings(firstPredicateBuilder.getResourceIdColumn());
if (!myGroupingAdded) {
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
/*
* Postgres and Oracle don't like it if we are doing a SELECT DISTINCT
* with multiple selected columns but no GROUP BY clause.
*/
if (mySqlBuilder.isSelectPartitionId()) {
mySqlBuilder
.getSelect()
.addGroupings(
firstPredicateBuilder.getPartitionIdColumn(),
firstPredicateBuilder.getResourceIdColumn());
} else {
mySqlBuilder.getSelect().addGroupings(firstPredicateBuilder.getJoinColumns());
}
myGroupingAdded = true;
}
}
public void addOrdering() {
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
mySqlBuilder.getSelect().addOrderings(firstPredicateBuilder.getResourceIdColumn());
mySqlBuilder.getSelect().addOrderings(firstPredicateBuilder.getJoinColumns());
}
public Condition createPredicateReferenceForEmbeddedChainedSearchResource(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
@ -1527,12 +1547,12 @@ public class QueryStack {
for (LeafNodeDefinition leafNodeDefinition : referenceLinks.get(nextReferenceLink)) {
SearchQueryBuilder builder;
if (wantChainedAndNormal) {
builder = mySqlBuilder.newChildSqlBuilder();
builder = mySqlBuilder.newChildSqlBuilder(mySqlBuilder.isIncludePartitionIdInJoins());
} else {
builder = mySqlBuilder;
}
DbColumn previousJoinColumn = null;
DbColumn[] previousJoinColumn = null;
// Create a reference link predicates to the subselect for every link but the last one
for (String nextLink : nextReferenceLink) {
@ -1543,7 +1563,7 @@ public class QueryStack {
builder.addReferencePredicateBuilder(this, previousJoinColumn);
builder.addPredicate(
resourceLinkPredicateBuilder.createPredicateSourcePaths(Lists.newArrayList(nextLink)));
previousJoinColumn = resourceLinkPredicateBuilder.getColumnTargetResourceId();
previousJoinColumn = resourceLinkPredicateBuilder.getJoinColumnsForTarget();
}
Condition containedCondition = createIndexPredicate(
@ -1572,8 +1592,15 @@ public class QueryStack {
if (wantChainedAndNormal) {
if (theSourceJoinColumn == null) {
retVal = new InCondition(
mySqlBuilder.getOrCreateFirstPredicateBuilder(false).getResourceIdColumn(), union);
BaseJoiningPredicateBuilder root = mySqlBuilder.getOrCreateFirstPredicateBuilder(false);
DbColumn[] joinColumns = root.getJoinColumns();
Object joinColumnObject;
if (joinColumns.length == 1) {
joinColumnObject = joinColumns[0];
} else {
joinColumnObject = ColumnTupleObject.from(joinColumns);
}
retVal = new InCondition(joinColumnObject, union);
} else {
// -- for the resource link, need join with target_resource_id
retVal = new InCondition(theSourceJoinColumn, union);
@ -1769,7 +1796,7 @@ public class QueryStack {
}
private Condition createIndexPredicate(
DbColumn theSourceJoinColumn,
DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
String theParamName,
@ -1883,7 +1910,7 @@ public class QueryStack {
@Nullable
public Condition createPredicateResourceId(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
List<List<IQueryParameterType>> theValues,
String theResourceName,
SearchFilterParser.CompareOperation theOperation,
@ -1894,7 +1921,7 @@ public class QueryStack {
}
private Condition createPredicateSourceForAndList(
@Nullable DbColumn theSourceJoinColumn, List<List<IQueryParameterType>> theAndOrParams) {
@Nullable DbColumn[] theSourceJoinColumn, List<List<IQueryParameterType>> theAndOrParams) {
mySqlBuilder.getOrCreateFirstPredicateBuilder();
List<Condition> andPredicates = new ArrayList<>(theAndOrParams.size());
@ -1905,7 +1932,7 @@ public class QueryStack {
}
private Condition createPredicateSource(
@Nullable DbColumn theSourceJoinColumn, List<? extends IQueryParameterType> theList) {
@Nullable DbColumn[] theSourceJoinColumn, List<? extends IQueryParameterType> theList) {
if (myStorageSettings.getStoreMetaSourceInformation()
== JpaStorageSettings.StoreMetaSourceInformationEnum.NONE) {
String msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "sourceParamDisabled");
@ -1948,7 +1975,7 @@ public class QueryStack {
}
private SourcePredicateBuilder getSourcePredicateBuilder(
@Nullable DbColumn theSourceJoinColumn, SelectQuery.JoinType theJoinType) {
@Nullable DbColumn[] theSourceJoinColumn, SelectQuery.JoinType theJoinType) {
return createOrReusePredicateBuilder(
PredicateBuilderTypeEnum.SOURCE,
theSourceJoinColumn,
@ -1958,7 +1985,7 @@ public class QueryStack {
}
public Condition createPredicateString(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -1977,7 +2004,7 @@ public class QueryStack {
}
public Condition createPredicateString(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -2017,7 +2044,7 @@ public class QueryStack {
}
public Condition createPredicateTag(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
List<List<IQueryParameterType>> theList,
String theParamName,
RequestPartitionId theRequestPartitionId) {
@ -2048,7 +2075,8 @@ public class QueryStack {
BaseJoiningPredicateBuilder join;
if (paramInverted) {
SearchQueryBuilder sqlBuilder = mySqlBuilder.newChildSqlBuilder();
boolean selectPartitionId = myPartitionSettings.isPartitionIdsInPrimaryKeys();
SearchQueryBuilder sqlBuilder = mySqlBuilder.newChildSqlBuilder(selectPartitionId);
TagPredicateBuilder tagSelector = sqlBuilder.addTagPredicateBuilder(null);
sqlBuilder.addPredicate(
tagSelector.createPredicateTag(tagType, tokens, theParamName, theRequestPartitionId));
@ -2056,7 +2084,14 @@ public class QueryStack {
join = mySqlBuilder.getOrCreateFirstPredicateBuilder();
Expression subSelect = new Subquery(sql);
tagPredicate = new InCondition(join.getResourceIdColumn(), subSelect).setNegate(true);
Object left;
if (selectPartitionId) {
left = new ColumnTupleObject(join.getJoinColumns());
} else {
left = join.getResourceIdColumn();
}
tagPredicate = new InCondition(left, subSelect).setNegate(true);
} else {
// Tag table can't be a query root because it will include deleted resources, and can't select by
@ -2129,7 +2164,7 @@ public class QueryStack {
}
public Condition createPredicateToken(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -2148,7 +2183,7 @@ public class QueryStack {
}
public Condition createPredicateToken(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -2219,7 +2254,8 @@ public class QueryStack {
BaseJoiningPredicateBuilder join;
if (paramInverted) {
SearchQueryBuilder sqlBuilder = theSqlBuilder.newChildSqlBuilder();
boolean selectPartitionId = myPartitionSettings.isPartitionIdsInPrimaryKeys();
SearchQueryBuilder sqlBuilder = theSqlBuilder.newChildSqlBuilder(selectPartitionId);
TokenPredicateBuilder tokenSelector = sqlBuilder.addTokenPredicateBuilder(null);
sqlBuilder.addPredicate(tokenSelector.createPredicateToken(
tokens, theResourceName, theSpnamePrefix, theSearchParam, theRequestPartitionId));
@ -2228,13 +2264,16 @@ public class QueryStack {
join = theSqlBuilder.getOrCreateFirstPredicateBuilder();
DbColumn[] leftColumns;
if (theSourceJoinColumn == null) {
predicate = new InCondition(join.getResourceIdColumn(), subSelect).setNegate(true);
leftColumns = join.getJoinColumns();
} else {
// -- for the resource link, need join with target_resource_id
predicate = new InCondition(theSourceJoinColumn, subSelect).setNegate(true);
leftColumns = theSourceJoinColumn;
}
Object left = new ColumnTupleObject(leftColumns);
predicate = new InCondition(left, subSelect).setNegate(true);
} else {
Boolean isMissing = theList.get(0).getMissing();
if (isMissing != null) {
@ -2264,7 +2303,7 @@ public class QueryStack {
}
public Condition createPredicateUri(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -2285,7 +2324,7 @@ public class QueryStack {
}
public Condition createPredicateUri(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
@ -2400,9 +2439,9 @@ public class QueryStack {
* Raw match on RES_ID
*/
private Condition createPredicateResourcePID(
DbColumn theSourceJoinColumn, List<List<IQueryParameterType>> theAndOrParams) {
DbColumn[] theSourceJoinColumn, List<List<IQueryParameterType>> theAndOrParams) {
DbColumn pidColumn = theSourceJoinColumn;
DbColumn pidColumn = getResourceIdColumn(theSourceJoinColumn);
if (pidColumn == null) {
BaseJoiningPredicateBuilder predicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
@ -2427,7 +2466,7 @@ public class QueryStack {
}
private Condition createReverseSearchPredicateLastUpdated(
List<List<IQueryParameterType>> theAndOrParams, DbColumn theSourceColumn) {
List<List<IQueryParameterType>> theAndOrParams, DbColumn[] theSourceColumn) {
ResourceTablePredicateBuilder resourceTableJoin =
mySqlBuilder.addResourceTablePredicateBuilder(theSourceColumn);
@ -2448,7 +2487,7 @@ public class QueryStack {
@Nullable
private Condition createPredicateSearchParameter(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theParamName,
List<List<IQueryParameterType>> theAndOrParams,
@ -2690,7 +2729,7 @@ public class QueryStack {
* by this method
*/
private boolean handleFullyChainedParameter(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
String theParamName,
RequestDetails theRequest,
@ -3147,7 +3186,7 @@ public class QueryStack {
}
public static class SearchForIdsParams {
DbColumn mySourceJoinColumn;
DbColumn[] mySourceJoinColumn;
String myResourceName;
String myParamName;
List<List<IQueryParameterType>> myAndOrParams;
@ -3159,11 +3198,11 @@ public class QueryStack {
return new SearchForIdsParams();
}
public DbColumn getSourceJoinColumn() {
public DbColumn[] getSourceJoinColumn() {
return mySourceJoinColumn;
}
public SearchForIdsParams setSourceJoinColumn(DbColumn theSourceJoinColumn) {
public SearchForIdsParams setSourceJoinColumn(DbColumn[] theSourceJoinColumn) {
mySourceJoinColumn = theSourceJoinColumn;
return this;
}

View File

@ -121,9 +121,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -141,7 +143,7 @@ import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.UNDESIRED_RESOURCE_LINKAGES_FOR_EVERYTHING_ON_PATIENT_INSTANCE;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.LOCATION_POSITION;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.SearchForIdsParams.with;
import static ca.uhn.fhir.jpa.util.InClauseNormalizer.*;
import static ca.uhn.fhir.jpa.util.InClauseNormalizer.normalizeIdListForInClause;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.defaultString;
@ -800,7 +802,16 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
String sql = allTargetsSql.getSql();
Object[] args = allTargetsSql.getBindVariables().toArray(new Object[0]);
List<Long> output = jdbcTemplate.query(sql, args, new SingleColumnRowMapper<>(Long.class));
List<Long> output = jdbcTemplate.query(sql, args, new RowMapper<Long>() {
@Override
public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
if (myPartitionSettings.isPartitioningEnabled()) {
return rs.getLong(2);
} else {
return rs.getLong(1);
}
}
});
// we add a search executor to fetch unlinked patients first
theSearchQueryExecutors.add(new ResolvedSearchQueryExecutor(output));

View File

@ -63,7 +63,7 @@ public class MissingParameterQueryParams {
/**
* The column on which to join.
*/
private final DbColumn mySourceJoinColumn;
private final DbColumn[] mySourceJoinColumn;
/**
* The partition id
@ -76,7 +76,7 @@ public class MissingParameterQueryParams {
List<? extends IQueryParameterType> theList,
String theParamName,
String theResourceType,
DbColumn theSourceJoinColumn,
DbColumn[] theSourceJoinColumn,
RequestPartitionId theRequestPartitionId) {
mySqlBuilder = theSqlBuilder;
myParamType = theParamType;
@ -116,7 +116,7 @@ public class MissingParameterQueryParams {
return myResourceType;
}
public DbColumn getSourceJoinColumn() {
public DbColumn[] getSourceJoinColumn() {
return mySourceJoinColumn;
}

View File

@ -24,20 +24,22 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class PredicateBuilderCacheKey {
private final DbColumn myDbColumn;
private final DbColumn[] myDbColumn;
private final PredicateBuilderTypeEnum myType;
private final String myParamName;
private final int myHashCode;
public PredicateBuilderCacheKey(DbColumn theDbColumn, PredicateBuilderTypeEnum theType, String theParamName) {
public PredicateBuilderCacheKey(DbColumn[] theDbColumn, PredicateBuilderTypeEnum theType, String theParamName) {
myDbColumn = theDbColumn;
myType = theType;
myParamName = theParamName;
myHashCode = new HashCodeBuilder()
.append(myDbColumn)
.append(myType)
.append(myParamName)
.toHashCode();
HashCodeBuilder hashBuilder = new HashCodeBuilder().append(myType).append(myParamName);
if (theDbColumn != null) {
for (DbColumn next : theDbColumn) {
hashBuilder.append(next);
}
}
myHashCode = hashBuilder.toHashCode();
}
@Override

View File

@ -51,10 +51,14 @@ public abstract class BaseJoiningPredicateBuilder extends BasePredicateBuilder {
public abstract DbColumn getResourceIdColumn();
DbColumn getPartitionIdColumn() {
public DbColumn getPartitionIdColumn() {
return myColumnPartitionId;
}
public DbColumn[] getJoinColumns() {
return getSearchQueryBuilder().toJoinColumns(getPartitionIdColumn(), getResourceIdColumn());
}
public Condition combineWithRequestPartitionIdPredicate(
RequestPartitionId theRequestPartitionId, Condition theCondition) {
Condition partitionIdPredicate = createPartitionIdPredicate(theRequestPartitionId);

View File

@ -41,6 +41,10 @@ public abstract class BasePredicateBuilder {
mySearchSqlBuilder = theSearchSqlBuilder;
}
protected SearchQueryBuilder getSearchQueryBuilder() {
return mySearchSqlBuilder;
}
PartitionSettings getPartitionSettings() {
return mySearchSqlBuilder.getPartitionSettings();
}
@ -84,7 +88,7 @@ public abstract class BasePredicateBuilder {
return mySearchSqlBuilder.getOrCreateFirstPredicateBuilder(theIncludeResourceTypeAndNonDeletedFlag);
}
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn theFromColumn, DbColumn theToColumn) {
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn[] theFromColumn, DbColumn[] theToColumn) {
mySearchSqlBuilder.addJoin(theFromTable, theToTable, theFromColumn, theToColumn);
}
}

View File

@ -59,7 +59,7 @@ public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
@Nullable
public Condition createPredicateResourceId(
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumn,
String theResourceName,
List<List<IQueryParameterType>> theValues,
SearchFilterParser.CompareOperation theOperation,
@ -134,8 +134,9 @@ public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
return queryRootTable.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
}
} else {
DbColumn resIdColumn = getResourceIdColumn(theSourceJoinColumn);
return QueryParameterUtils.toEqualToOrInPredicate(
theSourceJoinColumn,
resIdColumn,
generatePlaceholders(resourceIds),
operation == SearchFilterParser.CompareOperation.ne);
}
@ -143,4 +144,23 @@ public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
return null;
}
/**
* This method takes 1-2 columns and returns the last one. This is useful where the input is an array of
* join columns for SQL Search expressions. In partition key mode, there are 2 columns (partition id and resource id).
* In non partition key mode, only the resource id column is used.
*/
@Nullable
public static DbColumn getResourceIdColumn(@Nullable DbColumn[] theJoinColumns) {
DbColumn resIdColumn;
if (theJoinColumns == null) {
return null;
} else if (theJoinColumns.length == 1) {
resIdColumn = theJoinColumns[0];
} else {
assert theJoinColumns.length == 2;
resIdColumn = theJoinColumns[1];
}
return resIdColumn;
}
}

View File

@ -108,6 +108,9 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
private final QueryStack myQueryStack;
private final boolean myReversed;
private final DbColumn myColumnTargetPartitionId;
private final DbColumn myColumnSrcPartitionId;
@Autowired
private JpaStorageSettings myStorageSettings;
@ -133,9 +136,11 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
QueryStack theQueryStack, SearchQueryBuilder theSearchSqlBuilder, boolean theReversed) {
super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RES_LINK"));
myColumnSrcResourceId = getTable().addColumn("SRC_RESOURCE_ID");
myColumnSrcPartitionId = getTable().addColumn("PARTITION_ID");
myColumnSrcType = getTable().addColumn("SOURCE_RESOURCE_TYPE");
myColumnSrcPath = getTable().addColumn("SRC_PATH");
myColumnTargetResourceId = getTable().addColumn("TARGET_RESOURCE_ID");
myColumnTargetPartitionId = getTable().addColumn("TARGET_RES_PARTITION_ID");
myColumnTargetResourceUrl = getTable().addColumn("TARGET_RESOURCE_URL");
myColumnTargetResourceType = getTable().addColumn("TARGET_RESOURCE_TYPE");
@ -159,10 +164,36 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
return myColumnTargetResourceId;
}
public DbColumn getColumnTargetPartitionId() {
return myColumnTargetPartitionId;
}
public DbColumn[] getJoinColumnsForTarget() {
return getSearchQueryBuilder().toJoinColumns(getColumnTargetPartitionId(), getColumnTargetResourceId());
}
public DbColumn[] getJoinColumnsForSource() {
return getSearchQueryBuilder().toJoinColumns(getPartitionIdColumn(), myColumnSrcResourceId);
}
/**
* Note that this may return the SRC_RESOURCE_ID or TGT_RESOURCE_ID depending
* on whether we're building a forward or reverse link. If you need a specific
* one of these, use {@link #getJoinColumnsForSource()} or {@link #getJoinColumnsForTarget()}.
*/
@Override
public DbColumn[] getJoinColumns() {
return super.getJoinColumns();
}
public DbColumn getColumnSrcResourceId() {
return myColumnSrcResourceId;
}
public DbColumn getColumnSrcPartitionId() {
return myColumnSrcPartitionId;
}
public DbColumn getColumnTargetResourceType() {
return myColumnTargetResourceType;
}
@ -509,7 +540,7 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
List<List<IQueryParameterType>> chainParamValues = Collections.singletonList(orValues);
andPredicates.add(
childQueryFactory.searchForIdsWithAndOr(with().setSourceJoinColumn(myColumnTargetResourceId)
childQueryFactory.searchForIdsWithAndOr(with().setSourceJoinColumn(getJoinColumnsForTarget())
.setResourceName(subResourceName)
.setParamName(chain)
.setAndOrParams(chainParamValues)

View File

@ -34,8 +34,6 @@ import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.FunctionCall;
import com.healthmarketscience.sqlbuilder.UnaryCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
@ -44,7 +42,6 @@ import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.cr
public class SourcePredicateBuilder extends BaseJoiningPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(SourcePredicateBuilder.class);
private final DbColumn myColumnSourceUri;
private final DbColumn myColumnRequestId;
private final DbColumn myResourceIdColumn;

View File

@ -66,7 +66,9 @@ public class TagPredicateBuilder extends BaseJoiningPredicateBuilder {
List<Triple<String, String, String>> theTokens,
String theParamName,
RequestPartitionId theRequestPartitionId) {
addJoin(getTable(), myTagDefinitionTable, myColumnTagId, myTagDefinitionColumnTagId);
addJoin(getTable(), myTagDefinitionTable, new DbColumn[] {myColumnTagId}, new DbColumn[] {
myTagDefinitionColumnTagId
});
return createPredicateTagList(theTagType, theTokens);
}

View File

@ -0,0 +1,68 @@
package ca.uhn.fhir.jpa.search.builder.sql;
import com.healthmarketscience.common.util.AppendableExt;
import com.healthmarketscience.sqlbuilder.SqlContext;
import com.healthmarketscience.sqlbuilder.SqlObject;
import com.healthmarketscience.sqlbuilder.ValidationContext;
import com.healthmarketscience.sqlbuilder.dbspec.Column;
import com.healthmarketscience.sqlbuilder.dbspec.Table;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ColumnTupleObject extends SqlObject {
private final List<Column> myColumns;
public ColumnTupleObject(Column... theColumns) {
myColumns = List.of(theColumns);
}
@Override
protected void collectSchemaObjects(ValidationContext vContext) {
myColumns.forEach(vContext::addColumn);
}
@Override
public void appendTo(AppendableExt app) throws IOException {
app.append('(');
for (Iterator<Column> iter = myColumns.iterator(); iter.hasNext(); ) {
Column column = iter.next();
appendTableAliasPrefix(app, column.getTable());
app.append(column.getColumnNameSQL());
if (iter.hasNext()) {
app.append(',');
}
}
app.append(')');
}
/**
* Outputs the table alias prefix <code>"[&lt;tableAlias&gt;.]"</code> for a
* column reference if the current SqlContext specifies table aliases should
* be used (and the table has an alias), otherwise does nothing.
*/
static void appendTableAliasPrefix(AppendableExt app, Table table) throws IOException {
if (SqlContext.getContext(app).getUseTableAliases()) {
String alias = table.getAlias();
if (isNotBlank(alias)) {
app.append(alias).append(".");
}
}
}
public static Object from(DbColumn[] theJoinColumns) {
if (theJoinColumns.length == 1) {
return theJoinColumns[0];
} else {
return new ColumnTupleObject(theJoinColumns);
}
}
}

View File

@ -102,6 +102,7 @@ public class SearchQueryBuilder {
private final SqlObjectFactory mySqlBuilderFactory;
private final boolean myCountQuery;
private final Dialect myDialect;
private final boolean mySelectPartitionId;
private boolean myMatchNothing;
private ResourceTablePredicateBuilder myResourceTableRoot;
private boolean myHaveAtLeastOnePredicate;
@ -111,6 +112,7 @@ public class SearchQueryBuilder {
private boolean myNeedResourceTableRoot;
private int myNextNearnessColumnId = 0;
private DbColumn mySelectedResourceIdColumn;
private DbColumn mySelectedPartitionIdColumn;
/**
* Constructor
@ -134,7 +136,8 @@ public class SearchQueryBuilder {
UUID.randomUUID() + "-",
theDialectProvider.getDialect(),
theCountQuery,
new ArrayList<>());
new ArrayList<>(),
thePartitionSettings.isPartitioningEnabled());
}
/**
@ -150,7 +153,8 @@ public class SearchQueryBuilder {
String theBindVariableSubstitutionBase,
Dialect theDialect,
boolean theCountQuery,
ArrayList<Object> theBindVariableValues) {
ArrayList<Object> theBindVariableValues,
boolean theSelectPartitionId) {
myFhirContext = theFhirContext;
myStorageSettings = theStorageSettings;
myPartitionSettings = thePartitionSettings;
@ -172,6 +176,7 @@ public class SearchQueryBuilder {
myBindVariableSubstitutionBase = theBindVariableSubstitutionBase;
myBindVariableValues = theBindVariableValues;
mySelectPartitionId = theSelectPartitionId;
}
public FhirContext getFhirContext() {
@ -201,7 +206,7 @@ public class SearchQueryBuilder {
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a COORDS search parameter
*/
public CoordsPredicateBuilder addCoordsPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public CoordsPredicateBuilder addCoordsPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
CoordsPredicateBuilder retVal = mySqlBuilderFactory.coordsPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -210,7 +215,7 @@ public class SearchQueryBuilder {
/**
* Create, add and return a predicate builder (or a root query if no root query exists yet) for selecting on a DATE search parameter
*/
public DatePredicateBuilder addDatePredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public DatePredicateBuilder addDatePredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
DatePredicateBuilder retVal = mySqlBuilderFactory.dateIndexTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -226,7 +231,7 @@ public class SearchQueryBuilder {
/**
* Create, add and return a predicate builder (or a root query if no root query exists yet) for selecting on a NUMBER search parameter
*/
public NumberPredicateBuilder addNumberPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public NumberPredicateBuilder addNumberPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
NumberPredicateBuilder retVal = createNumberPredicateBuilder();
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -242,7 +247,7 @@ public class SearchQueryBuilder {
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on the Resource table
*/
public ResourceTablePredicateBuilder addResourceTablePredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public ResourceTablePredicateBuilder addResourceTablePredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
ResourceTablePredicateBuilder retVal = mySqlBuilderFactory.resourceTable(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -251,7 +256,7 @@ public class SearchQueryBuilder {
/**
* Create, 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) {
public QuantityPredicateBuilder addQuantityPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
QuantityPredicateBuilder retVal = createQuantityPredicateBuilder();
addTable(retVal, theSourceJoinColumn);
@ -266,7 +271,7 @@ public class SearchQueryBuilder {
}
public QuantityNormalizedPredicateBuilder addQuantityNormalizedPredicateBuilder(
@Nullable DbColumn theSourceJoinColumn) {
@Nullable DbColumn[] theSourceJoinColumn) {
QuantityNormalizedPredicateBuilder retVal = mySqlBuilderFactory.quantityNormalizedIndexTable(this);
addTable(retVal, theSourceJoinColumn);
@ -278,7 +283,7 @@ public class SearchQueryBuilder {
* 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
*/
public SourcePredicateBuilder addSourcePredicateBuilder(
@Nullable DbColumn theSourceJoinColumn, SelectQuery.JoinType theJoinType) {
@Nullable DbColumn[] theSourceJoinColumn, SelectQuery.JoinType theJoinType) {
SourcePredicateBuilder retVal = mySqlBuilderFactory.newSourcePredicateBuilder(this);
addTable(retVal, theSourceJoinColumn, theJoinType);
return retVal;
@ -288,7 +293,7 @@ public class SearchQueryBuilder {
* Create, add and return a predicate builder (or a root query if no root query exists yet) for selecting on a REFERENCE search parameter
*/
public ResourceLinkPredicateBuilder addReferencePredicateBuilder(
QueryStack theQueryStack, @Nullable DbColumn theSourceJoinColumn) {
QueryStack theQueryStack, @Nullable DbColumn[] theSourceJoinColumn) {
ResourceLinkPredicateBuilder retVal = createReferencePredicateBuilder(theQueryStack);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -306,7 +311,7 @@ public class SearchQueryBuilder {
* source and target are reversed. This is used for _has queries.
*/
public ResourceLinkPredicateBuilder addReferencePredicateBuilderReversed(
QueryStack theQueryStack, DbColumn theSourceJoinColumn) {
QueryStack theQueryStack, DbColumn[] theSourceJoinColumn) {
ResourceLinkPredicateBuilder retVal = mySqlBuilderFactory.referenceIndexTable(theQueryStack, this, true);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -315,7 +320,7 @@ public class SearchQueryBuilder {
/**
* Create, add and return a predicate builder (or a root query if no root query exists yet) for selecting on a STRING search parameter
*/
public StringPredicateBuilder addStringPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public StringPredicateBuilder addStringPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
StringPredicateBuilder retVal = createStringPredicateBuilder();
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -331,7 +336,7 @@ public class SearchQueryBuilder {
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a <code>_tag</code> search parameter
*/
public TagPredicateBuilder addTagPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public TagPredicateBuilder addTagPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
TagPredicateBuilder retVal = mySqlBuilderFactory.newTagPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -340,7 +345,7 @@ public class SearchQueryBuilder {
/**
* Create, add and return a predicate builder (or a root query if no root query exists yet) for selecting on a TOKEN search parameter
*/
public TokenPredicateBuilder addTokenPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public TokenPredicateBuilder addTokenPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
TokenPredicateBuilder retVal = createTokenPredicateBuilder();
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -358,10 +363,11 @@ public class SearchQueryBuilder {
mySelect.addCustomJoin(theJoinType, theFromTable, theToTable, theCondition);
}
public ComboCondition createOnCondition(DbColumn theSourceColumn, DbColumn theTargetColumn) {
public ComboCondition createOnCondition(DbColumn[] theSourceColumn, DbColumn[] theTargetColumn) {
ComboCondition onCondition = ComboCondition.and();
onCondition.addCondition(BinaryCondition.equalTo(theSourceColumn, theTargetColumn));
for (int i = 0; i < theSourceColumn.length; i += 1) {
onCondition.addCondition(BinaryCondition.equalTo(theSourceColumn[i], theTargetColumn[i]));
}
return onCondition;
}
@ -369,7 +375,7 @@ public class SearchQueryBuilder {
* Add and return a predicate builder (or a root query if no root query exists yet) for selecting on a <code>:missing</code> search parameter
*/
public SearchParamPresentPredicateBuilder addSearchParamPresentPredicateBuilder(
@Nullable DbColumn theSourceJoinColumn) {
@Nullable DbColumn[] theSourceJoinColumn) {
SearchParamPresentPredicateBuilder retVal = mySqlBuilderFactory.searchParamPresentPredicateBuilder(this);
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -378,7 +384,7 @@ public class SearchQueryBuilder {
/**
* Create, add and return a predicate builder (or a root query if no root query exists yet) for selecting on a URI search parameter
*/
public UriPredicateBuilder addUriPredicateBuilder(@Nullable DbColumn theSourceJoinColumn) {
public UriPredicateBuilder addUriPredicateBuilder(@Nullable DbColumn[] theSourceJoinColumn) {
UriPredicateBuilder retVal = createUriPredicateBuilder();
addTable(retVal, theSourceJoinColumn);
return retVal;
@ -402,19 +408,19 @@ public class SearchQueryBuilder {
/**
* Add and return a predicate builder (or a root query if no root query exists yet) for an arbitrary table
*/
private void addTable(BaseJoiningPredicateBuilder thePredicateBuilder, @Nullable DbColumn theSourceJoinColumn) {
private void addTable(BaseJoiningPredicateBuilder thePredicateBuilder, @Nullable DbColumn[] theSourceJoinColumn) {
addTable(thePredicateBuilder, theSourceJoinColumn, SelectQuery.JoinType.INNER);
}
private void addTable(
BaseJoiningPredicateBuilder thePredicateBuilder,
@Nullable DbColumn theSourceJoinColumn,
@Nullable DbColumn[] theSourceJoinColumns,
SelectQuery.JoinType theJoinType) {
if (theSourceJoinColumn != null) {
DbTable fromTable = theSourceJoinColumn.getTable();
if (theSourceJoinColumns != null) {
DbTable fromTable = theSourceJoinColumns[0].getTable();
DbTable toTable = thePredicateBuilder.getTable();
DbColumn toColumn = thePredicateBuilder.getResourceIdColumn();
addJoin(fromTable, toTable, theSourceJoinColumn, toColumn, theJoinType);
DbColumn[] toColumn = toJoinColumns(thePredicateBuilder);
addJoin(fromTable, toTable, theSourceJoinColumns, toColumn, theJoinType);
} else {
if (myFirstPredicateBuilder == null) {
@ -433,8 +439,14 @@ public class SearchQueryBuilder {
mySelect.addCustomColumns(
FunctionCall.count().setIsDistinct(true).addColumnParams(root.getResourceIdColumn()));
} else {
mySelectedResourceIdColumn = root.getResourceIdColumn();
mySelect.addColumns(mySelectedResourceIdColumn);
if (mySelectPartitionId) {
mySelectedResourceIdColumn = root.getResourceIdColumn();
mySelectedPartitionIdColumn = root.getPartitionIdColumn();
mySelect.addColumns(mySelectedPartitionIdColumn, mySelectedResourceIdColumn);
} else {
mySelectedResourceIdColumn = root.getResourceIdColumn();
mySelect.addColumns(mySelectedResourceIdColumn);
}
}
mySelect.addFromTable(root.getTable());
myFirstPredicateBuilder = root;
@ -446,27 +458,52 @@ public class SearchQueryBuilder {
DbTable fromTable = myFirstPredicateBuilder.getTable();
DbTable toTable = thePredicateBuilder.getTable();
DbColumn fromColumn = myFirstPredicateBuilder.getResourceIdColumn();
DbColumn toColumn = thePredicateBuilder.getResourceIdColumn();
DbColumn[] fromColumn = toJoinColumns(myFirstPredicateBuilder);
DbColumn[] toColumn = toJoinColumns(thePredicateBuilder);
addJoin(fromTable, toTable, fromColumn, toColumn, theJoinType);
}
}
@Nonnull
public DbColumn[] toJoinColumns(BaseJoiningPredicateBuilder theBuilder) {
DbColumn partitionIdColumn = theBuilder.getPartitionIdColumn();
DbColumn resourceIdColumn = theBuilder.getResourceIdColumn();
return toJoinColumns(partitionIdColumn, resourceIdColumn);
}
/**
* Remove or keep partition_id columns depending on settings.
*/
@Nonnull
public DbColumn[] toJoinColumns(DbColumn partitionIdColumn, DbColumn resourceIdColumn) {
if (isIncludePartitionIdInJoins()) {
return new DbColumn[] {partitionIdColumn, resourceIdColumn};
} else {
return new DbColumn[] {resourceIdColumn};
}
}
public boolean isIncludePartitionIdInJoins() {
return mySelectPartitionId && myPartitionSettings.isPartitionIdsInPrimaryKeys();
}
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn[] theFromColumn, DbColumn[] theToColumn) {
addJoin(theFromTable, theToTable, theFromColumn, theToColumn, SelectQuery.JoinType.INNER);
}
public void addJoin(
DbTable theFromTable,
DbTable theToTable,
DbColumn theFromColumn,
DbColumn theToColumn,
DbColumn[] theFromColumn,
DbColumn[] theToColumn,
SelectQuery.JoinType theJoinType) {
Join join = new DbJoin(
mySpec, theFromTable, theToTable, new DbColumn[] {theFromColumn}, new DbColumn[] {theToColumn});
assert theFromColumn.length == theToColumn.length;
Join join = new DbJoin(mySpec, theFromTable, theToTable, theFromColumn, theToColumn);
mySelect.addJoins(theJoinType, join);
}
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn theFromColumn, DbColumn theToColumn) {
Join join = new DbJoin(
mySpec, theFromTable, theToTable, new DbColumn[] {theFromColumn}, new DbColumn[] {theToColumn});
mySelect.addJoins(SelectQuery.JoinType.INNER, join);
public boolean isSelectPartitionId() {
return mySelectPartitionId;
}
/**
@ -831,7 +868,7 @@ public class SearchQueryBuilder {
}
}
public SearchQueryBuilder newChildSqlBuilder() {
public SearchQueryBuilder newChildSqlBuilder(boolean theSelectPartitionId) {
return new SearchQueryBuilder(
myFhirContext,
myStorageSettings,
@ -842,7 +879,8 @@ public class SearchQueryBuilder {
myBindVariableSubstitutionBase,
myDialect,
false,
myBindVariableValues);
myBindVariableValues,
theSelectPartitionId);
}
public SelectQuery getSelect() {

View File

@ -107,6 +107,7 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
* is managed by Spring has been started before this method is called.
*/
HapiTransactionService.requireTransaction();
ourLog.trace("About to execute SQL: {}. Parameters: {}", sql, Arrays.toString(args));
Query nativeQuery = myEntityManager.createNativeQuery(sql);
org.hibernate.query.Query<?> hibernateQuery = (org.hibernate.query.Query<?>) nativeQuery;
@ -114,8 +115,6 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
hibernateQuery.setParameter(i, args[i - 1]);
}
ourLog.trace("About to execute SQL: {}. Parameters: {}", sql, Arrays.toString(args));
/*
* These settings help to ensure that we use a search cursor
* as opposed to loading all search results into memory
@ -145,14 +144,7 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
if (myResultSet == null || !myResultSet.hasNext()) {
myNext = NO_MORE;
} else {
Object nextRow = Objects.requireNonNull(myResultSet.next());
Number next;
if (nextRow instanceof Number) {
next = (Number) nextRow;
} else {
next = (Number) ((Object[]) nextRow)[0];
}
myNext = next.longValue();
myNext = getNextPid(myResultSet);
}
} catch (Exception e) {
@ -163,6 +155,40 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
}
}
private long getNextPid(ScrollableResultsIterator<Object> theResultSet) {
Object nextRow = Objects.requireNonNull(theResultSet.next());
// We should typically get two columns back, the first is the partition ID and the second
// is the resource ID. But if we're doing a count query, we'll get a single column in an array
// or maybe even just a single non array value depending on how the platform handles it.
if (nextRow instanceof Number) {
return ((Number) nextRow).longValue();
} else {
Object[] nextRowAsArray = (Object[]) nextRow;
if (nextRowAsArray.length == 1) {
return (Long) nextRowAsArray[0];
} else {
int i;
// TODO MB add a strategy object to GeneratedSql to describe the result set.
// or make SQE generic
// Comment to reviewer: this will be cleaner with the next
// merge from ja_20240718_pk_schema_selector
// We have some cases to distinguish:
// - res_id
// - count
// - partition_id, res_id
// - res_id, coord-dist
// - partition_id, res_id, coord-dist
// Assume res_id is first Long in row, and is in first two columns
if (nextRowAsArray[0] instanceof Long) {
return (long) nextRowAsArray[0];
} else {
return (long) nextRowAsArray[1];
}
}
}
}
public static SearchQueryExecutor emptyExecutor() {
return NO_VALUE_EXECUTOR;
}

View File

@ -0,0 +1,69 @@
package ca.uhn.fhir.jpa.search.builder.models;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class PredicateBuilderCacheKeyTest {
static final DbSpec ourSpec = new DbSpec();
static final DbSchema ourSchema = new DbSchema(ourSpec, "schema");
static final DbTable ourTable = new DbTable(ourSchema, "HFJ_RESOURCE");
static final DbColumn ourColumn2 = new DbColumn(ourTable, "COL2", "varchar2");
static final DbColumn ourColumn1 = new DbColumn(ourTable, "COL1", "varchar2");
@Test
void testEqualsImpliesHashEqual() {
var key1 = new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "identifier");
var key2 = new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "identifier");
assertEquals(key1, key2);
assertEquals(key1.hashCode(), key2.hashCode());
}
static Stream<Object[]> differenceTestCases() {
return Stream.of(
new Object[]{
"different type",
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "identifier"),
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.DATE, "identifier")
},
new Object[]{
"different sp name",
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "identifier"),
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "code")
},
new Object[]{
"different number of columns",
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "identifier"),
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1}, PredicateBuilderTypeEnum.TOKEN, "code")
},
new Object[]{
"different column order",
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn1, ourColumn2}, PredicateBuilderTypeEnum.TOKEN, "identifier"),
new PredicateBuilderCacheKey(new DbColumn[]{ourColumn2, ourColumn1}, PredicateBuilderTypeEnum.TOKEN, "code")
}
);
}
@ParameterizedTest
@MethodSource("differenceTestCases")
void testChangesMakeNotEqual(String theMessage, PredicateBuilderCacheKey theValue, PredicateBuilderCacheKey theComparisonValue) {
assertNotEquals(theValue, theComparisonValue, theMessage + ": equals");
}
@ParameterizedTest
@MethodSource("differenceTestCases")
void testChangesChangeHashCode(String theMessage, PredicateBuilderCacheKey theValue, PredicateBuilderCacheKey theComparisonValue) {
assertNotEquals(theValue.hashCode(), theComparisonValue.hashCode(), theMessage);
}
}

View File

@ -33,8 +33,36 @@ public class PartitionSettings {
private Integer myDefaultPartitionId;
private boolean myAlwaysOpenNewTransactionForDifferentPartition;
private boolean myConditionalCreateDuplicateIdentifiersEnabled = false;
private boolean myPartitionIdsInPrimaryKeys = false;
public PartitionSettings() {
super();
}
/**
* This flag activates partition IDs in PKs mode, which is newly introduced in HAPI FHIR 8.0.0.
* This mode causes partition IDs to be included in all primary keys, joins, and emitted
* SQL. It also affects the generated schema and migrations. This setting should not be changed
* after the database has been initialized, unless you have performed an appropriate migration.
*
* @since 8.0.0
*/
public boolean isPartitionIdsInPrimaryKeys() {
return myPartitionIdsInPrimaryKeys;
}
/**
* This flag activates partition IDs in PKs mode, which is newly introduced in HAPI FHIR 8.0.0.
* This mode causes partition IDs to be included in all primary keys, joins, and emitted
* SQL. It also affects the generated schema and migrations. This setting should not be changed
* after the database has been initialized, unless you have performed an appropriate migration.
*
* @since 8.0.0
*/
public void setPartitionIdsInPrimaryKeys(boolean thePartitionIdsInPrimaryKeys) {
myPartitionIdsInPrimaryKeys = thePartitionIdsInPrimaryKeys;
}
public PartitionSettings() {}
/**
* Should we always open a new database transaction if the partition context changes
*

View File

@ -23,6 +23,7 @@ import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.rest.api.server.storage.BaseResourcePersistentId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@ -66,6 +67,10 @@ public class JpaPid extends BaseResourcePersistentId<Long> {
return this;
}
public static List<Long> toLongList(JpaPid[] thePids) {
return toLongList(Arrays.asList(thePids));
}
public static List<Long> toLongList(Collection<JpaPid> thePids) {
List<Long> retVal = new ArrayList<>(thePids.size());
for (JpaPid next : thePids) {
@ -129,4 +134,10 @@ public class JpaPid extends BaseResourcePersistentId<Long> {
public String toString() {
return myId.toString();
}
public Integer getPartitionId() {
// wipmb should we return null instead?
assert getPartitionablePartitionId() != null;
return getPartitionablePartitionId().getPartitionId();
}
}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.jpa.model.dao;
import org.junit.jupiter.api.Test;
import java.util.List;
import static ca.uhn.fhir.jpa.model.dao.JpaPid.fromId;
import static org.junit.jupiter.api.Assertions.*;
class JpaPidTest {
@Test
void testToLongList() {
assertEquals(List.of(), JpaPid.toLongList(List.of()));
assertEquals(List.of(1L, 2L), JpaPid.toLongList(List.of(fromId(1L), fromId(2L))));
}
@Test
void testArrayToLongList() {
assertEquals(List.of(), JpaPid.toLongList(new JpaPid[0]));
assertEquals(List.of(1L, 2L), JpaPid.toLongList(new JpaPid[]{fromId(1L), fromId(2L)}));
}
}

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.junit.jupiter.api.Assertions.assertEquals;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@ -16,7 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ResourceProviderSearchModifierDstu3Test extends BaseResourceProviderDstu3Test{
@Autowired

View File

@ -1,12 +1,13 @@
package ca.uhn.fhir.jpa.dao.r4;
import static org.junit.jupiter.api.Assertions.assertEquals;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import org.hl7.fhir.instance.model.api.IIdType;
@ -15,11 +16,17 @@ import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.UUID;
import static ca.uhn.fhir.interceptor.api.Pointcut.STORAGE_PARTITION_IDENTIFY_ANY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
@ -30,46 +37,110 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
public void before() throws Exception {
super.before();
myStorageSettings.setAdvancedHSearchIndexing(false);
myInterceptorRegistry.registerInterceptor(new MyPartitionInterceptor());
}
@AfterEach
public void after() {
myStorageSettings.setTagStorageMode(JpaStorageSettings.DEFAULT_TAG_STORAGE_MODE);
myPartitionSettings.setDefaultPartitionId(new PartitionSettings().getDefaultPartitionId());
myPartitionSettings.setPartitionIdsInPrimaryKeys(new PartitionSettings().isPartitionIdsInPrimaryKeys());
myInterceptorRegistry.unregisterInterceptorsIf(t->t instanceof MyPartitionInterceptor);
}
record SqlGenerationTestCase(String comment, String restQuery, String expectedSql, String expectedPartitionedSql) {
@Override
public String toString() {
return comment;
}
}
static List<SqlGenerationTestCase> sqlGenerationTestCases() {
return List.of(
new SqlGenerationTestCase(
"single string - no hfj_resource root",
"Patient?name=FOO",
"SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))",
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.PARTITION_ID = ?) AND ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)))"
)
, new SqlGenerationTestCase(
"two regular params - should use hfj_resource as root",
"Patient?name=smith&active=true",
"SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_VALUE = ?))",
"SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) INNER JOIN HFJ_SPIDX_TOKEN t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID)) WHERE (((t0.PARTITION_ID = ?) AND ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))) AND ((t2.PARTITION_ID = ?) AND (t2.HASH_VALUE = ?)))"
)
, new SqlGenerationTestCase(
"token not as a NOT IN subselect",
"Encounter?class:not=not-there",
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.RES_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))",
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.PARTITION_ID = ?) AND ((t0.PARTITION_ID,t0.RES_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) )))"
)
, new SqlGenerationTestCase(
"token not on chain join - NOT IN from hfj_res_link target columns",
"Observation?encounter.class:not=not-there",
"SELECT t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))",
"SELECT t0.PARTITION_ID,t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.PARTITION_ID = ?) AND ((t0.TARGET_RES_PARTITION_ID,t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) )))"
)
, new SqlGenerationTestCase(
"bare sort",
"Patient?_sort=name",
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST",
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.PARTITION_ID = ?)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST"
)
, new SqlGenerationTestCase(
"sort with predicate",
"Patient?active=true&_sort=name",
"SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON (t1.RES_ID = t0.RES_ID) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (t0.HASH_VALUE = ?) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST",
"SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE ((t0.PARTITION_ID = ?) AND (t0.HASH_VALUE = ?)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST"
)
, new SqlGenerationTestCase(
"chained sort",
"Patient?_sort=Practitioner:general-practitioner.name",
"SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_RES_LINK t1 ON ((t0.RES_ID = t1.SRC_RESOURCE_ID) AND (t1.SRC_PATH = ?)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.TARGET_RESOURCE_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST",
"SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_RES_LINK t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.SRC_RESOURCE_ID) AND (t1.SRC_PATH = ?)) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.TARGET_RES_PARTITION_ID = t2.PARTITION_ID) AND (t1.TARGET_RESOURCE_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.PARTITION_ID = ?)) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST"
)
);
}
/**
* One regular search params - Doesn't need HFJ_RESOURCE as root
* Test SQL generation with RES_ID joins.
*/
@Test
public void testSingleRegularSearchParam() {
@ParameterizedTest(name = "[{index}] - {0}")
@MethodSource("sqlGenerationTestCases")
void testSqlGeneration_DefaultNoPartitionJoin(SqlGenerationTestCase theTestCase) {
// default config
myCaptureQueriesListener.clear();
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_NAME, new StringParam("FOO"));
myPatientDao.search(map);
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))", sql);
String sql = getSqlForRestQuery(theTestCase.restQuery);
assertEquals(theTestCase.expectedSql, sql, theTestCase.comment);
}
/**
* Two regular search params - Should use HFJ_RESOURCE as root
* Test SQL generation with joins including RES_ID, and PARTITION_ID
*/
@Test
public void testTwoRegularSearchParams() {
myCaptureQueriesListener.clear();
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_NAME, new StringParam("FOO"))
.add(Patient.SP_GENDER, new TokenParam("a", "b"));
myPatientDao.search(map);
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_SYS_AND_VALUE = ?))", sql);
@ParameterizedTest(name = "[{index}] - {0}")
@MethodSource("sqlGenerationTestCases")
void testSqlGeneration_WithPartitionJoins(SqlGenerationTestCase theTestCase) {
// include partition_id in joins
myPartitionSettings.setDefaultPartitionId(0);
myPartitionSettings.setPartitionIdsInPrimaryKeys(true);
myPartitionSettings.setPartitioningEnabled(true);
String sql = getSqlForRestQuery(theTestCase.restQuery);
assertEquals(theTestCase.expectedPartitionedSql, sql, theTestCase.comment);
}
private String getSqlForRestQuery(String theFhirRestQuery) {
myCaptureQueriesListener.clear();
myTestDaoSearch.searchForIds(theFhirRestQuery);
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
return myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
}
@Test
public void testSearchByProfile_VersionedMode() {
@ -77,14 +148,14 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
String code = "http://" + UUID.randomUUID();
Patient p = new Patient();
p.getMeta().addProfile(code);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
myMemoryCacheService.invalidateAllCaches();
// Search
myCaptureQueriesListener.clear();
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Constants.PARAM_PROFILE, new TokenParam(code));
IBundleProvider outcome = myPatientDao.search(map);
IBundleProvider outcome = myPatientDao.search(map, mySrd);
assertEquals(3, myCaptureQueriesListener.countSelectQueries());
// Query 1 - Find resources: Make sure we search for tag type+system+code always
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
@ -106,7 +177,6 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
boolean reindexParamCache = myStorageSettings.isMarkResourcesForReindexingUponSearchParameterChange();
myStorageSettings.setMarkResourcesForReindexingUponSearchParameterChange(false);
// SearchParameter searchParameter = FhirResourceDaoR4TagsTest.createSearchParamForInlineResourceProfile();
SearchParameter searchParameter = FhirResourceDaoR4TagsInlineTest.createSearchParameterForInlineProfile();
ourLog.debug("SearchParam:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(searchParameter));
mySearchParameterDao.update(searchParameter, mySrd);
@ -116,14 +186,14 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
String code = "http://" + UUID.randomUUID();
Patient p = new Patient();
p.getMeta().addProfile(code);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
myMemoryCacheService.invalidateAllCaches();
// Search
myCaptureQueriesListener.clear();
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Constants.PARAM_PROFILE, new UriParam(code));
IBundleProvider outcome = myPatientDao.search(map);
IBundleProvider outcome = myPatientDao.search(map, mySrd);
assertEquals(2, myCaptureQueriesListener.countSelectQueries());
// Query 1 - Find resources: Just a standard token search in this mode
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
@ -137,5 +207,13 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
myStorageSettings.setMarkResourcesForReindexingUponSearchParameterChange(reindexParamCache);
}
public static class MyPartitionInterceptor {
@Hook(STORAGE_PARTITION_IDENTIFY_ANY)
public RequestPartitionId partition() {
return RequestPartitionId.defaultPartition();
}
}
}

View File

@ -56,7 +56,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.DateTimeType;
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;
@ -1275,7 +1274,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
// Only the read columns should be used, no criteria use partition
assertThat(searchSql).as(searchSql).contains("PARTITION_ID = '1'");
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
}
// Read in null Partition
@ -1327,7 +1326,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
// Only the read columns should be used, no criteria use partition
assertThat(searchSql).as(searchSql).contains("PARTITION_ID = '1'");
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2); // If this switches to 1 that would be fine
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(3); // If this switches to 2 that would be fine
}
// Read in null Partition
@ -1440,10 +1439,11 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
assertThat(StringUtils.countMatches(searchSql, "PARTITION_DATE")).as(searchSql).isEqualTo(1);
// Second SQL performs the search
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase();
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(searchSql).as(searchSql).contains("PARTITION_ID = '1'");
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2); // If this switches to 1 that would be fine
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(3);
}
// Read in null Partition
@ -1492,7 +1492,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'true'"));
}
@ -1509,7 +1509,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING = 'false'"));
}
}
@ -1618,7 +1618,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT"));
assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'"));
}
@ -1645,7 +1645,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
assertThat(StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "HASH_PRESENCE = '-3438137196820602023'")).as(searchSql).isEqualTo(1);
@ -1673,7 +1673,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
assertThat(StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'")).as(searchSql).isEqualTo(1);
@ -1699,7 +1699,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
assertThat(StringUtils.countMatches(searchSql, "t0.PARTITION_ID IS NULL")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "HASH_PRESENCE = '1919227773735728687'")).as(searchSql).isEqualTo(1);
@ -1724,7 +1724,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
@Test
@ -1744,7 +1744,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
@Test
@ -1814,7 +1814,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// Date OR param
@ -1830,7 +1830,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(4, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// Date AND param
@ -1846,7 +1846,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(4, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// DateRangeParam
@ -1862,7 +1862,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// NOTE: the query is changed, only one SP_VALUE_LOW and SP_VALUE_HIGH
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_HIGH"));
@ -1899,7 +1899,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
assertThat(StringUtils.countMatches(searchSql, "SP_VALUE_LOW")).as(searchSql).isEqualTo(2);
// Date OR param
@ -1915,7 +1915,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(4, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// Date AND param
@ -1931,7 +1931,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(4, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// DateRangeParam
@ -1947,7 +1947,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// NOTE: the query is changed, only one SP_VALUE_LOW and SP_VALUE_HIGH
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_HIGH"));
@ -1979,7 +1979,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// Date OR param
@ -1995,7 +1995,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(4, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// Date AND param
@ -2011,7 +2011,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(4, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
// DateRangeParam
@ -2027,7 +2027,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// NOTE: the query is changed, only one SP_VALUE_LOW and SP_VALUE_HIGH
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_HIGH"));
@ -2122,7 +2122,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@ -2145,7 +2145,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
searchSql = searchSql.toUpperCase();
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@ -2169,7 +2169,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@ -2202,7 +2202,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(searchSql).contains("PARTITION_ID IN ('1','2')");
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
// Match two partitions including null
@ -2219,7 +2219,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(searchSql).contains("PARTITION_ID IS NULL");
assertThat(searchSql).contains("PARTITION_ID = '1'");
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(3, StringUtils.countMatches(searchSql, "PARTITION_ID"));
}
}
@ -2280,7 +2280,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
searchSql = searchSql.toUpperCase();
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@ -2308,7 +2308,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
}
@ -2365,7 +2365,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
// And with another param
@ -2382,7 +2382,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(0);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, ".HASH_SYS_AND_VALUE =")).as(searchSql).isEqualTo(1);
@ -2406,7 +2406,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
@ -2434,7 +2434,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@ -2456,7 +2456,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@ -2482,7 +2482,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(2, StringUtils.countMatches(searchSql, "JOIN"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@ -2507,7 +2507,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@ -2532,7 +2532,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
}
@ -2556,7 +2556,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(searchSql).doesNotContain("PARTITION_ID");
assertThat(searchSql).doesNotContain("PARTITION_ID IN");
assertThat(searchSql).doesNotContain("PARTITION_ID =");
assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?family=FAM&gender=male'");
}
@ -2640,7 +2641,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
switch (theReadPartitions) {
case "ALL":
assertThat(searchSql).doesNotContain("t0.PARTITION_ID");
assertThat(searchSql).doesNotContain("t0.PARTITION_ID =");
assertThat(searchSql).doesNotContain("t0.PARTITION_ID IN");
break;
case "ONE":
assertThat(searchSql).contains("t0.PARTITION_ID = '1'");
@ -2678,7 +2680,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
assertThat(StringUtils.countMatches(searchSql, "t0.PARTITION_ID = '1'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "t0.SRC_PATH = 'Observation.subject'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "t0.TARGET_RESOURCE_ID = '" + patientId.getIdPartAsLong() + "'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
// Same query, different partition
addReadPartition(2);
@ -2715,7 +2717,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
assertEquals(1, StringUtils.countMatches(searchSql, "t0.PARTITION_ID IS NULL"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.SRC_PATH = 'Observation.subject'"));
assertEquals(1, StringUtils.countMatches(searchSql, "t0.TARGET_RESOURCE_ID = '" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
// Same query, different partition
addReadPartition(2);
@ -2750,7 +2752,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(StringUtils.countMatches(searchSql.toUpperCase(Locale.US), "PARTITION_ID = '1'")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
// Same query, different partition
addReadPartition(2);
@ -2820,7 +2822,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(StringUtils.countMatches(searchSql.toUpperCase(Locale.US), "PARTITION_ID IS NULL")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(1);
assertThat(StringUtils.countMatches(searchSql, "PARTITION_ID")).as(searchSql).isEqualTo(2);
// Same query, different partition
addReadPartition(2);

View File

@ -223,7 +223,7 @@ public class PartitioningInterceptorR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
} finally {
myInterceptorRegistry.unregisterInterceptor(interceptor);
@ -258,7 +258,7 @@ public class PartitioningInterceptorR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
} finally {
myInterceptorRegistry.unregisterInterceptor(interceptor);

View File

@ -252,7 +252,7 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes
assertEquals(1, outcome.size());
myCaptureQueriesListener.logSelectQueries();
assertThat(myCaptureQueriesListener.getSelectQueries()).hasSize(2);
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false)).contains("SELECT t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.PARTITION_ID = ?)");
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false)).contains("SELECT t0.PARTITION_ID,t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.PARTITION_ID = ?)");
// Typed
myCaptureQueriesListener.clear();
@ -262,7 +262,7 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes
assertEquals(1, outcome.size());
myCaptureQueriesListener.logSelectQueries();
assertThat(myCaptureQueriesListener.getSelectQueries()).hasSize(2);
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false)).contains("SELECT t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.PARTITION_ID = ?)");
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(false, false)).contains("SELECT t0.PARTITION_ID,t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.PARTITION_ID = ?)");
}
@Test
@ -273,7 +273,7 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes
myCaptureQueriesListener.clear();
myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd);
myCaptureQueriesListener.logSelectQueries();
assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE ((t0.RES_TYPE = 'Observation') AND (t0.RES_DELETED_AT IS NULL))", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
assertEquals("SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 WHERE ((t0.RES_TYPE = 'Observation') AND (t0.RES_DELETED_AT IS NULL))", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
}
@Test

View File

@ -49,7 +49,7 @@ public abstract class BaseSearchQueryBuilderDialectTest {
when(mySqlObjectFactory.dateIndexTable(any())).thenReturn(new DatePredicateBuilder(searchQueryBuilder));
BaseJoiningPredicateBuilder firstPredicateBuilder = searchQueryBuilder.getOrCreateFirstPredicateBuilder();
DatePredicateBuilder sortPredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
DatePredicateBuilder sortPredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(firstPredicateBuilder.getJoinColumns());
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate("MolecularSequence", "variant-start");
searchQueryBuilder.addPredicate(hashIdentityPredicate);

View File

@ -63,7 +63,7 @@ public class SearchQueryBuilderDialectMySqlTest extends BaseSearchQueryBuilderDi
when(mySqlObjectFactory.stringIndexTable(any())).thenReturn(new StringPredicateBuilder(searchQueryBuilder));
BaseJoiningPredicateBuilder firstPredicateBuilder = searchQueryBuilder.getOrCreateFirstPredicateBuilder();
StringPredicateBuilder sortPredicateBuilder = searchQueryBuilder.addStringPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
StringPredicateBuilder sortPredicateBuilder = searchQueryBuilder.addStringPredicateBuilder(firstPredicateBuilder.getJoinColumns());
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate("patient", "family");
searchQueryBuilder.addPredicate(hashIdentityPredicate);
@ -112,7 +112,7 @@ public class SearchQueryBuilderDialectMySqlTest extends BaseSearchQueryBuilderDi
when(mySqlObjectFactory.dateIndexTable(any())).thenReturn(new DatePredicateBuilder(searchQueryBuilder));
BaseJoiningPredicateBuilder firstPredicateBuilder = searchQueryBuilder.getOrCreateFirstPredicateBuilder();
DatePredicateBuilder sortPredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
DatePredicateBuilder sortPredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(firstPredicateBuilder.getJoinColumns());
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate("patient", "birthdate");
searchQueryBuilder.addPredicate(hashIdentityPredicate);