Skip to content

Commit 4e97a3e

Browse files
committed
Fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.LocalDateTime cannot be cast to java.sql.Timestamp.
Change-Id: I1cba2928b459854a71ff60e2d53f5356c7806f82
1 parent 93e2e2c commit 4e97a3e

File tree

7 files changed

+104
-4
lines changed

7 files changed

+104
-4
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
Version 8.2.0
55

6+
- Fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.LocalDateTime cannot be cast to java.sql.Timestamp.
7+
68
- WL#15747, Remove autoDeserialize feature.
79

810
- Fix for Bug#35358417, MySQL Connector/J is not parsing the sessionStateChanges from the OK_Packet correctly.

src/main/core-api/java/com/mysql/cj/conf/PropertyDefinitions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,9 @@ public enum DatabaseTerm {
552552
new BooleanPropertyDefinition(PropertyKey.preserveInstants, DEFAULT_VALUE_TRUE, RUNTIME_MODIFIABLE,
553553
Messages.getString("ConnectionProperties.preserveInstants"), "8.0.23", CATEGORY_DATETIMES, Integer.MIN_VALUE),
554554

555+
new BooleanPropertyDefinition(PropertyKey.treatMysqlDatetimeAsTimestamp, DEFAULT_VALUE_FALSE, RUNTIME_MODIFIABLE,
556+
Messages.getString("ConnectionProperties.treatMysqlDatetimeAsTimestamp"), "8.2.0", CATEGORY_DATETIMES, Integer.MIN_VALUE),
557+
555558
new BooleanPropertyDefinition(PropertyKey.treatUtilDateAsTimestamp, DEFAULT_VALUE_TRUE, RUNTIME_MODIFIABLE,
556559
Messages.getString("ConnectionProperties.treatUtilDateAsTimestamp"), "5.0.5", CATEGORY_DATETIMES, Integer.MIN_VALUE),
557560

src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ public enum PropertyKey {
236236
traceProtocol("traceProtocol", true), //
237237
trackSessionState("trackSessionState", true), //
238238
transformedBitIsBoolean("transformedBitIsBoolean", true), //
239+
treatMysqlDatetimeAsTimestamp("treatMysqlDatetimeAsTimestamp", true), //
239240
treatUtilDateAsTimestamp("treatUtilDateAsTimestamp", true), //
240241
trustCertificateKeyStorePassword("trustCertificateKeyStorePassword", true), //
241242
trustCertificateKeyStoreType("trustCertificateKeyStoreType", true), //

src/main/core-api/java/com/mysql/cj/result/ValueFactory.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@
4545
*
4646
* @param <T>
4747
* value type
48-
*
49-
* @since 6.0
5048
*/
5149
public interface ValueFactory<T> {
5250

src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ ConnectionProperties.tlsVersions=List of TLS protocols to allow when establishin
971971
ConnectionProperties.traceProtocol=Should the network protocol be logged at the TRACE level?
972972
ConnectionProperties.trackSessionState=Receive server session state changes on query results. These changes are accessible via ''MysqlConnection.getServerSessionStateController()''.
973973
ConnectionProperties.transformedBitIsBoolean=If the driver converts TINYINT(1) to a different type, should it use BOOLEAN instead of BIT?
974+
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''.
974975
ConnectionProperties.treatUtilDateAsTimestamp=Should the driver treat ''java.util.Date'' as a TIMESTAMP for the purposes of ''PreparedStatement.setObject()''?
975976
ConnectionProperties.trustCertificateKeyStorePassword=Password for the trusted root certificates key store.
976977
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.

src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ public class ResultSetImpl extends NativeResultset implements ResultSetInternalM
213213
private ValueFactory<ZonedDateTime> defaultZonedDateTimeValueFactory;
214214

215215
protected RuntimeProperty<Boolean> emulateLocators;
216+
217+
protected boolean treatMysqlDatetimeAsTimestamp = false;
216218
protected boolean yearIsDateType = true;
217219

218220
/**
@@ -264,6 +266,7 @@ public ResultSetImpl(ResultsetRows tuples, JdbcConnection conn, StatementImpl cr
264266
PropertySet pset = this.connection.getPropertySet();
265267
this.emulateLocators = pset.getBooleanProperty(PropertyKey.emulateLocators);
266268
this.padCharsWithSpace = pset.getBooleanProperty(PropertyKey.padCharsWithSpace).getValue();
269+
this.treatMysqlDatetimeAsTimestamp = pset.getBooleanProperty(PropertyKey.treatMysqlDatetimeAsTimestamp).getValue();
267270
this.yearIsDateType = pset.getBooleanProperty(PropertyKey.yearIsDateType).getValue();
268271
this.useUsageAdvisor = pset.getBooleanProperty(PropertyKey.useUsageAdvisor).getValue();
269272
this.gatherPerfMetrics = pset.getBooleanProperty(PropertyKey.gatherPerfMetrics).getValue();
@@ -1213,7 +1216,7 @@ public Object getObject(int columnIndex) throws SQLException {
12131216
return getTimestamp(columnIndex);
12141217

12151218
case DATETIME:
1216-
return getLocalDateTime(columnIndex);
1219+
return this.treatMysqlDatetimeAsTimestamp ? getTimestamp(columnIndex) : getLocalDateTime(columnIndex);
12171220

12181221
default:
12191222
return getString(columnIndex);

src/test/java/testsuite/regression/ResultSetRegressionTest.java

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
import java.util.concurrent.TimeUnit;
9494

9595
import javax.sql.rowset.CachedRowSet;
96+
import javax.sql.rowset.JdbcRowSet;
97+
import javax.sql.rowset.RowSetFactory;
98+
import javax.sql.rowset.RowSetProvider;
9699

97100
import org.junit.jupiter.api.Disabled;
98101
import org.junit.jupiter.api.Test;
@@ -8047,7 +8050,7 @@ void testBug102678() throws Exception {
80478050
}
80488051

80498052
/**
8050-
* Tests for Bug#68608 (Bug#16690898), UpdatableResultSet does not properly handle unsigned primary key.
8053+
* Tests fix for Bug#68608 (Bug#16690898), UpdatableResultSet does not properly handle unsigned primary key.
80518054
*
80528055
* @throws Exception
80538056
*/
@@ -8108,4 +8111,93 @@ public void testBug68608() throws Exception {
81088111
assertFalse(this.rs.next());
81098112
}
81108113

8114+
/**
8115+
* Tests fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.LocalDateTime cannot be cast to java.sql.Timestamp.
8116+
*
8117+
* Was failing in CachedRowSet.getDate() and CachedRowSet.getTimestamp() because ResultSet.getObject() returns java.time.LocalDateTime while the code in
8118+
* CachedRowSetImpl tries to cast the value to java.sql.Timestamp.
8119+
* See also: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/com/sun/rowset/CachedRowSetImpl.java#l2140
8120+
* See also: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/com/sun/rowset/CachedRowSetImpl.java#l619
8121+
*
8122+
* @throws Exception
8123+
*/
8124+
@Test
8125+
void testBug107215() throws Exception {
8126+
createTable("testBug107215", "(dt DATETIME, ts TIMESTAMP)");
8127+
this.stmt.execute("INSERT INTO testBug107215 VALUES(NOW(), NOW())");
8128+
8129+
final String sql = "SELECT * FROM testBug107215";
8130+
final String testDbUrl = dbUrl + (dbUrl.contains("?") ? "&" : "?");
8131+
8132+
try (Connection testConn = getConnectionWithProps("treatMysqlDatetimeAsTimestamp=false")) {
8133+
Statement testStmt = testConn.createStatement();
8134+
this.rs = testStmt.executeQuery(sql);
8135+
assertTrue(this.rs.next());
8136+
assertEquals(LocalDateTime.class, this.rs.getObject(1).getClass());
8137+
assertEquals(Timestamp.class, this.rs.getObject(2).getClass());
8138+
assertEquals(Date.class, this.rs.getDate(1).getClass());
8139+
assertEquals(Date.class, this.rs.getDate(2).getClass());
8140+
assertEquals(Timestamp.class, this.rs.getTimestamp(1).getClass());
8141+
assertEquals(Timestamp.class, this.rs.getTimestamp(2).getClass());
8142+
8143+
RowSetFactory rowSetFact = RowSetProvider.newFactory();
8144+
JdbcRowSet testRowSet1 = rowSetFact.createJdbcRowSet();
8145+
testRowSet1.setCommand(sql);
8146+
testRowSet1.setUrl(testDbUrl + "treatMysqlDatetimeAsTimestamp=false");
8147+
testRowSet1.execute();
8148+
assertTrue(testRowSet1.next());
8149+
assertEquals(LocalDateTime.class, testRowSet1.getObject(1).getClass());
8150+
assertEquals(Timestamp.class, testRowSet1.getObject(2).getClass());
8151+
assertEquals(Date.class, testRowSet1.getDate(1).getClass());
8152+
assertEquals(Date.class, testRowSet1.getDate(2).getClass());
8153+
assertEquals(Timestamp.class, testRowSet1.getTimestamp(1).getClass());
8154+
assertEquals(Timestamp.class, testRowSet1.getTimestamp(2).getClass());
8155+
8156+
CachedRowSet testRowSet2 = rowSetFact.createCachedRowSet();
8157+
testRowSet2.populate(testStmt.executeQuery(sql));
8158+
assertTrue(testRowSet2.next());
8159+
assertEquals(LocalDateTime.class, testRowSet2.getObject(1).getClass());
8160+
assertEquals(Timestamp.class, testRowSet2.getObject(2).getClass());
8161+
assertThrows(ClassCastException.class, () -> testRowSet2.getDate(1).getClass()); // Non-expected behavior.
8162+
assertEquals(Date.class, testRowSet2.getDate(2).getClass());
8163+
assertThrows(ClassCastException.class, () -> testRowSet2.getTimestamp(1).getClass()); // Non-expected behavior.
8164+
assertEquals(Timestamp.class, testRowSet2.getTimestamp(2).getClass());
8165+
}
8166+
8167+
try (Connection testConn = getConnectionWithProps("treatMysqlDatetimeAsTimestamp=true")) {
8168+
Statement testStmt = testConn.createStatement();
8169+
this.rs = testStmt.executeQuery(sql);
8170+
assertTrue(this.rs.next());
8171+
assertEquals(Timestamp.class, this.rs.getObject(1).getClass());
8172+
assertEquals(Timestamp.class, this.rs.getObject(2).getClass());
8173+
assertEquals(Date.class, this.rs.getDate(1).getClass());
8174+
assertEquals(Date.class, this.rs.getDate(2).getClass());
8175+
assertEquals(Timestamp.class, this.rs.getTimestamp(1).getClass());
8176+
assertEquals(Timestamp.class, this.rs.getTimestamp(2).getClass());
8177+
8178+
RowSetFactory rowSetFact = RowSetProvider.newFactory();
8179+
JdbcRowSet testRowSet1 = rowSetFact.createJdbcRowSet();
8180+
testRowSet1.setCommand(sql);
8181+
testRowSet1.setUrl(testDbUrl + "treatMysqlDatetimeAsTimestamp=true");
8182+
testRowSet1.execute();
8183+
assertTrue(testRowSet1.next());
8184+
assertEquals(Timestamp.class, testRowSet1.getObject(1).getClass());
8185+
assertEquals(Timestamp.class, testRowSet1.getObject(2).getClass());
8186+
assertEquals(Date.class, testRowSet1.getDate(1).getClass());
8187+
assertEquals(Date.class, testRowSet1.getDate(2).getClass());
8188+
assertEquals(Timestamp.class, testRowSet1.getTimestamp(1).getClass());
8189+
assertEquals(Timestamp.class, testRowSet1.getTimestamp(2).getClass());
8190+
8191+
CachedRowSet testRowSet2 = rowSetFact.createCachedRowSet();
8192+
testRowSet2.populate(testStmt.executeQuery(sql));
8193+
assertTrue(testRowSet2.next());
8194+
assertEquals(Timestamp.class, testRowSet2.getObject(1).getClass());
8195+
assertEquals(Timestamp.class, testRowSet2.getObject(2).getClass());
8196+
assertEquals(Date.class, testRowSet2.getDate(1).getClass()); // Expected behavior.
8197+
assertEquals(Date.class, testRowSet2.getDate(2).getClass());
8198+
assertEquals(Timestamp.class, testRowSet2.getTimestamp(1).getClass()); // Expected behavior.
8199+
assertEquals(Timestamp.class, testRowSet2.getTimestamp(2).getClass());
8200+
}
8201+
}
8202+
81118203
}

0 commit comments

Comments
 (0)