Compare commits
No commits in common. "75b894e9b4b8f6697c5beb3acb0f5189eba176a5" and "785f218382f6dae34a5c9116909d2d590f04d949" have entirely different histories.
75b894e9b4
...
785f218382
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
type: add
|
|
||||||
issue: 6325
|
|
||||||
title: "Add support for including the partitioning column in search query joins."
|
|
|
@ -55,7 +55,6 @@ 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.TagPredicateBuilder;
|
||||||
import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
|
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.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.PredicateBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
@ -360,7 +359,7 @@ public class QueryStack {
|
||||||
case STRING:
|
case STRING:
|
||||||
StringPredicateBuilder stringPredicateBuilder = mySqlBuilder.createStringPredicateBuilder();
|
StringPredicateBuilder stringPredicateBuilder = mySqlBuilder.createStringPredicateBuilder();
|
||||||
addSortCustomJoin(
|
addSortCustomJoin(
|
||||||
resourceLinkPredicateBuilder.getJoinColumnsForTarget(),
|
resourceLinkPredicateBuilder.getColumnTargetResourceId(),
|
||||||
stringPredicateBuilder,
|
stringPredicateBuilder,
|
||||||
stringPredicateBuilder.createHashIdentityPredicate(targetType, theChain));
|
stringPredicateBuilder.createHashIdentityPredicate(targetType, theChain));
|
||||||
|
|
||||||
|
@ -371,7 +370,7 @@ public class QueryStack {
|
||||||
case TOKEN:
|
case TOKEN:
|
||||||
TokenPredicateBuilder tokenPredicateBuilder = mySqlBuilder.createTokenPredicateBuilder();
|
TokenPredicateBuilder tokenPredicateBuilder = mySqlBuilder.createTokenPredicateBuilder();
|
||||||
addSortCustomJoin(
|
addSortCustomJoin(
|
||||||
resourceLinkPredicateBuilder.getJoinColumnsForTarget(),
|
resourceLinkPredicateBuilder.getColumnTargetResourceId(),
|
||||||
tokenPredicateBuilder,
|
tokenPredicateBuilder,
|
||||||
tokenPredicateBuilder.createHashIdentityPredicate(targetType, theChain));
|
tokenPredicateBuilder.createHashIdentityPredicate(targetType, theChain));
|
||||||
|
|
||||||
|
@ -382,7 +381,7 @@ public class QueryStack {
|
||||||
case DATE:
|
case DATE:
|
||||||
DatePredicateBuilder datePredicateBuilder = mySqlBuilder.createDatePredicateBuilder();
|
DatePredicateBuilder datePredicateBuilder = mySqlBuilder.createDatePredicateBuilder();
|
||||||
addSortCustomJoin(
|
addSortCustomJoin(
|
||||||
resourceLinkPredicateBuilder.getJoinColumnsForTarget(),
|
resourceLinkPredicateBuilder.getColumnTargetResourceId(),
|
||||||
datePredicateBuilder,
|
datePredicateBuilder,
|
||||||
datePredicateBuilder.createHashIdentityPredicate(targetType, theChain));
|
datePredicateBuilder.createHashIdentityPredicate(targetType, theChain));
|
||||||
|
|
||||||
|
@ -476,16 +475,16 @@ public class QueryStack {
|
||||||
BaseJoiningPredicateBuilder theFromJoiningPredicateBuilder,
|
BaseJoiningPredicateBuilder theFromJoiningPredicateBuilder,
|
||||||
BaseJoiningPredicateBuilder theToJoiningPredicateBuilder,
|
BaseJoiningPredicateBuilder theToJoiningPredicateBuilder,
|
||||||
Condition theCondition) {
|
Condition theCondition) {
|
||||||
addSortCustomJoin(theFromJoiningPredicateBuilder.getJoinColumns(), theToJoiningPredicateBuilder, theCondition);
|
addSortCustomJoin(
|
||||||
|
theFromJoiningPredicateBuilder.getResourceIdColumn(), theToJoiningPredicateBuilder, theCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSortCustomJoin(
|
private void addSortCustomJoin(
|
||||||
DbColumn theFromDbColumn[],
|
DbColumn theFromDbColumn,
|
||||||
BaseJoiningPredicateBuilder theToJoiningPredicateBuilder,
|
BaseJoiningPredicateBuilder theToJoiningPredicateBuilder,
|
||||||
Condition theCondition) {
|
Condition theCondition) {
|
||||||
|
|
||||||
ComboCondition onCondition =
|
ComboCondition onCondition =
|
||||||
mySqlBuilder.createOnCondition(theFromDbColumn, theToJoiningPredicateBuilder.getJoinColumns());
|
mySqlBuilder.createOnCondition(theFromDbColumn, theToJoiningPredicateBuilder.getResourceIdColumn());
|
||||||
|
|
||||||
if (theCondition != null) {
|
if (theCondition != null) {
|
||||||
onCondition.addCondition(theCondition);
|
onCondition.addCondition(theCondition);
|
||||||
|
@ -493,7 +492,7 @@ public class QueryStack {
|
||||||
|
|
||||||
mySqlBuilder.addCustomJoin(
|
mySqlBuilder.addCustomJoin(
|
||||||
SelectQuery.JoinType.LEFT_OUTER,
|
SelectQuery.JoinType.LEFT_OUTER,
|
||||||
theFromDbColumn[0].getTable(),
|
theFromDbColumn.getTable(),
|
||||||
theToJoiningPredicateBuilder.getTable(),
|
theToJoiningPredicateBuilder.getTable(),
|
||||||
onCondition);
|
onCondition);
|
||||||
}
|
}
|
||||||
|
@ -1477,12 +1476,12 @@ public class QueryStack {
|
||||||
|
|
||||||
public void addGrouping() {
|
public void addGrouping() {
|
||||||
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||||
mySqlBuilder.getSelect().addGroupings(firstPredicateBuilder.getJoinColumns());
|
mySqlBuilder.getSelect().addGroupings(firstPredicateBuilder.getResourceIdColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOrdering() {
|
public void addOrdering() {
|
||||||
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||||
mySqlBuilder.getSelect().addOrderings(firstPredicateBuilder.getJoinColumns());
|
mySqlBuilder.getSelect().addOrderings(firstPredicateBuilder.getResourceIdColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Condition createPredicateReferenceForEmbeddedChainedSearchResource(
|
public Condition createPredicateReferenceForEmbeddedChainedSearchResource(
|
||||||
|
@ -1530,7 +1529,7 @@ public class QueryStack {
|
||||||
for (LeafNodeDefinition leafNodeDefinition : referenceLinks.get(nextReferenceLink)) {
|
for (LeafNodeDefinition leafNodeDefinition : referenceLinks.get(nextReferenceLink)) {
|
||||||
SearchQueryBuilder builder;
|
SearchQueryBuilder builder;
|
||||||
if (wantChainedAndNormal) {
|
if (wantChainedAndNormal) {
|
||||||
builder = mySqlBuilder.newChildSqlBuilder(mySqlBuilder.isIncludePartitionIdInJoins());
|
builder = mySqlBuilder.newChildSqlBuilder();
|
||||||
} else {
|
} else {
|
||||||
builder = mySqlBuilder;
|
builder = mySqlBuilder;
|
||||||
}
|
}
|
||||||
|
@ -1575,15 +1574,8 @@ public class QueryStack {
|
||||||
if (wantChainedAndNormal) {
|
if (wantChainedAndNormal) {
|
||||||
|
|
||||||
if (theSourceJoinColumn == null) {
|
if (theSourceJoinColumn == null) {
|
||||||
BaseJoiningPredicateBuilder root = mySqlBuilder.getOrCreateFirstPredicateBuilder(false);
|
retVal = new InCondition(
|
||||||
DbColumn[] joinColumns = root.getJoinColumns();
|
mySqlBuilder.getOrCreateFirstPredicateBuilder(false).getResourceIdColumn(), union);
|
||||||
Object joinColumnObject;
|
|
||||||
if (joinColumns.length == 1) {
|
|
||||||
joinColumnObject = joinColumns[0];
|
|
||||||
} else {
|
|
||||||
joinColumnObject = ColumnTupleObject.from(joinColumns);
|
|
||||||
}
|
|
||||||
retVal = new InCondition(joinColumnObject, union);
|
|
||||||
} else {
|
} else {
|
||||||
// -- for the resource link, need join with target_resource_id
|
// -- for the resource link, need join with target_resource_id
|
||||||
retVal = new InCondition(theSourceJoinColumn, union);
|
retVal = new InCondition(theSourceJoinColumn, union);
|
||||||
|
@ -2058,8 +2050,7 @@ public class QueryStack {
|
||||||
BaseJoiningPredicateBuilder join;
|
BaseJoiningPredicateBuilder join;
|
||||||
if (paramInverted) {
|
if (paramInverted) {
|
||||||
|
|
||||||
boolean selectPartitionId = myPartitionSettings.isPartitionIdsInPrimaryKeys();
|
SearchQueryBuilder sqlBuilder = mySqlBuilder.newChildSqlBuilder();
|
||||||
SearchQueryBuilder sqlBuilder = mySqlBuilder.newChildSqlBuilder(selectPartitionId);
|
|
||||||
TagPredicateBuilder tagSelector = sqlBuilder.addTagPredicateBuilder(null);
|
TagPredicateBuilder tagSelector = sqlBuilder.addTagPredicateBuilder(null);
|
||||||
sqlBuilder.addPredicate(
|
sqlBuilder.addPredicate(
|
||||||
tagSelector.createPredicateTag(tagType, tokens, theParamName, theRequestPartitionId));
|
tagSelector.createPredicateTag(tagType, tokens, theParamName, theRequestPartitionId));
|
||||||
|
@ -2067,14 +2058,7 @@ public class QueryStack {
|
||||||
|
|
||||||
join = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
join = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||||
Expression subSelect = new Subquery(sql);
|
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 {
|
} else {
|
||||||
// Tag table can't be a query root because it will include deleted resources, and can't select by
|
// Tag table can't be a query root because it will include deleted resources, and can't select by
|
||||||
|
@ -2237,8 +2221,7 @@ public class QueryStack {
|
||||||
BaseJoiningPredicateBuilder join;
|
BaseJoiningPredicateBuilder join;
|
||||||
|
|
||||||
if (paramInverted) {
|
if (paramInverted) {
|
||||||
boolean selectPartitionId = myPartitionSettings.isPartitionIdsInPrimaryKeys();
|
SearchQueryBuilder sqlBuilder = theSqlBuilder.newChildSqlBuilder();
|
||||||
SearchQueryBuilder sqlBuilder = theSqlBuilder.newChildSqlBuilder(selectPartitionId);
|
|
||||||
TokenPredicateBuilder tokenSelector = sqlBuilder.addTokenPredicateBuilder(null);
|
TokenPredicateBuilder tokenSelector = sqlBuilder.addTokenPredicateBuilder(null);
|
||||||
sqlBuilder.addPredicate(tokenSelector.createPredicateToken(
|
sqlBuilder.addPredicate(tokenSelector.createPredicateToken(
|
||||||
tokens, theResourceName, theSpnamePrefix, theSearchParam, theRequestPartitionId));
|
tokens, theResourceName, theSpnamePrefix, theSearchParam, theRequestPartitionId));
|
||||||
|
@ -2247,16 +2230,13 @@ public class QueryStack {
|
||||||
|
|
||||||
join = theSqlBuilder.getOrCreateFirstPredicateBuilder();
|
join = theSqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||||
|
|
||||||
DbColumn[] leftColumns;
|
|
||||||
if (theSourceJoinColumn == null) {
|
if (theSourceJoinColumn == null) {
|
||||||
leftColumns = join.getJoinColumns();
|
predicate = new InCondition(join.getResourceIdColumn(), subSelect).setNegate(true);
|
||||||
} else {
|
} else {
|
||||||
leftColumns = theSourceJoinColumn;
|
// -- for the resource link, need join with target_resource_id
|
||||||
|
predicate = new InCondition(theSourceJoinColumn, subSelect).setNegate(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object left = new ColumnTupleObject(leftColumns);
|
|
||||||
predicate = new InCondition(left, subSelect).setNegate(true);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Boolean isMissing = theList.get(0).getMissing();
|
Boolean isMissing = theList.get(0).getMissing();
|
||||||
if (isMissing != null) {
|
if (isMissing != null) {
|
||||||
|
|
|
@ -55,9 +55,8 @@ import com.healthmarketscience.sqlbuilder.FunctionCall;
|
||||||
import com.healthmarketscience.sqlbuilder.InCondition;
|
import com.healthmarketscience.sqlbuilder.InCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.OrderObject;
|
import com.healthmarketscience.sqlbuilder.OrderObject;
|
||||||
import com.healthmarketscience.sqlbuilder.SelectQuery;
|
import com.healthmarketscience.sqlbuilder.SelectQuery;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.Join;
|
import com.healthmarketscience.sqlbuilder.UnaryCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbJoin;
|
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
|
||||||
|
@ -75,6 +74,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -102,7 +102,6 @@ public class SearchQueryBuilder {
|
||||||
private final SqlObjectFactory mySqlBuilderFactory;
|
private final SqlObjectFactory mySqlBuilderFactory;
|
||||||
private final boolean myCountQuery;
|
private final boolean myCountQuery;
|
||||||
private final Dialect myDialect;
|
private final Dialect myDialect;
|
||||||
private final boolean mySelectPartitionId;
|
|
||||||
private boolean myMatchNothing;
|
private boolean myMatchNothing;
|
||||||
private ResourceTablePredicateBuilder myResourceTableRoot;
|
private ResourceTablePredicateBuilder myResourceTableRoot;
|
||||||
private boolean myHaveAtLeastOnePredicate;
|
private boolean myHaveAtLeastOnePredicate;
|
||||||
|
@ -136,8 +135,7 @@ public class SearchQueryBuilder {
|
||||||
UUID.randomUUID() + "-",
|
UUID.randomUUID() + "-",
|
||||||
theDialectProvider.getDialect(),
|
theDialectProvider.getDialect(),
|
||||||
theCountQuery,
|
theCountQuery,
|
||||||
new ArrayList<>(),
|
new ArrayList<>());
|
||||||
thePartitionSettings.isPartitionIdsInPrimaryKeys());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,8 +151,7 @@ public class SearchQueryBuilder {
|
||||||
String theBindVariableSubstitutionBase,
|
String theBindVariableSubstitutionBase,
|
||||||
Dialect theDialect,
|
Dialect theDialect,
|
||||||
boolean theCountQuery,
|
boolean theCountQuery,
|
||||||
ArrayList<Object> theBindVariableValues,
|
ArrayList<Object> theBindVariableValues) {
|
||||||
boolean theSelectPartitionId) {
|
|
||||||
myFhirContext = theFhirContext;
|
myFhirContext = theFhirContext;
|
||||||
myStorageSettings = theStorageSettings;
|
myStorageSettings = theStorageSettings;
|
||||||
myPartitionSettings = thePartitionSettings;
|
myPartitionSettings = thePartitionSettings;
|
||||||
|
@ -176,7 +173,6 @@ public class SearchQueryBuilder {
|
||||||
|
|
||||||
myBindVariableSubstitutionBase = theBindVariableSubstitutionBase;
|
myBindVariableSubstitutionBase = theBindVariableSubstitutionBase;
|
||||||
myBindVariableValues = theBindVariableValues;
|
myBindVariableValues = theBindVariableValues;
|
||||||
mySelectPartitionId = theSelectPartitionId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FhirContext getFhirContext() {
|
public FhirContext getFhirContext() {
|
||||||
|
@ -363,11 +359,10 @@ public class SearchQueryBuilder {
|
||||||
mySelect.addCustomJoin(theJoinType, theFromTable, theToTable, theCondition);
|
mySelect.addCustomJoin(theJoinType, theFromTable, theToTable, theCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComboCondition createOnCondition(DbColumn[] theSourceColumn, DbColumn[] theTargetColumn) {
|
public ComboCondition createOnCondition(DbColumn theSourceColumn, DbColumn theTargetColumn) {
|
||||||
ComboCondition onCondition = ComboCondition.and();
|
ComboCondition onCondition = ComboCondition.and();
|
||||||
for (int i = 0; i < theSourceColumn.length; i += 1) {
|
onCondition.addCondition(BinaryCondition.equalTo(theSourceColumn, theTargetColumn));
|
||||||
onCondition.addCondition(BinaryCondition.equalTo(theSourceColumn[0], theTargetColumn[0]));
|
|
||||||
}
|
|
||||||
return onCondition;
|
return onCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,14 +434,8 @@ public class SearchQueryBuilder {
|
||||||
mySelect.addCustomColumns(
|
mySelect.addCustomColumns(
|
||||||
FunctionCall.count().setIsDistinct(true).addColumnParams(root.getResourceIdColumn()));
|
FunctionCall.count().setIsDistinct(true).addColumnParams(root.getResourceIdColumn()));
|
||||||
} else {
|
} else {
|
||||||
if (mySelectPartitionId) {
|
mySelectedResourceIdColumn = root.getResourceIdColumn();
|
||||||
mySelectedResourceIdColumn = root.getResourceIdColumn();
|
mySelect.addColumns(mySelectedResourceIdColumn);
|
||||||
mySelectedPartitionIdColumn = root.getPartitionIdColumn();
|
|
||||||
mySelect.addColumns(mySelectedPartitionIdColumn, mySelectedResourceIdColumn);
|
|
||||||
} else {
|
|
||||||
mySelectedResourceIdColumn = root.getResourceIdColumn();
|
|
||||||
mySelect.addColumns(mySelectedResourceIdColumn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mySelect.addFromTable(root.getTable());
|
mySelect.addFromTable(root.getTable());
|
||||||
myFirstPredicateBuilder = root;
|
myFirstPredicateBuilder = root;
|
||||||
|
@ -471,9 +460,6 @@ public class SearchQueryBuilder {
|
||||||
return toJoinColumns(partitionIdColumn, resourceIdColumn);
|
return toJoinColumns(partitionIdColumn, resourceIdColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove or keep partition_id columns depending on settings.
|
|
||||||
*/
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public DbColumn[] toJoinColumns(DbColumn partitionIdColumn, DbColumn resourceIdColumn) {
|
public DbColumn[] toJoinColumns(DbColumn partitionIdColumn, DbColumn resourceIdColumn) {
|
||||||
if (isIncludePartitionIdInJoins()) {
|
if (isIncludePartitionIdInJoins()) {
|
||||||
|
@ -484,7 +470,7 @@ public class SearchQueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIncludePartitionIdInJoins() {
|
public boolean isIncludePartitionIdInJoins() {
|
||||||
return mySelectPartitionId && myPartitionSettings.isPartitionIdsInPrimaryKeys();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn[] theFromColumn, DbColumn[] theToColumn) {
|
public void addJoin(DbTable theFromTable, DbTable theToTable, DbColumn[] theFromColumn, DbColumn[] theToColumn) {
|
||||||
|
@ -498,8 +484,29 @@ public class SearchQueryBuilder {
|
||||||
DbColumn[] theToColumn,
|
DbColumn[] theToColumn,
|
||||||
SelectQuery.JoinType theJoinType) {
|
SelectQuery.JoinType theJoinType) {
|
||||||
assert theFromColumn.length == theToColumn.length;
|
assert theFromColumn.length == theToColumn.length;
|
||||||
Join join = new DbJoin(mySpec, theFromTable, theToTable, theFromColumn, theToColumn);
|
assert theFromColumn.length > 0;
|
||||||
mySelect.addJoins(theJoinType, join);
|
// create custom join condition, allowing nullable columns.
|
||||||
|
var onCondition = ComboCondition.and();
|
||||||
|
for (int i = 0; i < theFromColumn.length; ++i) {
|
||||||
|
boolean isNullable =
|
||||||
|
theFromColumn[i].getColumnNameSQL().toLowerCase(Locale.ROOT).contains("partition_id");
|
||||||
|
onCondition.addCondition(buildJoinColumnCondition(isNullable, theFromColumn[i], theToColumn[i]));
|
||||||
|
}
|
||||||
|
mySelect.addCustomJoin(theJoinType, theFromTable, theToTable, onCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nonnull Condition buildJoinColumnCondition(
|
||||||
|
boolean theColumnNullability, DbColumn theFromColumn, DbColumn theToColumn) {
|
||||||
|
var normalEqualCondition = BinaryCondition.equalTo(theFromColumn, theToColumn);
|
||||||
|
if (!theColumnNullability) {
|
||||||
|
return normalEqualCondition;
|
||||||
|
} else {
|
||||||
|
// the column can be null
|
||||||
|
// we must combine raw = with IS NULL checks.
|
||||||
|
var orBothNullCondition =
|
||||||
|
ComboCondition.and(UnaryCondition.isNull(theFromColumn), UnaryCondition.isNull(theToColumn));
|
||||||
|
return ComboCondition.or(normalEqualCondition, orBothNullCondition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -835,7 +842,7 @@ public class SearchQueryBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchQueryBuilder newChildSqlBuilder(boolean theSelectPartitionId) {
|
public SearchQueryBuilder newChildSqlBuilder() {
|
||||||
return new SearchQueryBuilder(
|
return new SearchQueryBuilder(
|
||||||
myFhirContext,
|
myFhirContext,
|
||||||
myStorageSettings,
|
myStorageSettings,
|
||||||
|
@ -846,8 +853,7 @@ public class SearchQueryBuilder {
|
||||||
myBindVariableSubstitutionBase,
|
myBindVariableSubstitutionBase,
|
||||||
myDialect,
|
myDialect,
|
||||||
false,
|
false,
|
||||||
myBindVariableValues,
|
myBindVariableValues);
|
||||||
theSelectPartitionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SelectQuery getSelect() {
|
public SelectQuery getSelect() {
|
||||||
|
|
|
@ -107,7 +107,6 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
|
||||||
* is managed by Spring has been started before this method is called.
|
* is managed by Spring has been started before this method is called.
|
||||||
*/
|
*/
|
||||||
HapiTransactionService.requireTransaction();
|
HapiTransactionService.requireTransaction();
|
||||||
ourLog.trace("About to execute SQL: {}. Parameters: {}", sql, Arrays.toString(args));
|
|
||||||
|
|
||||||
Query nativeQuery = myEntityManager.createNativeQuery(sql);
|
Query nativeQuery = myEntityManager.createNativeQuery(sql);
|
||||||
org.hibernate.query.Query<?> hibernateQuery = (org.hibernate.query.Query<?>) nativeQuery;
|
org.hibernate.query.Query<?> hibernateQuery = (org.hibernate.query.Query<?>) nativeQuery;
|
||||||
|
@ -115,6 +114,8 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
|
||||||
hibernateQuery.setParameter(i, args[i - 1]);
|
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
|
* These settings help to ensure that we use a search cursor
|
||||||
* as opposed to loading all search results into memory
|
* as opposed to loading all search results into memory
|
||||||
|
@ -145,20 +146,13 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
|
||||||
myNext = NO_MORE;
|
myNext = NO_MORE;
|
||||||
} else {
|
} else {
|
||||||
Object nextRow = Objects.requireNonNull(myResultSet.next());
|
Object nextRow = Objects.requireNonNull(myResultSet.next());
|
||||||
// We should typically get two columns back, the first is the partition ID and the second
|
Number next;
|
||||||
// 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) {
|
if (nextRow instanceof Number) {
|
||||||
myNext = ((Number) nextRow).longValue();
|
next = (Number) nextRow;
|
||||||
} else {
|
} else {
|
||||||
Object[] nextRowAsArray = (Object[]) nextRow;
|
next = (Number) ((Object[]) nextRow)[0];
|
||||||
if (nextRowAsArray.length == 1) {
|
|
||||||
myNext = (Long) nextRowAsArray[0];
|
|
||||||
} else {
|
|
||||||
Integer nextPartitionId = (Integer) nextRowAsArray[0];
|
|
||||||
myNext = (Long) nextRowAsArray[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
myNext = next.longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -33,27 +33,8 @@ public class PartitionSettings {
|
||||||
private Integer myDefaultPartitionId;
|
private Integer myDefaultPartitionId;
|
||||||
private boolean myAlwaysOpenNewTransactionForDifferentPartition;
|
private boolean myAlwaysOpenNewTransactionForDifferentPartition;
|
||||||
private boolean myConditionalCreateDuplicateIdentifiersEnabled = false;
|
private boolean myConditionalCreateDuplicateIdentifiersEnabled = false;
|
||||||
private boolean myPartitionIdsInPrimaryKeys = false;
|
|
||||||
|
|
||||||
public PartitionSettings() {}
|
public PartitionSettings() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the table partition column (usually PARTITION_ID)
|
|
||||||
* participating in primary and foreign keys.
|
|
||||||
* Affects sql joins, sql in() expressions, etc.
|
|
||||||
*/
|
|
||||||
public boolean isPartitionIdsInPrimaryKeys() {
|
|
||||||
return myPartitionIdsInPrimaryKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the query engine if the primary and foreign keys
|
|
||||||
* of the partitioned tables include the partition column.
|
|
||||||
*/
|
|
||||||
public void setPartitionIdsInPrimaryKeys(boolean thePartitionIdsInPrimaryKeys) {
|
|
||||||
myPartitionIdsInPrimaryKeys = thePartitionIdsInPrimaryKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should we always open a new database transaction if the partition context changes
|
* Should we always open a new database transaction if the partition context changes
|
||||||
*
|
*
|
||||||
|
|
|
@ -4557,7 +4557,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
assertThat(patients).as(patients.toString()).containsExactly(obsId1);
|
assertThat(patients).as(patients.toString()).containsExactly(obsId1);
|
||||||
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||||
ourLog.info("Search query:\n{}", searchQuery);
|
ourLog.info("Search query:\n{}", searchQuery);
|
||||||
assertThat(countMatches(searchQuery.toLowerCase(), "partition")).as(searchQuery).isEqualTo(0);
|
assertThat(countMatches(searchQuery.toLowerCase(), "partition")).as(searchQuery).isEqualTo(0 /* criteria */ + 8 /* joins */);
|
||||||
assertThat(countMatches(searchQuery.toLowerCase(), "join")).as(searchQuery).isEqualTo(2);
|
assertThat(countMatches(searchQuery.toLowerCase(), "join")).as(searchQuery).isEqualTo(2);
|
||||||
assertThat(countMatches(searchQuery.toLowerCase(), "hash_identity")).as(searchQuery).isEqualTo(2);
|
assertThat(countMatches(searchQuery.toLowerCase(), "hash_identity")).as(searchQuery).isEqualTo(2);
|
||||||
// - query is changed 'or' is removed
|
// - query is changed 'or' is removed
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.SortSpec;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
@ -17,11 +17,9 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
|
@ -54,18 +52,6 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSortJoinIncludesPartitionId() {
|
|
||||||
|
|
||||||
myCaptureQueriesListener.clear();
|
|
||||||
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_ACTIVE, new TokenParam("true"));
|
|
||||||
map.setSort(new SortSpec(Patient.SP_NAME));
|
|
||||||
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_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", sql);
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Two regular search params - Should use HFJ_RESOURCE as root
|
* Two regular search params - Should use HFJ_RESOURCE as root
|
||||||
*/
|
*/
|
||||||
|
@ -79,7 +65,7 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
myPatientDao.search(map);
|
myPatientDao.search(map);
|
||||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
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);
|
assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (((t1.PARTITION_ID = t0.PARTITION_ID) OR ((t1.PARTITION_ID IS NULL) AND (t0.PARTITION_ID IS NULL))) AND (t1.RES_ID = t0.RES_ID)) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (((t1.PARTITION_ID = t2.PARTITION_ID) OR ((t1.PARTITION_ID IS NULL) AND (t2.PARTITION_ID IS NULL))) AND (t1.RES_ID = t2.RES_ID)) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_SYS_AND_VALUE = ?))", sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -100,7 +86,7 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||||
assertEquals(3, myCaptureQueriesListener.countSelectQueries());
|
assertEquals(3, myCaptureQueriesListener.countSelectQueries());
|
||||||
// Query 1 - Find resources: Make sure we search for tag type+system+code always
|
// Query 1 - Find resources: Make sure we search for tag type+system+code always
|
||||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||||
assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 INNER JOIN HFJ_RES_TAG t1 ON (t0.RES_ID = t1.RES_ID) INNER JOIN HFJ_TAG_DEF t2 ON (t1.TAG_ID = t2.TAG_ID) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t2.TAG_TYPE = ?) AND (t2.TAG_SYSTEM = ?) AND (t2.TAG_CODE = ?)))", sql);
|
assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 INNER JOIN HFJ_RES_TAG t1 ON (((t0.PARTITION_ID = t1.PARTITION_ID) OR ((t0.PARTITION_ID IS NULL) AND (t1.PARTITION_ID IS NULL))) AND (t0.RES_ID = t1.RES_ID)) INNER JOIN HFJ_TAG_DEF t2 ON (t1.TAG_ID = t2.TAG_ID) WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t2.TAG_TYPE = ?) AND (t2.TAG_SYSTEM = ?) AND (t2.TAG_CODE = ?)))", sql);
|
||||||
// Query 2 - Load resourece contents
|
// Query 2 - Load resourece contents
|
||||||
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(false, false);
|
sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(false, false);
|
||||||
assertThat(sql).contains("where rsv1_0.RES_ID in (?)");
|
assertThat(sql).contains("where rsv1_0.RES_ID in (?)");
|
||||||
|
|
|
@ -1327,7 +1327,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
||||||
|
|
||||||
// Only the read columns should be used, no criteria use partition
|
// Only the read columns should be used, no criteria use partition
|
||||||
assertThat(searchSql).as(searchSql).contains("PARTITION_ID = '1'");
|
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(2 /* criteria */ + 4 /* joins */); // If this switches to 1 that would be fine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read in null Partition
|
// Read in null Partition
|
||||||
|
@ -1443,7 +1443,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
||||||
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase();
|
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase();
|
||||||
ourLog.info("Search SQL:\n{}", searchSql);
|
ourLog.info("Search SQL:\n{}", searchSql);
|
||||||
assertThat(searchSql).as(searchSql).contains("PARTITION_ID = '1'");
|
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(2 /* criteria */ + 4 /* joins */); // If this switches to 1 that would be fine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read in null Partition
|
// Read in null Partition
|
||||||
|
@ -2383,7 +2383,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
||||||
|
|
||||||
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||||
ourLog.info("Search SQL:\n{}", searchSql);
|
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(0 /* criteria */ + 4 /* joins */);
|
||||||
assertThat(StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'")).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);
|
assertThat(StringUtils.countMatches(searchSql, ".HASH_SYS_AND_VALUE =")).as(searchSql).isEqualTo(1);
|
||||||
|
|
||||||
|
@ -2457,7 +2457,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
||||||
|
|
||||||
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||||
ourLog.info("Search SQL:\n{}", searchSql);
|
ourLog.info("Search SQL:\n{}", searchSql);
|
||||||
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
assertEquals(0 /* criteria */ + 4 /* joins */, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
||||||
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
|
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2483,7 +2483,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
||||||
ourLog.info("Search SQL:\n{}", searchSql);
|
ourLog.info("Search SQL:\n{}", searchSql);
|
||||||
|
|
||||||
assertEquals(2, StringUtils.countMatches(searchSql, "JOIN"));
|
assertEquals(2, StringUtils.countMatches(searchSql, "JOIN"));
|
||||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
assertEquals(1 /* criteria */ + 4 /* joins */, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
||||||
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
|
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM = 'http://system'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue