Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Timestamp fixes for Oracle, MySQL and MariaDB #461

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ public Scheduler scheduler(DbSchedulerCustomizer customizer, StatsRegistry regis
.jdbcCustomization()
.orElse(new AutodetectJdbcCustomization(transactionalDataSource)));

if (config.isAlwaysPersistTimestampInUtc()) {
builder.alwaysPersistTimestampInUTC();
}

if (config.isImmediateExecutionEnabled()) {
builder.enableImmediateExecution();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ public class DbSchedulerProperties {
@DurationUnit(SECONDS)
private Duration shutdownMaxWait = SchedulerBuilder.SHUTDOWN_MAX_WAIT;

/**
* Store timestamps in UTC timezone even though the schema supports storing timezone information
*/
private boolean alwaysPersistTimestampInUtc = false;

/** Which log level to use when logging task failures. Defaults to {@link LogLevel#DEBUG}. */
private LogLevel failureLoggerLevel = SchedulerBuilder.DEFAULT_FAILURE_LOG_LEVEL;

Expand Down Expand Up @@ -228,4 +233,12 @@ public void setPollingStrategyUpperLimitFractionOfThreads(
double pollingStrategyUpperLimitFractionOfThreads) {
this.pollingStrategyUpperLimitFractionOfThreads = pollingStrategyUpperLimitFractionOfThreads;
}

public boolean isAlwaysPersistTimestampInUtc() {
return alwaysPersistTimestampInUtc;
}

public void setAlwaysPersistTimestampInUtc(boolean alwaysPersistTimestampInUTC) {
this.alwaysPersistTimestampInUtc = alwaysPersistTimestampInUTC;
}
}
4 changes: 2 additions & 2 deletions db-scheduler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>8.2.1.jre8</version>
<version>12.4.2.jre8</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -287,7 +287,7 @@
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.4</version>
<version>3.3.2</version>
<scope>test</scope>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class SchedulerBuilder {
protected LogLevel logLevel = DEFAULT_FAILURE_LOG_LEVEL;
protected boolean logStackTrace = LOG_STACK_TRACE_ON_FAILURE;
private boolean registerShutdownHook = false;
private boolean alwaysPersistTimestampInUTC = false;

public SchedulerBuilder(DataSource dataSource, List<Task<?>> knownTasks) {
this.dataSource = dataSource;
Expand Down Expand Up @@ -156,6 +157,11 @@ public SchedulerBuilder jdbcCustomization(JdbcCustomization jdbcCustomization) {
return this;
}

public SchedulerBuilder alwaysPersistTimestampInUTC() {
this.alwaysPersistTimestampInUTC = true;
return this;
}

public SchedulerBuilder shutdownMaxWait(Duration shutdownMaxWait) {
this.shutdownMaxWait = shutdownMaxWait;
return this;
Expand Down Expand Up @@ -208,7 +214,8 @@ public Scheduler build() {
final TaskResolver taskResolver = new TaskResolver(statsRegistry, clock, knownTasks);
final JdbcCustomization jdbcCustomization =
ofNullable(this.jdbcCustomization)
.orElseGet(() -> new AutodetectJdbcCustomization(dataSource));
.orElseGet(
() -> new AutodetectJdbcCustomization(dataSource, alwaysPersistTimestampInUTC));
final JdbcTaskRepository schedulerTaskRepository =
new JdbcTaskRepository(
dataSource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ public class AutodetectJdbcCustomization implements JdbcCustomization {
private final JdbcCustomization jdbcCustomization;

public AutodetectJdbcCustomization(DataSource dataSource) {
JdbcCustomization detectedCustomization = new DefaultJdbcCustomization();
this(dataSource, false);
}

public AutodetectJdbcCustomization(DataSource dataSource, boolean persistTimestampInUTC) {
JdbcCustomization detectedCustomization = new DefaultJdbcCustomization(persistTimestampInUTC);

LOG.debug("Detecting database...");
try (Connection c = dataSource.getConnection()) {
Expand All @@ -44,25 +48,25 @@ public AutodetectJdbcCustomization(DataSource dataSource) {

if (databaseProductName.equals(MICROSOFT_SQL_SERVER)) {
LOG.info("Using MSSQL jdbc-overrides.");
detectedCustomization = new MssqlJdbcCustomization();
detectedCustomization = new MssqlJdbcCustomization(true);
} else if (databaseProductName.equals(POSTGRESQL)) {
LOG.info("Using PostgreSQL jdbc-overrides.");
detectedCustomization = new PostgreSqlJdbcCustomization();
detectedCustomization = new PostgreSqlJdbcCustomization(false, persistTimestampInUTC);
} else if (databaseProductName.contains(ORACLE)) {
LOG.info("Using Oracle jdbc-overrides.");
detectedCustomization = new OracleJdbcCustomization();
detectedCustomization = new OracleJdbcCustomization(persistTimestampInUTC);
} else if (databaseProductName.contains(MARIADB)) {
LOG.info("Using MariaDB jdbc-overrides.");
detectedCustomization = new MariaDBJdbcCustomization();
detectedCustomization = new MariaDBJdbcCustomization(true);
} else if (databaseProductName.contains(MYSQL)) {
int databaseMajorVersion = c.getMetaData().getDatabaseMajorVersion();
String dbVersion = c.getMetaData().getDatabaseProductVersion();
if (databaseMajorVersion >= 8) {
LOG.info("Using MySQL jdbc-overrides version 8 and later. (v {})", dbVersion);
detectedCustomization = new MySQL8JdbcCustomization();
detectedCustomization = new MySQL8JdbcCustomization(true);
} else {
LOG.info("Using MySQL jdbc-overrides for version older than 8. (v {})", dbVersion);
detectedCustomization = new MySQLJdbcCustomization();
detectedCustomization = new MySQLJdbcCustomization(true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,46 @@
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Optional;
import java.util.TimeZone;

public class DefaultJdbcCustomization implements JdbcCustomization {
public static final Calendar UTC = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
private final boolean persistTimestampInUTC;

public DefaultJdbcCustomization(boolean persistTimestampInUTC) {

this.persistTimestampInUTC = persistTimestampInUTC;
}

@Override
public void setInstant(PreparedStatement p, int index, Instant value) throws SQLException {
p.setTimestamp(index, value != null ? Timestamp.from(value) : null);
if (value == null) {
p.setTimestamp(index, null);
return;
}

if (persistTimestampInUTC) {
p.setTimestamp(index, Timestamp.from(value), UTC);
} else {
p.setTimestamp(index, Timestamp.from(value));
}
}

@Override
public Instant getInstant(ResultSet rs, String columnName) throws SQLException {
return Optional.ofNullable(rs.getTimestamp(columnName)).map(Timestamp::toInstant).orElse(null);
if (persistTimestampInUTC) {
return Optional.ofNullable(rs.getTimestamp(columnName, UTC))
.map(Timestamp::toInstant)
.orElse(null);
} else {
return Optional.ofNullable(rs.getTimestamp(columnName))
.map(Timestamp::toInstant)
.orElse(null);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
import java.util.List;
import java.util.function.Supplier;

class JdbcTaskRepositoryContext {
final TaskResolver taskResolver;
final String tableName;
final SchedulerName schedulerName;
final JdbcRunner jdbcRunner;
final Supplier<ResultSetMapper<List<Execution>>> resultSetMapper;
public class JdbcTaskRepositoryContext {
public final TaskResolver taskResolver;
public final String tableName;
public final SchedulerName schedulerName;
public final JdbcRunner jdbcRunner;
public final Supplier<ResultSetMapper<List<Execution>>> resultSetMapper;

JdbcTaskRepositoryContext(
TaskResolver taskResolver,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,21 @@

import static com.github.kagkarlsson.scheduler.jdbc.Queries.selectForUpdate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MariaDBJdbcCustomization extends DefaultJdbcCustomization {
private static final Logger LOG = LoggerFactory.getLogger(MariaDBJdbcCustomization.class);

public MariaDBJdbcCustomization(boolean persistTimestampInUTC) {
super(persistTimestampInUTC);
if (!persistTimestampInUTC) {
LOG.warn(
"{} does not support persistent timezones. "
+ "It is recommended to store time in UTC to avoid issues with for example DST",
getClass().getName());
}
}

@Override
public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,29 @@

import static com.github.kagkarlsson.scheduler.jdbc.Queries.selectForUpdate;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Calendar;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MssqlJdbcCustomization extends DefaultJdbcCustomization {
private static final Logger LOG = LoggerFactory.getLogger(MssqlJdbcCustomization.class);

@Override
public String getName() {
return "MSSQL";
public MssqlJdbcCustomization() {
super(true);
}

public MssqlJdbcCustomization(boolean persistTimestampInUTC) {
super(persistTimestampInUTC);
if (!persistTimestampInUTC) {
LOG.warn(
"{} must explicitly specify timezone when persisting a timestamp. "
+ "Persisting timestamp with undefined timezone is not recommended and will likely cause issues",
getClass().getName());
}
}

@Override
public void setInstant(PreparedStatement p, int index, Instant value) throws SQLException {
p.setTimestamp(
index,
value != null ? Timestamp.from(value) : null,
Calendar.getInstance(TimeZone.getTimeZone("UTC")));
public String getName() {
return "MSSQL";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,21 @@

import static com.github.kagkarlsson.scheduler.jdbc.Queries.selectForUpdate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MySQL8JdbcCustomization extends DefaultJdbcCustomization {
private static final Logger LOG = LoggerFactory.getLogger(MySQL8JdbcCustomization.class);

public MySQL8JdbcCustomization(boolean persistTimestampInUTC) {
super(persistTimestampInUTC);
if (!persistTimestampInUTC) {
LOG.warn(
"{} does not support persistent timezones. "
+ "It is recommended to store time in UTC to avoid issues with for example DST",
getClass().getName());
}
}

@Override
public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,21 @@
*/
package com.github.kagkarlsson.scheduler.jdbc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MySQLJdbcCustomization extends DefaultJdbcCustomization {
private static final Logger LOG = LoggerFactory.getLogger(MySQLJdbcCustomization.class);

public MySQLJdbcCustomization(boolean persistTimestampInUTC) {
super(persistTimestampInUTC);
if (!persistTimestampInUTC) {
LOG.warn(
"{} does not support persistent timezones. "
+ "It is recommended to store time in UTC to avoid issues with for example DST",
getClass().getName());
}
}

@Override
public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

public class OracleJdbcCustomization extends DefaultJdbcCustomization {

public OracleJdbcCustomization(boolean persistTimestampInUTC) {
super(persistTimestampInUTC);
}

@Override
public String getName() {
return "Oracle";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@
public class PostgreSqlJdbcCustomization extends DefaultJdbcCustomization {
private final boolean useGenericLockAndFetch;

public PostgreSqlJdbcCustomization() {
this(false);
}

public PostgreSqlJdbcCustomization(boolean useGenericLockAndFetch) {
public PostgreSqlJdbcCustomization(
boolean useGenericLockAndFetch, boolean persistTimestampInUTC) {
super(persistTimestampInUTC);
this.useGenericLockAndFetch = useGenericLockAndFetch;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public ManualScheduler build() {
new JdbcTaskRepository(
dataSource,
true,
new DefaultJdbcCustomization(),
new DefaultJdbcCustomization(false),
tableName,
taskResolver,
new SchedulerName.Fixed("manual"),
Expand All @@ -85,7 +85,7 @@ public ManualScheduler build() {
new JdbcTaskRepository(
dataSource,
commitWhenAutocommitDisabled,
new DefaultJdbcCustomization(),
new DefaultJdbcCustomization(false),
tableName,
taskResolver,
new SchedulerName.Fixed("manual"),
Expand Down
Loading
Loading