Repository: spring-cloud/spring-cloud-task (java)

Issue:
Mariadb needs to be treated as a independent db type.
Currently Task treats mariadb as a version of MySql.   Task needs to treat mariadb as its own type.

Pull request description:
Add support for mariadb
resolves #833

PagingProvider and integration tests added
Added integration tests for mariadb

Special thanks to @ben-enfuse-io for the work he has done for this PR!

Patch:
diff --git a/pom.xml b/pom.xml
index 94afad20..93449189 100755
--- a/pom.xml
+++ b/pom.xml
@@ -120,6 +120,8 @@
 		</maven-checkstyle-plugin.includeTestSourceDirectory>
 		<java.version>17</java.version>
 		<spring-javaformat-maven-plugin.version>0.0.34</spring-javaformat-maven-plugin.version>
+		<test.containers.version>1.17.5</test.containers.version>
+		<test.ducttape.version>1.0.8</test.ducttape.version>
 	</properties>
 
 	<build>
diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml
index 2ef20c63..4d65f633 100755
--- a/spring-cloud-task-core/pom.xml
+++ b/spring-cloud-task-core/pom.xml
@@ -59,6 +59,7 @@
 		<dependency>
 			<groupId>org.springframework.batch</groupId>
 			<artifactId>spring-batch-infrastructure</artifactId>
+			<version>5.0.0-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.data</groupId>
@@ -164,6 +165,35 @@
 			<artifactId>zipkin-sender-urlconnection</artifactId>
 			<scope>test</scope>
 		</dependency>
