diff --git a/Jenkinsfile b/Jenkinsfile
index 95554dd875..bb0a945e76 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -29,6 +29,7 @@ pipeline {
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
+ sh './accept-third-party-license.sh'
sh 'mkdir -p /tmp/jenkins-home'
sh 'chown -R 1001:1001 .'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,all-dbs clean dependency:list test -Dsort -U -B'
@@ -55,6 +56,7 @@ pipeline {
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
+ sh './accept-third-party-license.sh'
sh 'mkdir -p /tmp/jenkins-home'
sh 'chown -R 1001:1001 .'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,all-dbs,java11 clean dependency:list test -Dsort -U -B'
@@ -73,7 +75,8 @@ pipeline {
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
- sh 'mkdir -p /tmp/jenkins-home'
+ sh './accept-third-party-license.sh'
+ sh 'mkdir -p /tmp/jenkins-home'
sh 'chown -R 1001:1001 .'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,all-dbs,java11 clean dependency:list test -Dsort -U -B'
sh 'chown -R 1001:1001 .'
diff --git a/accept-third-party-license.sh b/accept-third-party-license.sh
new file mode 100755
index 0000000000..efc815ac45
--- /dev/null
+++ b/accept-third-party-license.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+{
+ echo "mcr.microsoft.com/mssql/server:2017-CU12"
+ echo "ibmcom/db2:11.5.0.0a"
+} > spring-data-jdbc/src/test/resources/container-license-acceptance.txt
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f6359158d4..9590d76a7c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-relational-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAJDBC-257-SNAPSHOT
pom
Spring Data Relational Parent
@@ -25,6 +25,7 @@
0.1.4
+ 11.5.0.0
1.4.200
2.2.8
7.0.0.jre8
@@ -170,6 +171,24 @@
+
+ db2-test
+ test
+
+ test
+
+
+
+ **/*IntegrationTests.java
+
+
+ **/*HsqlIntegrationTests.java
+
+
+ db2
+
+
+
diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml
index 858a5f4b4b..fc35fada8c 100644
--- a/spring-data-jdbc-distribution/pom.xml
+++ b/spring-data-jdbc-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAJDBC-257-SNAPSHOT
../pom.xml
diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml
index 1fdf3e615a..ba965ec34a 100644
--- a/spring-data-jdbc/pom.xml
+++ b/spring-data-jdbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-jdbc
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAJDBC-257-SNAPSHOT
Spring Data JDBC
Spring Data module for JDBC repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAJDBC-257-SNAPSHOT
@@ -186,6 +186,12 @@
test
+
+ com.ibm.db2
+ jcc
+ 11.1.4.4
+
+
de.schauderhaft.degraph
degraph-check
@@ -223,6 +229,12 @@
test
+
+ org.testcontainers
+ db2
+ test
+
+
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java
index 2784e7e97a..7aca7c53f0 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java
@@ -26,6 +26,7 @@
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.dao.NonTransientDataAccessException;
+import org.springframework.data.relational.core.dialect.Db2Dialect;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.H2Dialect;
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
@@ -123,7 +124,9 @@ private static Dialect getDialect(Connection connection) throws SQLException {
if (name.contains("microsoft")) {
return SqlServerDialect.INSTANCE;
}
-
+ if (name.contains("db2")) {
+ return Db2Dialect.INSTANCE;
+ }
return null;
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
index c406fc9eca..0b260c1827 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
@@ -514,6 +514,7 @@ public void saveAndLoadAnEntityWithArray() {
assumeNot("mysql");
assumeNot("mariadb");
assumeNot("mssql");
+ assumeNot("db2");
ArrayOwner arrayOwner = new ArrayOwner();
arrayOwner.digits = new String[] { "one", "two", "three" };
@@ -539,6 +540,7 @@ public void saveAndLoadAnEntityWithMultidimensionalArray() {
assumeNot("mariadb");
assumeNot("mssql");
assumeNot("hsqldb");
+ assumeNot("db2");
ArrayOwner arrayOwner = new ArrayOwner();
arrayOwner.multidimensional = new String[][] { { "one-a", "two-a", "three-a" }, { "one-b", "two-b", "three-b" } };
@@ -563,6 +565,7 @@ public void saveAndLoadAnEntityWithList() {
assumeNot("mysql");
assumeNot("mariadb");
assumeNot("mssql");
+ assumeNot("db2");
ListOwner arrayOwner = new ListOwner();
arrayOwner.digits.addAll(Arrays.asList("one", "two", "three"));
@@ -586,6 +589,7 @@ public void saveAndLoadAnEntityWithSet() {
assumeNot("mysql");
assumeNot("mariadb");
assumeNot("mssql");
+ assumeNot("db2");
SetOwner setOwner = new SetOwner();
setOwner.digits.addAll(Arrays.asList("one", "two", "three"));
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java
similarity index 83%
rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationIntegrationTests.java
rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java
index 7524bc0df2..e90cc12f73 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationIntegrationTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java
@@ -19,18 +19,16 @@
import lombok.Value;
-import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
-import org.junit.Assume;
+import org.assertj.core.api.SoftAssertions;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -42,7 +40,7 @@
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.Nullable;
-import org.springframework.test.annotation.ProfileValueUtils;
+import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.transaction.annotation.Transactional;
@@ -55,7 +53,8 @@
* @author Mark Paluch
*/
@Transactional
-public class QueryAnnotationIntegrationTests {
+@ActiveProfiles("hsql")
+public class QueryAnnotationHsqlIntegrationTests {
@Configuration
@Import(TestConfiguration.class)
@@ -64,7 +63,7 @@ static class Config {
@Bean
Class> testClass() {
- return QueryAnnotationIntegrationTests.class;
+ return QueryAnnotationHsqlIntegrationTests.class;
}
}
@@ -76,8 +75,6 @@ Class> testClass() {
@Test // DATAJDBC-164
public void executeCustomQueryWithoutParameter() {
- assumeNot("mysql");
-
repository.save(dummyEntity("Example"));
repository.save(dummyEntity("example"));
repository.save(dummyEntity("EXAMPLE"));
@@ -180,8 +177,6 @@ public void executeCustomQueryWithReturnTypeIsStream() {
@Test // DATAJDBC-175
public void executeCustomQueryWithReturnTypeIsNumber() {
- assumeNot("mysql");
-
repository.save(dummyEntity("aaa"));
repository.save(dummyEntity("bbb"));
repository.save(dummyEntity("cac"));
@@ -194,40 +189,29 @@ public void executeCustomQueryWithReturnTypeIsNumber() {
@Test // DATAJDBC-175
public void executeCustomQueryWithReturnTypeIsBoolean() {
- assumeNot("mysql");
-
repository.save(dummyEntity("aaa"));
repository.save(dummyEntity("bbb"));
repository.save(dummyEntity("cac"));
- assertThat(repository.existsByNameContaining("a")).isTrue();
- assertThat(repository.existsByNameContaining("d")).isFalse();
+ SoftAssertions.assertSoftly(softly -> {
+
+ softly.assertThat(repository.existsByNameContaining("a")).describedAs("entities with A in the name").isTrue();
+ softly.assertThat(repository.existsByNameContaining("d")).describedAs("entities with D in the name").isFalse();
+ });
}
@Test // DATAJDBC-175
public void executeCustomQueryWithReturnTypeIsDate() {
- assumeNot("mysql");
-
- // Since Timestamp extends Date the repository returns the Timestamp as it comes from the database.
- // Trying to compare that to an actual Date results in non deterministic results, so we have to use an actual
- // Timestamp.
- Date now = new Timestamp(System.currentTimeMillis());
- assertThat(repository.nowWithDate()).isAfterOrEqualsTo(now);
-
+ assertThat(repository.nowWithDate()).isInstanceOf(Date.class);
}
@Test // DATAJDBC-175
public void executeCustomQueryWithReturnTypeIsLocalDateTimeList() {
- // mysql does not support plain VALUES(…)
- assumeNot("mysql");
-
- LocalDateTime preciseNow = LocalDateTime.now();
- LocalDateTime truncatedNow = truncateSubmillis(preciseNow);
-
- repository.nowWithLocalDateTimeList() //
- .forEach(d -> assertThat(d).isAfterOrEqualTo(truncatedNow));
+ assertThat(repository.nowWithLocalDateTimeList()) //
+ .hasSize(2) //
+ .allSatisfy(d -> assertThat(d).isInstanceOf(LocalDateTime.class));
}
@Test // DATAJDBC-182
@@ -270,19 +254,9 @@ public void executeCustomModifyingQueryWithReturnTypeVoid() {
@Test // DATAJDBC-175
public void executeCustomQueryWithImmutableResultType() {
- // mysql does not support plain VALUES(…)
-
- assumeNot("mysql");
-
assertThat(repository.immutableTuple()).isEqualTo(new DummyEntityRepository.ImmutableTuple("one", "two", 3));
}
- private static LocalDateTime truncateSubmillis(LocalDateTime now) {
-
- int NANOS_IN_MILLIS = 1_000_000;
- return now.withNano((now.getNano() / NANOS_IN_MILLIS) * 1_000_000);
- }
-
private DummyEntity dummyEntity(String name) {
DummyEntity entity = new DummyEntity();
@@ -290,13 +264,6 @@ private DummyEntity dummyEntity(String name) {
return entity;
}
- private static void assumeNot(String dbProfileName) {
-
- Assume.assumeTrue(
- "true".equalsIgnoreCase(ProfileValueUtils.retrieveProfileValueSource(QueryAnnotationIntegrationTests.class)
- .get("current.database.is.not." + dbProfileName)));
- }
-
private static class DummyEntity {
@Id Long id;
@@ -327,11 +294,11 @@ private interface DummyEntityRepository extends CrudRepository findAllWithReturnTypeIsStream();
// DATAJDBC-175
- @Query("SELECT count(*) FROM DUMMY_ENTITY WHERE name like '%' || :name || '%'")
+ @Query("SELECT count(*) FROM DUMMY_ENTITY WHERE name like concat('%', :name, '%')")
int countByNameContaining(@Param("name") String name);
// DATAJDBC-175
- @Query("SELECT count(*) FROM DUMMY_ENTITY WHERE name like '%' || :name || '%'")
+ @Query("SELECT case when count(*) > 0 THEN 'true' ELSE 'false' END FROM DUMMY_ENTITY WHERE name like '%' || :name || '%'")
boolean existsByNameContaining(@Param("name") String name);
// DATAJDBC-175
@@ -358,7 +325,7 @@ private interface DummyEntityRepository extends CrudRepository4.0.0
spring-data-relational
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAJDBC-257-SNAPSHOT
Spring Data Relational
Spring Data Relational support
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAJDBC-257-SNAPSHOT
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java
new file mode 100644
index 0000000000..7d2fef315a
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.relational.core.dialect;
+
+import org.springframework.data.relational.core.sql.IdentifierProcessing;
+
+/**
+ * An SQL dialect for DB2.
+ *
+ * @author Jens Schauder
+ * @since 2.1
+ */
+public class Db2Dialect extends AbstractDialect {
+
+ /**
+ * Singleton instance.
+ */
+ public static final Db2Dialect INSTANCE = new Db2Dialect();
+
+ protected Db2Dialect() {}
+
+ private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.dialect.LimitClause#getLimit(long)
+ */
+ @Override
+ public String getLimit(long limit) {
+ return "FIRST " + limit + " ROWS ONLY";
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.dialect.LimitClause#getOffset(long)
+ */
+ @Override
+ public String getOffset(long offset) {
+ return "OFFSET " + offset + " ROWS";
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.dialect.LimitClause#getClause(long, long)
+ */
+ @Override
+ public String getLimitOffset(long limit, long offset) {
+ return String.format("OFFSET %d ROWS FETCH FIRST %d ROWS ONLY", offset, limit);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.dialect.LimitClause#getClausePosition()
+ */
+ @Override
+ public Position getClausePosition() {
+ return Position.AFTER_ORDER_BY;
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.dialect.Dialect#limit()
+ */
+ @Override
+ public LimitClause limit() {
+ return LIMIT_CLAUSE;
+ }
+
+ @Override
+ public IdentifierProcessing getIdentifierProcessing() {
+ return IdentifierProcessing.ANSI;
+ }
+}