Bump HIbernate to 5.6.x (#3305)

* Bump HIbernate to 5.6.x

* Test fixes

* Test fix

* Dependency bump

* Rollback change

* Fix tests

* One more fix

* Avoid bump
This commit is contained in:
James Agnew 2022-01-19 11:14:14 -05:00 committed by GitHub
parent 0f0bf85f18
commit 80e4c3dd92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 340 additions and 66 deletions

View File

@ -5,16 +5,17 @@
(dependent HAPI modules listed in brackets): (dependent HAPI modules listed in brackets):
<ul> <ul>
<li>log4j-api (JPA): 2.11.1 -> 2.17.1 (Addresses CVE-2021-44228 - HAPI FHIR was not vulnerable to this issue but this upgrade avoids unnecessary OWASP scan notices)</li> <li>log4j-api (JPA): 2.11.1 -> 2.17.1 (Addresses CVE-2021-44228 - HAPI FHIR was not vulnerable to this issue but this upgrade avoids unnecessary OWASP scan notices)</li>
<li>SLF4j (All): 1.7.30 -> 1.7.32</li> <li>SLF4j (All): 1.7.30 -> 1.7.33</li>
<li>Logback (All): 1.2.8 -> 1.2.10</li> <li>Logback (All): 1.2.8 -> 1.2.10</li>
<li>Commons-IO (All): 2.8.0 -> 2.11.0</li> <li>Commons-IO (All): 2.8.0 -> 2.11.0</li>
<li>Jackson (All): 2.13.0 -> 2.13.1</li> <li>Jackson (All): 2.13.0 -> 2.13.1</li>
<li>Guava (All): 30.1.1-jre -> 31.0.1-jre</li> <li>Guava (All): 30.1.1-jre -> 31.0.1-jre</li>
<li>JDOM (XML Patch Support): 2.0.6 -> 2.0.6.1 (Addresses CVE-2021-33813)</li> <li>JDOM (XML Patch Support): 2.0.6 -> 2.0.6.1 (Addresses CVE-2021-33813)</li>
<li>Spring (JPA): 5.3.7 -> 5.3.14</li> <li>Spring (JPA): 5.3.7 -> 5.3.15</li>
<li>Spring-Data (JPA): 2.5.0 -> 2.6.0</li> <li>Spring-Data (JPA): 2.5.0 -> 2.6.1</li>
<li>Hibernate ORM (JPA): 5.4.30.Final -> 5.4.33</li> <li>Hibernate ORM (JPA): 5.4.30.Final -> 5.6.2.Final</li>
<li>Flyway (JPA): 6.5.4 -> 8.3.0</li> <li>Flyway (JPA): 6.5.4 -> 8.4.1</li>
<li>Sqlbuilder (JPA): 3.0.1 -> 3.0.2</li>
<li>H2 (JPA): 1.4.200 -> 2.0.206 (Note that this change requires the use of the HapiFhirH2Dialect instead of the built-in Hibernate H2Dialect due to Hibernate issue <a href='https://hibernate.atlassian.net/browse/HHH-15002'>HHH-15002</a></li> <li>H2 (JPA): 1.4.200 -> 2.0.206 (Note that this change requires the use of the HapiFhirH2Dialect instead of the built-in Hibernate H2Dialect due to Hibernate issue <a href='https://hibernate.atlassian.net/browse/HHH-15002'>HHH-15002</a></li>
<li>Commons-DBCP2 (JPA): .8.0 -> .8.0 -> 2.9.0</li> <li>Commons-DBCP2 (JPA): .8.0 -> .8.0 -> 2.9.0</li>
<li>Swagger-Models (OpenAPI Support): 2.1.7 -> 2.1.12</li> <li>Swagger-Models (OpenAPI Support): 2.1.7 -> 2.1.12</li>

View File

@ -350,6 +350,25 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
case ANY: case ANY:
ForcedId forcedId = createForcedIdIfNeeded(updatedEntity, theResource.getIdElement(), true); ForcedId forcedId = createForcedIdIfNeeded(updatedEntity, theResource.getIdElement(), true);
if (forcedId != null) { if (forcedId != null) {
/*
* As of Hibernate 5.6.2, assigning the forced ID to the
* resource table causes an extra update to happen, even
* though the ResourceTable entity isn't actually changed
* (there is a @OneToOne reference on ResourceTable to the
* ForcedId table, but the actual column is on the ForcedId
* table so it doesn't actually make sense to update the table
* when this is set). But to work around that we clear this
* here.
*
* If you get rid of the following line (maybe possible
* in a future version of Hibernate) try running the tests
* in FhirResourceDaoR4QueryCountTest
* JA 20220126
*/
updatedEntity.setForcedId(null);
updatedEntity.setTransientForcedId(forcedId.getForcedId());
myForcedIdDao.save(forcedId); myForcedIdDao.save(forcedId);
} }
break; break;

View File

@ -74,6 +74,7 @@ import javax.annotation.Nullable;
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;
@ -408,7 +409,7 @@ public class SearchQueryBuilder {
// The SQLServerDialect has a bunch of one-off processing to deal with rules on when // The SQLServerDialect has a bunch of one-off processing to deal with rules on when
// a limit can be used, so we can't rely on the flags that the limithandler exposes since // a limit can be used, so we can't rely on the flags that the limithandler exposes since
// the exact structure of the query depends on the parameters // the exact structure of the query depends on the parameters
if (sql.contains("TOP(?)")) { if (sql.contains("top(?)")) {
bindVariables.add(0, maxResultsToFetch); bindVariables.add(0, maxResultsToFetch);
} }
if (sql.contains("offset 0 rows fetch next ? rows only")) { if (sql.contains("offset 0 rows fetch next ? rows only")) {
@ -418,7 +419,7 @@ public class SearchQueryBuilder {
bindVariables.add(theOffset); bindVariables.add(theOffset);
bindVariables.add(maxResultsToFetch); bindVariables.add(maxResultsToFetch);
} }
if (offset != null && sql.contains("__hibernate_row_nr__")) { if (offset != null && sql.contains("__row__")) {
bindVariables.add(theOffset + 1); bindVariables.add(theOffset + 1);
bindVariables.add(theOffset + maxResultsToFetch + 1); bindVariables.add(theOffset + maxResultsToFetch + 1);
} }

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails; import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.CoreMatchers.startsWith;
@ -155,10 +157,19 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true); myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY); myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
myCaptureQueriesListener.clear();
Observation o = new Observation(); Observation o = new Observation();
o.setStatus(ObservationStatus.FINAL); o.setStatus(ObservationStatus.FINAL);
IIdType id = myObservationDao.create(o, mySrd).getId(); IIdType id = myObservationDao.create(o, mySrd).getId();
myCaptureQueriesListener.logAllQueries();
runInTransaction(()->{
ResourceTable entity = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(()->new IllegalArgumentException());
assertEquals(1, entity.getVersion());
});
try { try {
myPatientDao.read(new IdType("Patient/999999999999999")); myPatientDao.read(new IdType("Patient/999999999999999"));
fail(); fail();
@ -167,11 +178,15 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
} }
o = new Observation(); o = new Observation();
o.setId(id); o.setId(id.getValue());
o.setStatus(ObservationStatus.FINAL); o.setStatus(ObservationStatus.FINAL);
o.getSubject().setReference("Patient/999999999999999"); o.getSubject().setReference("Patient/999999999999999");
myObservationDao.update(o, mySrd); myObservationDao.update(o, mySrd);
runInTransaction(()->{
ResourceTable entity = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(()->new IllegalArgumentException());
assertEquals(2, entity.getVersion());
});
myPatientDao.read(new IdType("Patient/999999999999999")); myPatientDao.read(new IdType("Patient/999999999999999"));

View File

@ -2,7 +2,9 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum; import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails; import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
@ -56,6 +58,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -77,6 +80,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode()); myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets()); myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets()); myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets());
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
} }
@Override @Override
@ -136,6 +140,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
} }
@Test @Test
public void testRead() { public void testRead() {
IIdType id = runInTransaction(() -> { IIdType id = runInTransaction(() -> {
@ -255,6 +260,129 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
assertEquals(4, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); assertEquals(4, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
myCaptureQueriesListener.logDeleteQueriesForCurrentThread(); myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
runInTransaction(() -> {
List<ResourceTable> resources = myResourceTableDao.findAll();
assertEquals(2, resources.size());
assertEquals(1, resources.get(0).getVersion());
assertEquals(1, resources.get(1).getVersion());
});
}
@Test
public void testCreateWithServerAssignedId_AnyClientAssignedIdStrategy() {
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
myCaptureQueriesListener.clear();
IIdType resourceId = runInTransaction(() -> {
Patient p = new Patient();
p.setUserData("ABAB", "ABAB");
p.getMaritalStatus().setText("123");
return myPatientDao.create(p).getId().toUnqualifiedVersionless();
});
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
runInTransaction(() -> {
List<ForcedId> allForcedIds = myForcedIdDao.findAll();
for (ForcedId next : allForcedIds) {
assertNotNull(next.getResourceId());
assertNotNull(next.getForcedId());
}
List<ResourceTable> resources = myResourceTableDao.findAll();
String versions = "Resource Versions:\n * " + resources
.stream()
.map(t->"Resource " + t.getIdDt() + " has version: " + t.getVersion())
.collect(Collectors.joining("\n * "));
for (ResourceTable next : resources) {
assertEquals(1, next.getVersion(), versions);
}
});
runInTransaction(() -> {
Patient patient = myPatientDao.read(resourceId, mySrd);
assertEquals(resourceId.getIdPart(), patient.getIdElement().getIdPart());
assertEquals("123", patient.getMaritalStatus().getText());
assertEquals("1", patient.getIdElement().getVersionIdPart());
});
}
@Test
public void testCreateWithClientAssignedId_AnyClientAssignedIdStrategy() {
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
runInTransaction(() -> {
Patient p = new Patient();
p.setUserData("ABAB", "ABAB");
p.getMaritalStatus().setText("123");
return myPatientDao.create(p).getId().toUnqualified();
});
runInTransaction(() -> {
Patient p = new Patient();
p.setId("BBB");
p.getMaritalStatus().setText("123");
myPatientDao.update(p);
});
myCaptureQueriesListener.clear();
runInTransaction(() -> {
Patient p = new Patient();
p.setId("AAA");
p.getMaritalStatus().setText("123");
myPatientDao.update(p);
});
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
runInTransaction(() -> {
List<ForcedId> allForcedIds = myForcedIdDao.findAll();
for (ForcedId next : allForcedIds) {
assertNotNull(next.getResourceId());
assertNotNull(next.getForcedId());
}
List<ResourceTable> resources = myResourceTableDao.findAll();
String versions = "Resource Versions:\n * " + resources
.stream()
.map(t->"Resource " + t.getIdDt() + " has version: " + t.getVersion())
.collect(Collectors.joining("\n * "));
for (ResourceTable next : resources) {
assertEquals(1, next.getVersion(), versions);
}
});
runInTransaction(() -> {
Patient patient = myPatientDao.read(new IdType("Patient/AAA"), mySrd);
assertEquals("AAA", patient.getIdElement().getIdPart());
assertEquals("123", patient.getMaritalStatus().getText());
assertEquals("1", patient.getIdElement().getVersionIdPart());
});
} }
@Test @Test

View File

@ -0,0 +1,71 @@
package ca.uhn.fhir.jpa.search.builder.sql;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.OrderObject;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
public abstract class BaseSearchQueryBuilderDialectTest {
private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchQueryBuilderDialectTest.class);
protected final FhirContext myFhirContext = FhirContext.forR4Cached();
@Mock
protected SqlObjectFactory mySqlObjectFactory;
@Mock
protected HibernatePropertiesProvider myHibernatePropertiesProvider;
@BeforeEach
public void beforeInitMocks() {
when(myHibernatePropertiesProvider.getDialect())
.thenReturn(createDialect());
}
@Nonnull
protected abstract Dialect createDialect();
protected SearchQueryBuilder createSearchQueryBuilder() {
return new SearchQueryBuilder(myFhirContext, new ModelConfig(), new PartitionSettings(), RequestPartitionId.allPartitions(), "Patient", mySqlObjectFactory, myHibernatePropertiesProvider, false);
}
protected GeneratedSql buildSqlWithNumericSort(Boolean theAscending, OrderObject.NullOrder theNullOrder) {
SearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder();
when(mySqlObjectFactory.resourceTable(any())).thenReturn(new ResourceTablePredicateBuilder(searchQueryBuilder));
when(mySqlObjectFactory.dateIndexTable(any())).thenReturn(new DatePredicateBuilder(searchQueryBuilder));
BaseJoiningPredicateBuilder firstPredicateBuilder = searchQueryBuilder.getOrCreateFirstPredicateBuilder();
DatePredicateBuilder sortPredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate("MolecularSequence", "variant-start");
searchQueryBuilder.addPredicate(hashIdentityPredicate);
if (theNullOrder == null) {
searchQueryBuilder.addSortNumeric(sortPredicateBuilder.getColumnValueLow(), theAscending);
} else {
searchQueryBuilder.addSortNumeric(sortPredicateBuilder.getColumnValueLow(), theAscending, theNullOrder);
}
return searchQueryBuilder.generate(0, 500);
}
public void logSql(GeneratedSql theGeneratedSql) {
String output = new BasicFormatterImpl().format(theGeneratedSql.getSql());
ourLog.info("SQL: {}", output);
}
}

View File

@ -1,43 +1,24 @@
package ca.uhn.fhir.jpa.search.builder.sql; package ca.uhn.fhir.jpa.search.builder.sql;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder; import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder; import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder; import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder; import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import com.healthmarketscience.sqlbuilder.Condition; import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.OrderObject; import com.healthmarketscience.sqlbuilder.OrderObject;
import org.junit.jupiter.api.BeforeEach; import org.hibernate.dialect.Dialect;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mock; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension;
import javax.annotation.Nonnull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class SearchQueryBuilderMySqlTest { @ExtendWith(MockitoExtension.class)
public class SearchQueryBuilderDialectMySqlTest extends BaseSearchQueryBuilderDialectTest {
@Mock
private SqlObjectFactory mySqlObjectFactory;
@Mock
private HibernatePropertiesProvider myHibernatePropertiesProvider;
private final FhirContext myFhirContext = FhirContext.forR4();
@BeforeEach
public void beforeInitMocks() {
MockitoAnnotations.initMocks(this);
when(myHibernatePropertiesProvider.getDialect()).thenReturn(new org.hibernate.dialect.MySQL57Dialect());
}
private SearchQueryBuilder createSearchQueryBuilder() {
return new SearchQueryBuilder(myFhirContext, new ModelConfig(), new PartitionSettings(), RequestPartitionId.allPartitions(), "Patient", mySqlObjectFactory, myHibernatePropertiesProvider, false);
}
@Test @Test
public void testAddSortNumericNoNullOrder() { public void testAddSortNumericNoNullOrder() {
@ -49,26 +30,6 @@ public class SearchQueryBuilderMySqlTest {
} }
private GeneratedSql buildSqlWithNumericSort(Boolean theAscending, OrderObject.NullOrder theNullOrder) {
SearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder();
when(mySqlObjectFactory.resourceTable(any())).thenReturn(new ResourceTablePredicateBuilder(searchQueryBuilder));
when(mySqlObjectFactory.dateIndexTable(any())).thenReturn(new DatePredicateBuilder(searchQueryBuilder));
BaseJoiningPredicateBuilder firstPredicateBuilder = searchQueryBuilder.getOrCreateFirstPredicateBuilder();
DatePredicateBuilder sortPredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate("MolecularSequence", "variant-start");
searchQueryBuilder.addPredicate(hashIdentityPredicate);
if (theNullOrder == null) {
searchQueryBuilder.addSortNumeric(sortPredicateBuilder.getColumnValueLow(), theAscending);
} else {
searchQueryBuilder.addSortNumeric(sortPredicateBuilder.getColumnValueLow(), theAscending, theNullOrder);
}
return searchQueryBuilder.generate(0,500);
}
@Test @Test
public void testAddSortNumericWithNullOrder() { public void testAddSortNumericWithNullOrder() {
GeneratedSql generatedSql = buildSqlWithNumericSort(true, OrderObject.NullOrder.FIRST); GeneratedSql generatedSql = buildSqlWithNumericSort(true, OrderObject.NullOrder.FIRST);
@ -182,4 +143,10 @@ public class SearchQueryBuilderMySqlTest {
assertTrue(generatedSql.getSql().endsWith("ORDER BY t1.SP_VALUE_LOW DESC limit ?")); assertTrue(generatedSql.getSql().endsWith("ORDER BY t1.SP_VALUE_LOW DESC limit ?"));
} }
@Nonnull
@Override
protected Dialect createDialect() {
return new org.hibernate.dialect.MySQL57Dialect();
}
} }

View File

@ -0,0 +1,69 @@
package ca.uhn.fhir.jpa.search.builder.sql;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.SQLServer2012Dialect;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.annotation.Nonnull;
import java.util.Locale;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class SearchQueryBuilderDialectSqlServerTest extends BaseSearchQueryBuilderDialectTest {
@Test
public void testAddSort() {
GeneratedSql generatedSql = buildSqlWithNumericSort(true, null);
logSql(generatedSql);
String sql = generatedSql.getSql();
assertTrue(sql.endsWith("ORDER BY -t1.SP_VALUE_LOW DESC offset 0 rows fetch next ? rows only"), sql);
assertEquals(3, StringUtils.countMatches(sql, "?"));
assertEquals(3, generatedSql.getBindVariables().size());
}
@Test
public void testRangeWithOffset() {
SearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder();
when(mySqlObjectFactory.resourceTable(any())).thenReturn(new ResourceTablePredicateBuilder(searchQueryBuilder));
GeneratedSql generatedSql = searchQueryBuilder.generate(10, 500);
logSql(generatedSql);
String sql = generatedSql.getSql();
assertTrue(sql.endsWith("select page0_ from query where __row__ >= ? and __row__ < ?"), sql);
assertEquals(3, StringUtils.countMatches(sql, "?"));
assertEquals(3, generatedSql.getBindVariables().size());
}
@Test
public void testRangeWithoutOffset() {
SearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder();
when(mySqlObjectFactory.resourceTable(any())).thenReturn(new ResourceTablePredicateBuilder(searchQueryBuilder));
GeneratedSql generatedSql = searchQueryBuilder.generate(0, 500);
logSql(generatedSql);
String sql = generatedSql.getSql();
assertTrue(sql.toUpperCase(Locale.ROOT).contains("SELECT TOP(?) T0.RES_ID FROM"), sql);
assertEquals(2, StringUtils.countMatches(sql, "?"));
assertEquals(2, generatedSql.getBindVariables().size());
}
@Nonnull
@Override
protected Dialect createDialect() {
return new SQLServer2012Dialect();
}
}

View File

@ -24,6 +24,8 @@ import org.springframework.context.annotation.Scope;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Locale;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -64,12 +66,12 @@ public class SearchQueryBuilderTest {
// Max only // Max only
generated = builder.generate(null, 10); generated = builder.generate(null, 10);
assertEquals("SELECT TOP(?) t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) ))", generated.getSql()); assertEquals("SELECT TOP(?) T0.RES_ID FROM HFJ_RESOURCE T0 WHERE (((T0.RES_TYPE = ?) AND (T0.RES_DELETED_AT IS NULL)) AND (T0.RES_ID IN (?,?) ))", generated.getSql().toUpperCase(Locale.ROOT));
assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(10, "Patient", 500L, 501L)); assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(10, "Patient", 500L, 501L));
// Range // Range
generated = builder.generate(10, 5); generated = builder.generate(10, 5);
assertEquals("WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( SELECT t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ) inner_query ) SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", generated.getSql()); assertEquals("with query as (select inner_query.*, row_number() over (order by current_timestamp) as __row__ from ( SELECT t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ) inner_query ) select page0_ from query where __row__ >= ? and __row__ < ?", generated.getSql());
assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains("Patient", 500L, 501L, 11, 16)); assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains("Patient", 500L, 501L, 11, 16));
} }
@ -93,13 +95,13 @@ public class SearchQueryBuilderTest {
// Max only // Max only
generated = builder.generate(null, 10); generated = builder.generate(null, 10);
// assertEquals("SELECT TOP(?) t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY CASE WHEN t0.RES_UPDATED IS NULL THEN 1 ELSE 0 END ASC, t0.RES_UPDATED ASC", generated.getSql()); // assertEquals("SELECT TOP(?) t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY CASE WHEN t0.RES_UPDATED IS NULL THEN 1 ELSE 0 END ASC, t0.RES_UPDATED ASC", generated.getSql());
assertEquals("SELECT TOP(?) t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY t0.RES_UPDATED ASC", generated.getSql()); assertEquals("SELECT TOP(?) T0.RES_ID FROM HFJ_RESOURCE T0 WHERE (((T0.RES_TYPE = ?) AND (T0.RES_DELETED_AT IS NULL)) AND (T0.RES_ID IN (?,?) )) ORDER BY T0.RES_UPDATED ASC", generated.getSql().toUpperCase(Locale.ROOT));
assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(10, "Patient", 500L, 501L)); assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(10, "Patient", 500L, 501L));
// Range // Range
generated = builder.generate(10, 5); generated = builder.generate(10, 5);
// assertEquals("WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( SELECT TOP(?) t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY CASE WHEN t0.RES_UPDATED IS NULL THEN 1 ELSE 0 END ASC, t0.RES_UPDATED ASC ) inner_query ) SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", generated.getSql()); // assertEquals("WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( SELECT TOP(?) t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY CASE WHEN t0.RES_UPDATED IS NULL THEN 1 ELSE 0 END ASC, t0.RES_UPDATED ASC ) inner_query ) SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", generated.getSql());
assertEquals("WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( SELECT TOP(?) t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY t0.RES_UPDATED ASC ) inner_query ) SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", generated.getSql()); assertEquals("with query as (select inner_query.*, row_number() over (order by current_timestamp) as __row__ from ( SELECT top(?) t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ORDER BY t0.RES_UPDATED ASC ) inner_query ) select page0_ from query where __row__ >= ? and __row__ < ?", generated.getSql());
assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(5, "Patient", 500L, 501L, 11, 16)); assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(5, "Patient", 500L, 501L, 11, 16));
} }
@ -121,12 +123,12 @@ public class SearchQueryBuilderTest {
// Max only // Max only
generated = builder.generate(null, 10); generated = builder.generate(null, 10);
assertEquals("SELECT TOP(?) t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) ))", generated.getSql()); assertEquals("SELECT TOP(?) T0.RES_ID FROM HFJ_RESOURCE T0 WHERE (((T0.RES_TYPE = ?) AND (T0.RES_DELETED_AT IS NULL)) AND (T0.RES_ID IN (?,?) ))", generated.getSql().toUpperCase(Locale.ROOT));
assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(10, "Patient", 500L, 501L)); assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains(10, "Patient", 500L, 501L));
// Range // Range
generated = builder.generate(10, 5); generated = builder.generate(10, 5);
assertEquals("WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( SELECT t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ) inner_query ) SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", generated.getSql()); assertEquals("with query as (select inner_query.*, row_number() over (order by current_timestamp) as __row__ from ( SELECT t0.RES_ID as page0_ FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND (t0.RES_ID IN (?,?) )) ) inner_query ) select page0_ from query where __row__ >= ? and __row__ < ?", generated.getSql());
assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains("Patient", 500L, 501L, 11, 16)); assertThat(generated.getBindVariables().toString(), generated.getBindVariables(), contains("Patient", 500L, 501L, 11, 16));
} }

