Skip to content

Commit

Permalink
Fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.Loca…
Browse files Browse the repository at this point in the history
…lDateTime cannot be cast to java.sql.Timestamp.

Change-Id: I1cba2928b459854a71ff60e2d53f5356c7806f82
  • Loading branch information
fjssilva committed Aug 31, 2023
1 parent 93e2e2c commit 4e97a3e
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

Version 8.2.0

- Fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.LocalDateTime cannot be cast to java.sql.Timestamp.

- WL#15747, Remove autoDeserialize feature.

- Fix for Bug#35358417, MySQL Connector/J is not parsing the sessionStateChanges from the OK_Packet correctly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ public enum DatabaseTerm {
new BooleanPropertyDefinition(PropertyKey.preserveInstants, DEFAULT_VALUE_TRUE, RUNTIME_MODIFIABLE,
Messages.getString("ConnectionProperties.preserveInstants"), "8.0.23", CATEGORY_DATETIMES, Integer.MIN_VALUE),

new BooleanPropertyDefinition(PropertyKey.treatMysqlDatetimeAsTimestamp, DEFAULT_VALUE_FALSE, RUNTIME_MODIFIABLE,
Messages.getString("ConnectionProperties.treatMysqlDatetimeAsTimestamp"), "8.2.0", CATEGORY_DATETIMES, Integer.MIN_VALUE),

new BooleanPropertyDefinition(PropertyKey.treatUtilDateAsTimestamp, DEFAULT_VALUE_TRUE, RUNTIME_MODIFIABLE,
Messages.getString("ConnectionProperties.treatUtilDateAsTimestamp"), "5.0.5", CATEGORY_DATETIMES, Integer.MIN_VALUE),