- </dependencies>
+		<dependency>
+			<groupId>org.testcontainers</groupId>
+			<artifactId>testcontainers</artifactId>
+			<version>${test.containers.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.testcontainers</groupId>
+			<artifactId>junit-jupiter</artifactId>
+			<version>${test.containers.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.testcontainers</groupId>
+			<artifactId>mariadb</artifactId>
+			<version>${test.containers.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.rnorth.duct-tape</groupId>
+			<artifactId>duct-tape</artifactId>
+			<version>${test.ducttape.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mariadb.jdbc</groupId>
+			<artifactId>mariadb-java-client</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
 
 </project>
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/MariaDbPagingQueryProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/MariaDbPagingQueryProvider.java
new file mode 100644
index 00000000..f2c789a2
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/MariaDbPagingQueryProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022-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.repository.database.support;
+
+import org.springframework.cloud.task.repository.database.PagingQueryProvider;
+import org.springframework.data.domain.Pageable;
+
+/**
+ * MariaDB implementation of a {@link PagingQueryProvider} using database specific
+ * features.
+ *
+ * @author Glenn Renfro
+ */
+public class MariaDbPagingQueryProvider extends AbstractSqlPagingQueryProvider {
+
+	@Override
+	public String getPageQuery(Pageable pageable) {
+		String topClause = new StringBuilder().append("LIMIT ").append(pageable.getOffset()).append(", ")
+				.append(pageable.getPageSize()).toString();
+		return SqlPagingQueryUtils.generateLimitJumpToQuery(this, topClause);
+	}
+
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java
index cc407d88..b9ce01ea 100755
--- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryProviderFactoryBean.java
@@ -35,6 +35,7 @@ import static org.springframework.cloud.task.repository.support.DatabaseType.DB2
 import static org.springframework.cloud.task.repository.support.DatabaseType.DB2ZOS;
 import static org.springframework.cloud.task.repository.support.DatabaseType.H2;
 import static org.springframework.cloud.task.repository.support.DatabaseType.HSQL;
+import static org.springframework.cloud.task.repository.support.DatabaseType.MARIADB;
 import static org.springframework.cloud.task.repository.support.DatabaseType.MYSQL;
 import static org.springframework.cloud.task.repository.support.DatabaseType.ORACLE;
 import static org.springframework.cloud.task.repository.support.DatabaseType.POSTGRES;
@@ -67,6 +68,7 @@ public class SqlPagingQueryProviderFactoryBean implements FactoryBean<PagingQuer
 		this.providers.put(HSQL, new HsqlPagingQueryProvider());
 		this.providers.put(H2, new H2PagingQueryProvider());
 		this.providers.put(MYSQL, new MySqlPagingQueryProvider());
+		this.providers.put(MARIADB, new MariaDbPagingQueryProvider());
 		this.providers.put(POSTGRES, new PostgresPagingQueryProvider());
 		this.providers.put(ORACLE, new OraclePagingQueryProvider());
 		this.providers.put(SQLSERVER, new SqlServerPagingQueryProvider());
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java
index 7a27fb48..298c035d 100644
--- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/DatabaseType.java
@@ -57,6 +57,11 @@ public enum DatabaseType {
 	 */
 	MYSQL("MySQL"),
 
+	/**
+	 * MySQL DB.
+	 */
+	MARIADB("MariaDB"),
+
 	/**
 	 * PostgreSQL DB.
 	 */
@@ -145,7 +150,9 @@ public enum DatabaseType {
 			}
 		}
 		else {
-			databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName);
+			if (!databaseProductName.equals(MARIADB.getProductName())) {
+				databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName);
+			}
 		}
 		return fromProductName(databaseProductName);
 	}
@@ -157,9 +164,6 @@ public enum DatabaseType {
 	 * @throws IllegalArgumentException if none is found.
 	 */
 	public static DatabaseType fromProductName(String productName) {
-		if (productName.equals("MariaDB")) {
-			productName = "MySQL";
-		}
 		if (!dbNameMap.containsKey(productName)) {
 			throw new IllegalArgumentException("DatabaseType not found for product name: [" + productName + "]");
 		}
diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mariadb.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mariadb.sql
new file mode 100644
index 00000000..b54ddc62
--- /dev/null
+++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mariadb.sql
@@ -0,0 +1,37 @@
+
+CREATE TABLE TASK_EXECUTION  (
+	TASK_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY ,
+	START_TIME DATETIME(6) DEFAULT NULL ,
+	END_TIME DATETIME(6) DEFAULT NULL ,
+	TASK_NAME  VARCHAR(100) ,
+	EXIT_CODE INTEGER ,
+	EXIT_MESSAGE VARCHAR(2500) ,
+	ERROR_MESSAGE VARCHAR(2500) ,
+	LAST_UPDATED TIMESTAMP,
+	EXTERNAL_EXECUTION_ID VARCHAR(255),
+	PARENT_EXECUTION_ID BIGINT
+) ENGINE=InnoDB;
+
+CREATE TABLE TASK_EXECUTION_PARAMS  (
+	TASK_EXECUTION_ID BIGINT NOT NULL ,
+	TASK_PARAM VARCHAR(2500) ,
+	constraint TASK_EXEC_PARAMS_FK foreign key (TASK_EXECUTION_ID)
+	references TASK_EXECUTION(TASK_EXECUTION_ID)
+) ENGINE=InnoDB;
+
+CREATE TABLE TASK_TASK_BATCH (
+ TASK_EXECUTION_ID BIGINT NOT NULL ,
+ JOB_EXECUTION_ID BIGINT NOT NULL ,
+	constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID)
+	references TASK_EXECUTION(TASK_EXECUTION_ID)
+)  ENGINE=InnoDB;
+
+CREATE TABLE TASK_LOCK  (
+	LOCK_KEY CHAR(36) NOT NULL,
+	REGION VARCHAR(100) NOT NULL,
+	CLIENT_ID CHAR(36),
+	CREATED_DATE DATETIME(6) NOT NULL,
+	constraint LOCK_PK primary key (LOCK_KEY, REGION)
+) ENGINE=InnoDB;
+
+CREATE SEQUENCE TASK_SEQ START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775806 INCREMENT BY 1 NOCACHE NOCYCLE ENGINE=InnoDB;


Test patch:
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/MariaDbTaskRepositoryIntegrationTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/MariaDbTaskRepositoryIntegrationTests.java
new file mode 100644
index 00000000..edffcba9
--- /dev/null
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/MariaDbTaskRepositoryIntegrationTests.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2022-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.repository;
+
+import javax.sql.DataSource;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.mariadb.jdbc.MariaDbDataSource;
+import org.testcontainers.containers.MariaDBContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.task.configuration.EnableTask;
+import org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Tag("DockerRequired")
+@Testcontainers
+@SpringJUnitConfig
+public class MariaDbTaskRepositoryIntegrationTests {
+
+	private static final DockerImageName MARIADB_IMAGE = DockerImageName.parse("mariadb:10.9.3");
+
+	@Container
+	public static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>(MARIADB_IMAGE);
+
+	@Test
+	public void testTaskExplorer() {
+		ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
+				.withUserConfiguration(MariaDbTaskRepositoryIntegrationTests.TestConfiguration.class);
+
+		applicationContextRunner.run((context -> {
+			TaskExplorer taskExplorer = context.getBean(TaskExplorer.class);
+			assertThat(taskExplorer.getTaskExecutionCount()).isOne();
+		}));
+		applicationContextRunner.run((context -> {
+			TaskExplorer taskExplorer = context.getBean(TaskExplorer.class);
+			assertThat(taskExplorer.getTaskExecutionCount()).isEqualTo(2);
+		}));
+	}
+
+	@EnableTask
+	@ImportAutoConfiguration(SimpleTaskAutoConfiguration.class)
+	static class TestConfiguration {
+
+		public static boolean firstTime = true;
+
+		@Bean
+		public DataSource dataSource() throws Exception {
+			MariaDbDataSource datasource = new MariaDbDataSource();
+			datasource.setUrl(mariaDBContainer.getJdbcUrl());
+			datasource.setUser(mariaDBContainer.getUsername());
+			datasource.setPassword(mariaDBContainer.getPassword());
+			if (firstTime) {
+				ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
+				databasePopulator
+						.addScript(new ClassPathResource("/org/springframework/cloud/task/schema-mariadb.sql"));
+				databasePopulator.execute(datasource);
+				firstTime = false;
+			}
+			return datasource;
+		}
+
+	}
+
+}
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoMariaDBIntegrationTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoMariaDBIntegrationTests.java
new file mode 100644
index 00000000..4d9b1859
--- /dev/null
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDaoMariaDBIntegrationTests.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2022-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.repository.dao;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.UUID;
+
+import javax.sql.DataSource;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mariadb.jdbc.MariaDbDataSource;
+import org.testcontainers.containers.MariaDBContainer;
+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.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
+import org.springframework.cloud.task.configuration.TestConfiguration;
+import org.springframework.cloud.task.repository.TaskExecution;
+import org.springframework.cloud.task.repository.TaskRepository;
+import org.springframework.cloud.task.util.TestDBUtils;
+import org.springframework.cloud.task.util.TestVerifierUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Executes unit integration tests on JdbcTaskExecutionDao for MARIADB.
+ *
+ * @author Glenn Renfro
+ */
+@Tag("DockerRequired")
+@Testcontainers
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = { TestConfiguration.class,
+		JdbcTaskExecutionDaoMariaDBIntegrationTests.TestDataSourceConfiguration.class,
+		PropertyPlaceholderAutoConfiguration.class })
+public class JdbcTaskExecutionDaoMariaDBIntegrationTests extends BaseTaskExecutionDaoTestCases {
+
+	private static final DockerImageName MARIADB_IMAGE = DockerImageName.parse("mariadb:10.9.3");
+
+	@Container
+	public static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>(MARIADB_IMAGE);
+
+	@Autowired
+	TaskRepository repository;
+
+	@Autowired
+	private DataSource dataSource;
+
+	@BeforeEach
+	public void setup() {
+		final JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(this.dataSource);
+		dao.setTaskIncrementer(TestDBUtils.getIncrementer(this.dataSource));
+		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+		jdbcTemplate.execute("TRUNCATE TABLE TASK_EXECUTION_PARAMS");
+		jdbcTemplate.execute("DELETE FROM TASK_EXECUTION");
+		jdbcTemplate.execute("ALTER SEQUENCE TASK_SEQ RESTART;");
+		super.dao = dao;
+	}
+
+	@Test
+	@DirtiesContext
+	public void testStartTaskExecution() {
+		TaskExecution expectedTaskExecution = this.dao.createTaskExecution(null, null, new ArrayList<>(0), null);
+
+		expectedTaskExecution.setArguments(Collections.singletonList("foo=" + UUID.randomUUID().toString()));
+		expectedTaskExecution.setStartTime(LocalDateTime.now());
+		expectedTaskExecution.setTaskName(UUID.randomUUID().toString());
+
+		this.dao.startTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getTaskName(),
+				expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
+				expectedTaskExecution.getExternalExecutionId());
+
+		TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
+				TestDBUtils.getTaskExecutionFromDB(this.dataSource, expectedTaskExecution.getExecutionId()));
+	}
+
+	@Test
+	@DirtiesContext
+	public void createTaskExecution() {
+		TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoArg();
+		expectedTaskExecution = this.dao.createTaskExecution(expectedTaskExecution.getTaskName(),
+				expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
+				expectedTaskExecution.getExternalExecutionId());
+
+		TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
+				TestDBUtils.getTaskExecutionFromDB(this.dataSource, expectedTaskExecution.getExecutionId()));
+	}
+
+	@Test
+	@DirtiesContext
+	public void createEmptyTaskExecution() {
+		TaskExecution expectedTaskExecution = this.dao.createTaskExecution(null, null, new ArrayList<>(0), null);
+
+		TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
+				TestDBUtils.getTaskExecutionFromDB(this.dataSource, expectedTaskExecution.getExecutionId()));
+	}
+
+	@Test
+	@DirtiesContext
+	public void completeTaskExecution() {
+		TaskExecution expectedTaskExecution = TestVerifierUtils.endSampleTaskExecutionNoArg();
+		expectedTaskExecution = this.dao.createTaskExecution(expectedTaskExecution.getTaskName(),
+				expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(),
+				expectedTaskExecution.getExternalExecutionId());
+		this.dao.completeTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getExitCode(),
+				expectedTaskExecution.getEndTime(), expectedTaskExecution.getExitMessage());
+		TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
+				TestDBUtils.getTaskExecutionFromDB(this.dataSource, expectedTaskExecution.getExecutionId()));
+	}
+
+	@Test
+	@DirtiesContext
+	public void completeTaskExecutionWithNoCreate() {
+		JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(this.dataSource);
+
+		TaskExecution expectedTaskExecution = TestVerifierUtils.endSampleTaskExecutionNoArg();
+		assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+			dao.completeTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getExitCode(),
+					expectedTaskExecution.getEndTime(), expectedTaskExecution.getExitMessage());
+		});
+	}
+
+	@Test
+	@DirtiesContext
+	public void testFindAllPageableSort() {
+		initializeRepositoryNotInOrder();
+		Sort sort = Sort.by(new Sort.Order(Sort.Direction.ASC, "EXTERNAL_EXECUTION_ID"));
+		Iterator<TaskExecution> iter = getPageIterator(0, 2, sort);
+		TaskExecution taskExecution = iter.next();
+		assertThat(taskExecution.getTaskName()).isEqualTo("FOO2");
+		taskExecution = iter.next();
+		assertThat(taskExecution.getTaskName()).isEqualTo("FOO3");
+
+		iter = getPageIterator(1, 2, sort);
+		taskExecution = iter.next();
+		assertThat(taskExecution.getTaskName()).isEqualTo("FOO1");
+	}
+
+	@Test
+	@DirtiesContext
+	public void testFindAllDefaultSort() {
+		initializeRepository();
+		Iterator<TaskExecution> iter = getPageIterator(0, 2, null);
+		TaskExecution taskExecution = iter.next();
+		assertThat(taskExecution.getTaskName()).isEqualTo("FOO1");
+		taskExecution = iter.next();
+		assertThat(taskExecution.getTaskName()).isEqualTo("FOO2");
+
+		iter = getPageIterator(1, 2, null);
+		taskExecution = iter.next();
+		assertThat(taskExecution.getTaskName()).isEqualTo("FOO3");
+	}
+
+	@Test
+	@DirtiesContext
+	public void testStartExecutionWithNullExternalExecutionIdExisting() {
+		TaskExecution expectedTaskExecution = initializeTaskExecutionWithExternalExecutionId();
+
+		this.dao.startTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getTaskName(),
+				expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(), null);
+		TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
+				TestDBUtils.getTaskExecutionFromDB(this.dataSource, expectedTaskExecution.getExecutionId()));
+	}
+
+	@Test
+	@DirtiesContext
+	public void testStartExecutionWithNullExternalExecutionIdNonExisting() {
+		TaskExecution expectedTaskExecution = initializeTaskExecutionWithExternalExecutionId();
+
+		this.dao.startTaskExecution(expectedTaskExecution.getExecutionId(), expectedTaskExecution.getTaskName(),
+				expectedTaskExecution.getStartTime(), expectedTaskExecution.getArguments(), "BAR");
+		expectedTaskExecution.setExternalExecutionId("BAR");
+		TestVerifierUtils.verifyTaskExecution(expectedTaskExecution,
+				TestDBUtils.getTaskExecutionFromDB(this.dataSource, expectedTaskExecution.getExecutionId()));
+	}
+
+	@Test
+	@DirtiesContext
+	public void testFindRunningTaskExecutions() {
+		initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+		assertThat(
+				this.dao.findRunningTaskExecutions("FOO1", PageRequest.of(1, Integer.MAX_VALUE, Sort.by("START_TIME")))
+						.getTotalElements()).isEqualTo(4);
+	}
+
+	@Test
+	@DirtiesContext
+	public void testFindRunningTaskExecutionsIllegalSort() {
+		initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+		assertThatThrownBy(() -> this.dao
+				.findRunningTaskExecutions("FOO1", PageRequest.of(1, Integer.MAX_VALUE, Sort.by("ILLEGAL_SORT")))
+				.getTotalElements()).isInstanceOf(IllegalArgumentException.class)
+						.hasMessage("Invalid sort option selected: ILLEGAL_SORT");
+	}
+
+	@Test
+	@DirtiesContext
+	public void testFindRunningTaskExecutionsSortWithDifferentCase() {
+		initializeRepositoryNotInOrderWithMultipleTaskExecutions();
+		assertThat(
+				this.dao.findRunningTaskExecutions("FOO1", PageRequest.of(1, Integer.MAX_VALUE, Sort.by("StArT_TiMe")))
+						.getTotalElements()).isEqualTo(4);
+	}
+
+	private TaskExecution initializeTaskExecutionWithExternalExecutionId() {
+		TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoArg();
+		return this.dao.createTaskExecution(expectedTaskExecution.getTaskName(), expectedTaskExecution.getStartTime(),
+				expectedTaskExecution.getArguments(), "FOO1");
+	}
+
+	private Iterator<TaskExecution> getPageIterator(int pageNum, int pageSize, Sort sort) {
+		Pageable pageable = (sort == null) ? PageRequest.of(pageNum, pageSize)
+				: PageRequest.of(pageNum, pageSize, sort);
+		Page<TaskExecution> page = this.dao.findAll(pageable);
+		assertThat(page.getTotalElements()).isEqualTo(3);
+		assertThat(page.getTotalPages()).isEqualTo(2);
+		return page.iterator();
+	}
+
+	private void initializeRepository() {
+		this.repository.createTaskExecution(getTaskExecution("FOO3", "externalA"));
+		this.repository.createTaskExecution(getTaskExecution("FOO2", "externalB"));
+		this.repository.createTaskExecution(getTaskExecution("FOO1", "externalC"));
+	}
+
+	private void initializeRepositoryNotInOrder() {
+		this.repository.createTaskExecution(getTaskExecution("FOO1", "externalC"));
+		this.repository.createTaskExecution(getTaskExecution("FOO2", "externalA"));
+		this.repository.createTaskExecution(getTaskExecution("FOO3", "externalB"));
+	}
+
+	@Configuration
+	static class TestDataSourceConfiguration {
+
+		public static boolean firstTime = true;
+
+		@Bean
+		public DataSource dataSource() throws Exception {
+			MariaDbDataSource datasource = new MariaDbDataSource();
+			datasource.setUrl(mariaDBContainer.getJdbcUrl());
+			datasource.setUser(mariaDBContainer.getUsername());
+			datasource.setPassword(mariaDBContainer.getPassword());
+			if (firstTime) {
+				ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
+				databasePopulator
+						.addScript(new ClassPathResource("/org/springframework/cloud/task/schema-mariadb.sql"));
+				databasePopulator.execute(datasource);
+				firstTime = false;
+			}
+			return datasource;
+		}
+
+	}
+
+}
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java
index b02bc89b..5462aa2d 100644
--- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/DatabaseTypeTests.java
@@ -25,6 +25,7 @@ import org.springframework.cloud.task.util.TestDBUtils;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.springframework.cloud.task.repository.support.DatabaseType.HSQL;
+import static org.springframework.cloud.task.repository.support.DatabaseType.MARIADB;
 import static org.springframework.cloud.task.repository.support.DatabaseType.MYSQL;
 import static org.springframework.cloud.task.repository.support.DatabaseType.ORACLE;
 import static org.springframework.cloud.task.repository.support.DatabaseType.POSTGRES;