View File

@ -270,6 +270,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
private transient ResourceHistoryTable myCurrentVersionEntity; private transient ResourceHistoryTable myCurrentVersionEntity;
@OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource") @OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource")
@OptimisticLock(excluded = true)
private ForcedId myForcedId; private ForcedId myForcedId;
@Transient @Transient

12
pom.xml
View File

@ -809,8 +809,8 @@
<jsr305_version>3.0.2</jsr305_version> <jsr305_version>3.0.2</jsr305_version>
<junit_version>5.8.2</junit_version> <junit_version>5.8.2</junit_version>
<flexmark_version>0.50.40</flexmark_version> <flexmark_version>0.50.40</flexmark_version>
<flyway_version>8.3.0</flyway_version> <flyway_version>8.4.1</flyway_version>
<hibernate_version>5.4.33</hibernate_version> <hibernate_version>5.6.2.Final</hibernate_version>
<hibernate_search_version>6.0.3.Final</hibernate_search_version> <hibernate_search_version>6.0.3.Final</hibernate_search_version>
<!-- Update lucene version when you update hibernate-search version --> <!-- Update lucene version when you update hibernate-search version -->
<lucene_version>8.7.0</lucene_version> <lucene_version>8.7.0</lucene_version>
@ -832,10 +832,10 @@
<servicemix_saxon_version>9.8.0-15</servicemix_saxon_version> <servicemix_saxon_version>9.8.0-15</servicemix_saxon_version>
<servicemix_xmlresolver_version>1.2_5</servicemix_xmlresolver_version> <servicemix_xmlresolver_version>1.2_5</servicemix_xmlresolver_version>
<swagger_version>2.1.12</swagger_version> <swagger_version>2.1.12</swagger_version>
<slf4j_version>1.7.32</slf4j_version> <slf4j_version>1.7.33</slf4j_version>
<log4j_to_slf4j_version>2.17.1</log4j_to_slf4j_version> <log4j_to_slf4j_version>2.17.1</log4j_to_slf4j_version>
<spring_version>5.3.14</spring_version> <spring_version>5.3.15</spring_version>
<spring_data_version>2.6.0</spring_data_version> <spring_data_version>2.6.1</spring_data_version>
<spring_batch_version>4.3.3</spring_batch_version> <spring_batch_version>4.3.3</spring_batch_version>
<spring_boot_version>2.6.2</spring_boot_version> <spring_boot_version>2.6.2</spring_boot_version>
<spring_retry_version>1.2.2.RELEASE</spring_retry_version> <spring_retry_version>1.2.2.RELEASE</spring_retry_version>
@ -1134,7 +1134,7 @@
<dependency> <dependency>
<groupId>com.healthmarketscience.sqlbuilder</groupId> <groupId>com.healthmarketscience.sqlbuilder</groupId>
<artifactId>sqlbuilder</artifactId> <artifactId>sqlbuilder</artifactId>
<version>3.0.1</version> <version>3.0.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.microsoft.sqlserver</groupId> <groupId>com.microsoft.sqlserver</groupId>