diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_0/changes.yaml
index 3ecfec73f71..15194940028 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_0/changes.yaml
@@ -5,16 +5,17 @@
(dependent HAPI modules listed in brackets):
- 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)
-- SLF4j (All): 1.7.30 -> 1.7.32
+- SLF4j (All): 1.7.30 -> 1.7.33
- Logback (All): 1.2.8 -> 1.2.10
- Commons-IO (All): 2.8.0 -> 2.11.0
- Jackson (All): 2.13.0 -> 2.13.1
- Guava (All): 30.1.1-jre -> 31.0.1-jre
- JDOM (XML Patch Support): 2.0.6 -> 2.0.6.1 (Addresses CVE-2021-33813)
-- Spring (JPA): 5.3.7 -> 5.3.14
-- Spring-Data (JPA): 2.5.0 -> 2.6.0
-- Hibernate ORM (JPA): 5.4.30.Final -> 5.4.33
-- Flyway (JPA): 6.5.4 -> 8.3.0
+- Spring (JPA): 5.3.7 -> 5.3.15
+- Spring-Data (JPA): 2.5.0 -> 2.6.1
+- Hibernate ORM (JPA): 5.4.30.Final -> 5.6.2.Final
+- Flyway (JPA): 6.5.4 -> 8.4.1
+- Sqlbuilder (JPA): 3.0.1 -> 3.0.2
- 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 HHH-15002
- Commons-DBCP2 (JPA): .8.0 -> .8.0 -> 2.9.0
- Swagger-Models (OpenAPI Support): 2.1.7 -> 2.1.12
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index 0aeb5eac1b1..04645db37bf 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -350,6 +350,25 @@ public abstract class BaseHapiFhirResourceDao extends B
case ANY:
ForcedId forcedId = createForcedIdIfNeeded(updatedEntity, theResource.getIdElement(), true);
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);
}
break;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java
index a56805e442f..7c45a1a83e2 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java
@@ -74,6 +74,7 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.UUID;
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
// 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
- if (sql.contains("TOP(?)")) {
+ if (sql.contains("top(?)")) {
bindVariables.add(0, maxResultsToFetch);
}
if (sql.contains("offset 0 rows fetch next ? rows only")) {
@@ -418,7 +419,7 @@ public class SearchQueryBuilder {
bindVariables.add(theOffset);
bindVariables.add(maxResultsToFetch);
}
- if (offset != null && sql.contains("__hibernate_row_nr__")) {
+ if (offset != null && sql.contains("__row__")) {
bindVariables.add(theOffset + 1);
bindVariables.add(theOffset + maxResultsToFetch + 1);
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java
index 8a9ec107422..2cf72ae69ce 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java
@@ -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.model.DaoMethodOutcome;
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.searchparam.SearchParameterMap;
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.List;
+import java.util.Optional;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
@@ -155,10 +157,19 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
+ myCaptureQueriesListener.clear();
+
Observation o = new Observation();
o.setStatus(ObservationStatus.FINAL);
IIdType id = myObservationDao.create(o, mySrd).getId();
+ myCaptureQueriesListener.logAllQueries();
+
+ runInTransaction(()->{
+ ResourceTable entity = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(()->new IllegalArgumentException());
+ assertEquals(1, entity.getVersion());
+ });
+
try {
myPatientDao.read(new IdType("Patient/999999999999999"));
fail();
@@ -167,11 +178,15 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
}
o = new Observation();
- o.setId(id);
+ o.setId(id.getValue());
o.setStatus(ObservationStatus.FINAL);
o.getSubject().setReference("Patient/999999999999999");
myObservationDao.update(o, mySrd);
+ runInTransaction(()->{
+ ResourceTable entity = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(()->new IllegalArgumentException());
+ assertEquals(2, entity.getVersion());
+ });
myPatientDao.read(new IdType("Patient/999999999999999"));
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java
index c08efc764fc..0b733aa127c 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java
@@ -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.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.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
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.empty;
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.Mockito.when;
@@ -77,6 +80,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets());
+ myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
}
@Override
@@ -136,6 +140,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
}
+
@Test
public void testRead() {
IIdType id = runInTransaction(() -> {
@@ -255,6 +260,129 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
assertEquals(4, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
+
+ runInTransaction(() -> {
+ List 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 allForcedIds = myForcedIdDao.findAll();
+ for (ForcedId next : allForcedIds) {
+ assertNotNull(next.getResourceId());
+ assertNotNull(next.getForcedId());
+ }
+
+ List 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 allForcedIds = myForcedIdDao.findAll();
+ for (ForcedId next : allForcedIds) {
+ assertNotNull(next.getResourceId());
+ assertNotNull(next.getForcedId());
+ }
+
+ List 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
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/BaseSearchQueryBuilderDialectTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/BaseSearchQueryBuilderDialectTest.java
new file mode 100644
index 00000000000..19c8e577c0b
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/BaseSearchQueryBuilderDialectTest.java
@@ -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);
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderMySqlTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderDialectMySqlTest.java
similarity index 76%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderMySqlTest.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderDialectMySqlTest.java
index 3998c78a51d..5d222e7dd98 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderMySqlTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderDialectMySqlTest.java
@@ -1,43 +1,24 @@
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 ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.OrderObject;
-import org.junit.jupiter.api.BeforeEach;
+import org.hibernate.dialect.Dialect;
import org.junit.jupiter.api.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.annotation.Nonnull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
-public class SearchQueryBuilderMySqlTest {
-
- @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);
- }
+@ExtendWith(MockitoExtension.class)
+public class SearchQueryBuilderDialectMySqlTest extends BaseSearchQueryBuilderDialectTest {
@Test
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
public void testAddSortNumericWithNullOrder() {
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 ?"));
}
+
+ @Nonnull
+ @Override
+ protected Dialect createDialect() {
+ return new org.hibernate.dialect.MySQL57Dialect();
+ }
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderDialectSqlServerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderDialectSqlServerTest.java
new file mode 100644
index 00000000000..2a6c042227b
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderDialectSqlServerTest.java
@@ -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();
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderTest.java
index 99f799469ac..2686e61ddfa 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilderTest.java
@@ -24,6 +24,8 @@ import org.springframework.context.annotation.Scope;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
+import java.util.Locale;
+
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -64,12 +66,12 @@ public class SearchQueryBuilderTest {
// Max only
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));
// Range
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));
}
@@ -93,13 +95,13 @@ public class SearchQueryBuilderTest {
// Max only
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 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));
// Range
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 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));
}
@@ -121,12 +123,12 @@ public class SearchQueryBuilderTest {
// Max only
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));
// Range
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));
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
index 2935e856d6d..7107ad609b1 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
@@ -270,6 +270,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
private transient ResourceHistoryTable myCurrentVersionEntity;
@OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource")
+ @OptimisticLock(excluded = true)
private ForcedId myForcedId;
@Transient
diff --git a/pom.xml b/pom.xml
index 079c7295e29..9e941c4f7dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -809,8 +809,8 @@
3.0.2
5.8.2
0.50.40
- 8.3.0
- 5.4.33
+ 8.4.1
+ 5.6.2.Final
6.0.3.Final
8.7.0
@@ -832,10 +832,10 @@
9.8.0-15
1.2_5
2.1.12
- 1.7.32
+ 1.7.33
2.17.1
- 5.3.14
- 2.6.0
+ 5.3.15
+ 2.6.1
4.3.3
2.6.2
1.2.2.RELEASE
@@ -1134,7 +1134,7 @@
com.healthmarketscience.sqlbuilder
sqlbuilder
- 3.0.1
+ 3.0.2
com.microsoft.sqlserver