@@ -46,7 +47,7 @@ public class DatabaseTypeTests {
 		assertThat(fromProductName("Oracle")).isEqualTo(ORACLE);
 		assertThat(fromProductName("PostgreSQL")).isEqualTo(POSTGRES);
 		assertThat(fromProductName("MySQL")).isEqualTo(MYSQL);
-		assertThat(fromProductName("MariaDB")).isEqualTo(MYSQL);
+		assertThat(fromProductName("MariaDB")).isEqualTo(MARIADB);
 	}
 
 	@Test
@@ -81,7 +82,7 @@ public class DatabaseTypeTests {
 	@Test
 	public void testFromMetaDataForMariaDB() throws Exception {
 		DataSource ds = TestDBUtils.getMockDataSource("MariaDB");
-		assertThat(DatabaseType.fromMetaData(ds)).isEqualTo(MYSQL);
+		assertThat(DatabaseType.fromMetaData(ds)).isEqualTo(MARIADB);
 	}
 
 }
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java
index e5e2ef9e..012b4d51 100644
--- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestVerifierUtils.java
@@ -133,12 +133,34 @@ public final class TestVerifierUtils {
 		assertThat(actualTaskExecution.getExecutionId()).as("taskExecutionId must be equal")
 				.isEqualTo(expectedTaskExecution.getExecutionId());
 		if (actualTaskExecution.getStartTime() != null) {
-			assertThat(actualTaskExecution.getStartTime().isEqual(expectedTaskExecution.getStartTime()))
-					.as("startTime must be equal").isTrue();
+			assertThat(actualTaskExecution.getStartTime().getHour() == expectedTaskExecution.getStartTime().getHour())
+					.as("startTime hour must be equal").isTrue();
+			assertThat(
+					actualTaskExecution.getStartTime().getMinute() == expectedTaskExecution.getStartTime().getMinute())
+							.as("startTime minute must be equal").isTrue();
+			assertThat(
+					actualTaskExecution.getStartTime().getSecond() == expectedTaskExecution.getStartTime().getSecond())
+							.as("startTime second must be equal").isTrue();
+			assertThat(actualTaskExecution.getStartTime().getDayOfYear() == expectedTaskExecution.getStartTime()
+					.getDayOfYear()).as("startTime day must be equal").isTrue();
+			assertThat(actualTaskExecution.getStartTime().getYear() == expectedTaskExecution.getStartTime().getYear())
+					.as("startTime year must be equal").isTrue();
+			assertThat(actualTaskExecution.getStartTime().getMonthValue() == expectedTaskExecution.getStartTime()
+					.getMonthValue()).as("startTime month must be equal").isTrue();
 		}
 		if (actualTaskExecution.getEndTime() != null) {
-			assertThat(actualTaskExecution.getEndTime().isEqual(expectedTaskExecution.getEndTime()))
-					.as("endTime must be equal").isTrue();
+			assertThat(actualTaskExecution.getEndTime().getHour() == expectedTaskExecution.getEndTime().getHour())
+					.as("endTime hour must be equal").isTrue();
+			assertThat(actualTaskExecution.getEndTime().getMinute() == expectedTaskExecution.getEndTime().getMinute())
+					.as("endTime minute must be equal").isTrue();
+			assertThat(actualTaskExecution.getEndTime().getSecond() == expectedTaskExecution.getEndTime().getSecond())
+					.as("endTime second must be equal").isTrue();
+			assertThat(actualTaskExecution.getEndTime().getDayOfYear() == expectedTaskExecution.getEndTime()
+					.getDayOfYear()).as("endTime day must be equal").isTrue();
+			assertThat(actualTaskExecution.getEndTime().getYear() == expectedTaskExecution.getEndTime().getYear())
+					.as("endTime year must be equal").isTrue();
+			assertThat(actualTaskExecution.getEndTime().getMonthValue() == expectedTaskExecution.getEndTime()
+					.getMonthValue()).as("endTime month must be equal").isTrue();
 		}
 		assertThat(actualTaskExecution.getExitCode()).as("exitCode must be equal")
 				.isEqualTo(expectedTaskExecution.getExitCode());