Expand Down
1 change: 1 addition & 0 deletions src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public enum PropertyKey {
traceProtocol("traceProtocol", true), //
trackSessionState("trackSessionState", true), //
transformedBitIsBoolean("transformedBitIsBoolean", true), //
treatMysqlDatetimeAsTimestamp("treatMysqlDatetimeAsTimestamp", true), //
treatUtilDateAsTimestamp("treatUtilDateAsTimestamp", true), //
trustCertificateKeyStorePassword("trustCertificateKeyStorePassword", true), //
trustCertificateKeyStoreType("trustCertificateKeyStoreType", true), //
Expand Down
2 changes: 0 additions & 2 deletions src/main/core-api/java/com/mysql/cj/result/ValueFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@
*
* @param <T>
* value type
*
* @since 6.0
*/
public interface ValueFactory<T> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,7 @@ ConnectionProperties.tlsVersions=List of TLS protocols to allow when establishin
ConnectionProperties.traceProtocol=Should the network protocol be logged at the TRACE level?
ConnectionProperties.trackSessionState=Receive server session state changes on query results. These changes are accessible via ''MysqlConnection.getServerSessionStateController()''.
ConnectionProperties.transformedBitIsBoolean=If the driver converts TINYINT(1) to a different type, should it use BOOLEAN instead of BIT?
ConnectionProperties.treatMysqlDatetimeAsTimestamp=Should the driver treat the MySQL DATETIME type as TIMESTAMP for the purposes of ''ResultSet.getObject()''? Enabling this option changes the default MySQL type to Java mapping for DATETIME, from ''java.time.LocalDateTime'' to ''java.sql.Timestamp''. Given the nature of the DATETIME type and its inability to represent instant values, it is not advisable to enable this option unless the driver is used with a framework or API that expects exclusively objects from the default database to Java types mapping, which is the case of, for example, ''javax.sql.rowset.CachedRowSet''.
ConnectionProperties.treatUtilDateAsTimestamp=Should the driver treat ''java.util.Date'' as a TIMESTAMP for the purposes of ''PreparedStatement.setObject()''?
ConnectionProperties.trustCertificateKeyStorePassword=Password for the trusted root certificates key store.
ConnectionProperties.trustCertificateKeyStoreType=Key store type for trusted root certificates.[CR]Null or empty means use the default, which is "JKS". Standard key store types supported by the JVM are "JKS" and "PKCS12", your environment may have more available depending on what security providers are installed and available to the JVM.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ public class ResultSetImpl extends NativeResultset implements ResultSetInternalM
private ValueFactory<ZonedDateTime> defaultZonedDateTimeValueFactory;

protected RuntimeProperty<Boolean> emulateLocators;

protected boolean treatMysqlDatetimeAsTimestamp = false;
protected boolean yearIsDateType = true;

/**
Expand Down Expand Up @@ -264,6 +266,7 @@ public ResultSetImpl(ResultsetRows tuples, JdbcConnection conn, StatementImpl cr
PropertySet pset = this.connection.getPropertySet();
this.emulateLocators = pset.getBooleanProperty(PropertyKey.emulateLocators);
this.padCharsWithSpace = pset.getBooleanProperty(PropertyKey.padCharsWithSpace).getValue();
this.treatMysqlDatetimeAsTimestamp = pset.getBooleanProperty(PropertyKey.treatMysqlDatetimeAsTimestamp).getValue();
this.yearIsDateType = pset.getBooleanProperty(PropertyKey.yearIsDateType).getValue();
this.useUsageAdvisor = pset.getBooleanProperty(PropertyKey.useUsageAdvisor).getValue();
this.gatherPerfMetrics = pset.getBooleanProperty(PropertyKey.gatherPerfMetrics).getValue();
Expand Down Expand Up @@ -1213,7 +1216,7 @@ public Object getObject(int columnIndex) throws SQLException {
return getTimestamp(columnIndex);

case DATETIME:
return getLocalDateTime(columnIndex);
return this.treatMysqlDatetimeAsTimestamp ? getTimestamp(columnIndex) : getLocalDateTime(columnIndex);

default:
return getString(columnIndex);
Expand Down
94 changes: 93 additions & 1 deletion src/test/java/testsuite/regression/ResultSetRegressionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
import java.util.concurrent.TimeUnit;

import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.JdbcRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -8047,7 +8050,7 @@ void testBug102678() throws Exception {
}

/**
* Tests for Bug#68608 (Bug#16690898), UpdatableResultSet does not properly handle unsigned primary key.
* Tests fix for Bug#68608 (Bug#16690898), UpdatableResultSet does not properly handle unsigned primary key.
*
* @throws Exception
*/
Expand Down Expand Up @@ -8108,4 +8111,93 @@ public void testBug68608() throws Exception {
assertFalse(this.rs.next());
}

/**
* Tests fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.LocalDateTime cannot be cast to java.sql.Timestamp.
*
* Was failing in CachedRowSet.getDate() and CachedRowSet.getTimestamp() because ResultSet.getObject() returns java.time.LocalDateTime while the code in
* CachedRowSetImpl tries to cast the value to java.sql.Timestamp.
* See also: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/com/sun/rowset/CachedRowSetImpl.java#l2140
* See also: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/com/sun/rowset/CachedRowSetImpl.java#l619
*
* @throws Exception
*/
@Test
void testBug107215() throws Exception {
createTable("testBug107215", "(dt DATETIME, ts TIMESTAMP)");
this.stmt.execute("INSERT INTO testBug107215 VALUES(NOW(), NOW())");

final String sql = "SELECT * FROM testBug107215";
final String testDbUrl = dbUrl + (dbUrl.contains("?") ? "&" : "?");

try (Connection testConn = getConnectionWithProps("treatMysqlDatetimeAsTimestamp=false")) {
Statement testStmt = testConn.createStatement();
this.rs = testStmt.executeQuery(sql);
assertTrue(this.rs.next());
assertEquals(LocalDateTime.class, this.rs.getObject(1).getClass());
assertEquals(Timestamp.class, this.rs.getObject(2).getClass());
assertEquals(Date.class, this.rs.getDate(1).getClass());
assertEquals(Date.class, this.rs.getDate(2).getClass());
assertEquals(Timestamp.class, this.rs.getTimestamp(1).getClass());
assertEquals(Timestamp.class, this.rs.getTimestamp(2).getClass());

RowSetFactory rowSetFact = RowSetProvider.newFactory();
JdbcRowSet testRowSet1 = rowSetFact.createJdbcRowSet();
testRowSet1.setCommand(sql);
testRowSet1.setUrl(testDbUrl + "treatMysqlDatetimeAsTimestamp=false");
testRowSet1.execute();
assertTrue(testRowSet1.next());
assertEquals(LocalDateTime.class, testRowSet1.getObject(1).getClass());
assertEquals(Timestamp.class, testRowSet1.getObject(2).getClass());
assertEquals(Date.class, testRowSet1.getDate(1).getClass());
assertEquals(Date.class, testRowSet1.getDate(2).getClass());
assertEquals(Timestamp.class, testRowSet1.getTimestamp(1).getClass());
assertEquals(Timestamp.class, testRowSet1.getTimestamp(2).getClass());

CachedRowSet testRowSet2 = rowSetFact.createCachedRowSet();
testRowSet2.populate(testStmt.executeQuery(sql));
assertTrue(testRowSet2.next());
assertEquals(LocalDateTime.class, testRowSet2.getObject(1).getClass());
assertEquals(Timestamp.class, testRowSet2.getObject(2).getClass());
assertThrows(ClassCastException.class, () -> testRowSet2.getDate(1).getClass()); // Non-expected behavior.
assertEquals(Date.class, testRowSet2.getDate(2).getClass());
assertThrows(ClassCastException.class, () -> testRowSet2.getTimestamp(1).getClass()); // Non-expected behavior.
assertEquals(Timestamp.class, testRowSet2.getTimestamp(2).getClass());
}

try (Connection testConn = getConnectionWithProps("treatMysqlDatetimeAsTimestamp=true")) {
Statement testStmt = testConn.createStatement();
this.rs = testStmt.executeQuery(sql);
assertTrue(this.rs.next());
assertEquals(Timestamp.class, this.rs.getObject(1).getClass());
assertEquals(Timestamp.class, this.rs.getObject(2).getClass());
assertEquals(Date.class, this.rs.getDate(1).getClass());
assertEquals(Date.class, this.rs.getDate(2).getClass());
assertEquals(Timestamp.class, this.rs.getTimestamp(1).getClass());
assertEquals(Timestamp.class, this.rs.getTimestamp(2).getClass());

RowSetFactory rowSetFact = RowSetProvider.newFactory();
JdbcRowSet testRowSet1 = rowSetFact.createJdbcRowSet();
testRowSet1.setCommand(sql);
testRowSet1.setUrl(testDbUrl + "treatMysqlDatetimeAsTimestamp=true");
testRowSet1.execute();
assertTrue(testRowSet1.next());
assertEquals(Timestamp.class, testRowSet1.getObject(1).getClass());
assertEquals(Timestamp.class, testRowSet1.getObject(2).getClass());
assertEquals(Date.class, testRowSet1.getDate(1).getClass());
assertEquals(Date.class, testRowSet1.getDate(2).getClass());
assertEquals(Timestamp.class, testRowSet1.getTimestamp(1).getClass());
assertEquals(Timestamp.class, testRowSet1.getTimestamp(2).getClass());

CachedRowSet testRowSet2 = rowSetFact.createCachedRowSet();
testRowSet2.populate(testStmt.executeQuery(sql));
assertTrue(testRowSet2.next());
assertEquals(Timestamp.class, testRowSet2.getObject(1).getClass());
assertEquals(Timestamp.class, testRowSet2.getObject(2).getClass());
assertEquals(Date.class, testRowSet2.getDate(1).getClass()); // Expected behavior.
assertEquals(Date.class, testRowSet2.getDate(2).getClass());
assertEquals(Timestamp.class, testRowSet2.getTimestamp(1).getClass()); // Expected behavior.
assertEquals(Timestamp.class, testRowSet2.getTimestamp(2).getClass());
}
}

}

0 comments on commit 4e97a3e

Please sign in to comment.