From fc89e23144f5227abd4664572a808da1f0fe6022 Mon Sep 17 00:00:00 2001 From: Tyler Carpenter-Rivers Date: Mon, 11 Apr 2022 19:30:47 -0700 Subject: [PATCH] resolves https://github.com/spring-cloud/spring-cloud-task/issues/780 update db2 'between' logic to be exclusive Signed-off-by: Ryan DCruz use .acceptLicense() instead of the classpath file checkstyle add test to verify a result set that's smaller than requested page size cleanup --- .../support/Db2PagingQueryProvider.java | 4 +- .../FindAllPagingQueryProviderTests.java | 12 +- spring-cloud-task-integration-tests/pom.xml | 25 +++ .../db2/Db2PagingQueryProviderTest.java | 178 ++++++++++++++++++ 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 spring-cloud-task-integration-tests/src/test/java/org/springframework/cloud/task/database/support/db2/Db2PagingQueryProviderTest.java diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/Db2PagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/Db2PagingQueryProvider.java index 07c3b36a..66424c18 100755 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/Db2PagingQueryProvider.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/Db2PagingQueryProvider.java @@ -24,6 +24,7 @@ import org.springframework.data.domain.Pageable; * database specific features. * * @author Thomas Schuettel + * @author Ryan DCruz */ public class Db2PagingQueryProvider extends AbstractSqlPagingQueryProvider { @@ -31,8 +32,7 @@ public class Db2PagingQueryProvider extends AbstractSqlPagingQueryProvider { public String getPageQuery(Pageable pageable) { long offset = pageable.getOffset() + 1; return generateRowNumSqlQueryWithNesting(getSelectClause(), false, - "TMP_ROW_NUM BETWEEN " + offset + " AND " - + (offset + pageable.getPageSize())); + "TMP_ROW_NUM >= " + offset + " AND TMP_ROW_NUM < " + (offset + pageable.getPageSize())); } private String generateRowNumSqlQueryWithNesting(String selectClause, diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java index f091e3b6..a4802014 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/database/support/FindAllPagingQueryProviderTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Glenn Renfro + * @author Ryan DCruz */ public class FindAllPagingQueryProviderTests { @@ -66,7 +67,16 @@ public class FindAllPagingQueryProviderTests { + "OVER (ORDER BY START_TIME DESC, TASK_EXECUTION_ID DESC) AS " + "TMP_ROW_NUM FROM %PREFIX%EXECUTION) TASK_EXECUTION_PAGE " + "WHERE TMP_ROW_NUM >= 1 AND TMP_ROW_NUM < 11 ORDER BY START_TIME DESC, " - + "TASK_EXECUTION_ID DESC" } }); + + "TASK_EXECUTION_ID DESC" }, + { "DB2/Linux", + "SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, " + + "TASK_NAME, EXIT_CODE, EXIT_MESSAGE, ERROR_MESSAGE, LAST_UPDATED, EXTERNAL_EXECUTION_ID, PARENT_EXECUTION_ID FROM " + + "(SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, " + + "EXIT_CODE, EXIT_MESSAGE, ERROR_MESSAGE, LAST_UPDATED, EXTERNAL_EXECUTION_ID, PARENT_EXECUTION_ID, ROW_NUMBER() " + + "OVER() as TMP_ROW_NUM FROM " + + "(SELECT TASK_EXECUTION_ID, START_TIME, END_TIME, TASK_NAME, EXIT_CODE, EXIT_MESSAGE, ERROR_MESSAGE, LAST_UPDATED, " + + "EXTERNAL_EXECUTION_ID, PARENT_EXECUTION_ID FROM %PREFIX%EXECUTION ORDER BY START_TIME DESC, TASK_EXECUTION_ID DESC)) " + + "WHERE TMP_ROW_NUM >= 1 AND TMP_ROW_NUM < 11"}}); } @ParameterizedTest diff --git a/spring-cloud-task-integration-tests/pom.xml b/spring-cloud-task-integration-tests/pom.xml index d76eb75a..0b0d2588 100644 --- a/spring-cloud-task-integration-tests/pom.xml +++ b/spring-cloud-task-integration-tests/pom.xml @@ -17,6 +17,7 @@ 1.16.3 1.16.3 1.0.8 + 11.5.7.0 @@ -28,6 +29,13 @@ pom import + + org.testcontainers + testcontainers-bom + ${test.containers.version} + pom + import + @@ -103,12 +111,29 @@ ${test.containers.version} test + + org.testcontainers + junit-jupiter + test + org.testcontainers rabbitmq ${test.rabbit.containers.version} test + + org.testcontainers + db2 + ${test.containers.version} + test + + + com.ibm.db2 + jcc + ${db2-jdbc.version} + test + org.rnorth.duct-tape duct-tape diff --git a/spring-cloud-task-integration-tests/src/test/java/org/springframework/cloud/task/database/support/db2/Db2PagingQueryProviderTest.java b/spring-cloud-task-integration-tests/src/test/java/org/springframework/cloud/task/database/support/db2/Db2PagingQueryProviderTest.java new file mode 100644 index 00000000..738f063d --- /dev/null +++ b/spring-cloud-task-integration-tests/src/test/java/org/springframework/cloud/task/database/support/db2/Db2PagingQueryProviderTest.java @@ -0,0 +1,178 @@ +/* + * Copyright 2015-2022 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.cloud.task.database.support.db2; + + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Db2Container; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.task.executionid.TaskStartApplication; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.dao.TaskExecutionDao; +import org.springframework.cloud.task.repository.database.support.Db2PagingQueryProvider; +import org.springframework.cloud.task.repository.support.SimpleTaskExplorer; +import org.springframework.cloud.task.repository.support.TaskExecutionDaoFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.jdbc.SqlConfig.ErrorMode.CONTINUE_ON_ERROR; + +/** + * @author Tyler Carpenter-Rivers + * @author Ryan DCruz + * @see Db2PagingQueryProvider + */ +@SpringBootTest( + classes = {TaskStartApplication.class, Db2PagingQueryProviderTest.TestConfig.class}, + properties = "spring.cloud.task.events.enabled=false" +) +@Testcontainers(disabledWithoutDocker = true) +@DirtiesContext +@Sql( + scripts = "classpath:/org/springframework/cloud/task/schema-db2.sql", + config = @SqlConfig(errorMode = CONTINUE_ON_ERROR) +) +@Sql(statements = { + " DELETE FROM TASK_EXECUTION_PARAMS ", + " DELETE FROM TASK_TASK_BATCH ", + " DELETE FROM TASK_EXECUTION " +}) +class Db2PagingQueryProviderTest { + + @Container + public static Db2Container db2Container = + new Db2Container(DockerImageName.parse("ibmcom/db2:11.5.7.0")) + .acceptLicense() + .withDatabaseName("test") + .withStartupTimeoutSeconds(30) + .withExposedPorts(50000); + + @DynamicPropertySource + static void properties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", db2Container::getJdbcUrl); + registry.add("spring.datasource.username", db2Container::getUsername); + registry.add("spring.datasource.password", db2Container::getPassword); + } + + @Autowired + private SimpleTaskExplorer simpleTaskExplorer; + + @Autowired + private TaskExecutionDao taskExecutionDao; + + @DisplayName("Scenario: The page request size is less than the total number of elements" + + "Given the total number of elements is 3" + + "And the page request size is 2" + + "When the query is executed" + + "Then the returned page has 2 elements") + @Test + void pageRequest() { + // setup test data + taskExecutionDao.createTaskExecution( + UUID.randomUUID().toString(), new Date(), List.of(), UUID.randomUUID().toString()); + taskExecutionDao.createTaskExecution( + UUID.randomUUID().toString(), new Date(), List.of(), UUID.randomUUID().toString()); + taskExecutionDao.createTaskExecution( + UUID.randomUUID().toString(), new Date(), List.of(), UUID.randomUUID().toString()); + + PageRequest pageRequest = PageRequest.of(0, 2); + + // run subject under test + Page page = simpleTaskExplorer.findAll(pageRequest); + + // assert and verify + assertThat(page.getTotalElements()) + .as("Expected the total number of records in the table to be 3") + .isEqualTo(3); + + assertThat(pageRequest.getPageSize()) + .as("Expected the size of the returned page to be 2") + .isEqualTo(2); + } + + @DisplayName("Scenario: the page request size is larger than the total number of elements" + + "Given the total number of elements is 1" + + "And the page request size is 2" + + "When the query is executed" + + "Then the returned page has 1 element") + @Test + void pageRequestSinglePageEntries() { + // setup test data + taskExecutionDao.createTaskExecution( + UUID.randomUUID().toString(), new Date(), List.of(), UUID.randomUUID().toString()); + + PageRequest pageRequest = PageRequest.of(0, 2); + + // run subject under test + Page page = simpleTaskExplorer.findAll(pageRequest); + + // assert and verify + assertThat(page.getTotalElements()) + .as("Should only have one record in the table") + .isEqualTo(1); + + assertThat(page.getNumberOfElements()) + .as("Should have one record in the returned page") + .isEqualTo(1); + + assertThat(page.getTotalPages()) + .as("Should expect the total number of pages to be 1") + .isEqualTo(1); + } + + @TestConfiguration + public static class TestConfig { + @Bean + @ConfigurationProperties("spring.datasource") + public DataSource dataSource() { + return new DriverManagerDataSource(); + } + + @Bean + public TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBean(DataSource dataSource) { + return new TaskExecutionDaoFactoryBean(dataSource); + } + + @Bean + public TaskExecutionDao taskExecutionDao(TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBean) throws Exception { + return taskExecutionDaoFactoryBean.getObject(); + } + } +}