|
31 | 31 | import org.testng.annotations.Test; |
32 | 32 |
|
33 | 33 | import java.math.BigDecimal; |
| 34 | +import java.sql.Connection; |
| 35 | +import java.sql.DriverManager; |
| 36 | +import java.sql.ResultSet; |
| 37 | +import java.sql.Statement; |
34 | 38 | import java.time.LocalDate; |
35 | 39 | import java.time.ZoneId; |
36 | 40 |
|
|
53 | 57 | import static com.google.common.base.Strings.repeat; |
54 | 58 | import static com.google.common.base.Verify.verify; |
55 | 59 | import static java.lang.String.format; |
| 60 | +import static org.testng.Assert.assertEquals; |
| 61 | +import static org.testng.Assert.assertTrue; |
56 | 62 |
|
57 | 63 | @Test |
58 | 64 | public class TestMySqlTypeMapping |
@@ -254,15 +260,154 @@ public void testDate() |
254 | 260 | } |
255 | 261 |
|
256 | 262 | @Test |
257 | | - public void testDatetime() |
| 263 | + public void testDatetimeUnderlyingStorageVerification() |
| 264 | + throws Exception |
258 | 265 | { |
259 | | - // TODO MySQL datetime is not correctly read (see comment in StandardColumnMappings.timestampReadMapping), but testing this is hard because of #7122 |
| 266 | + String jdbcUrl = mysqlContainer.getJdbcUrl(); |
| 267 | + String jdbcUrlWithCredentials = format("%s%suser=%s&password=%s", |
| 268 | + jdbcUrl, |
| 269 | + jdbcUrl.contains("?") ? "&" : "?", |
| 270 | + mysqlContainer.getUsername(), |
| 271 | + mysqlContainer.getPassword()); |
| 272 | + JdbcSqlExecutor jdbcExecutor = new JdbcSqlExecutor(jdbcUrlWithCredentials); |
| 273 | + |
| 274 | + try { |
| 275 | + jdbcExecutor.execute("CREATE TABLE tpch.test_datetime_storage (" + |
| 276 | + "id INT PRIMARY KEY, " + |
| 277 | + "dt DATETIME(6), " + |
| 278 | + "source VARCHAR(10))"); |
| 279 | + |
| 280 | + // MySQL insertion, MySQL retrieval, and Presto retrieval all agree on wall clock time |
| 281 | + jdbcExecutor.execute("INSERT INTO tpch.test_datetime_storage VALUES (1, '1970-01-01 00:00:00.000000', 'jdbc')"); |
| 282 | + |
| 283 | + try (Connection conn = DriverManager.getConnection(jdbcUrlWithCredentials); |
| 284 | + Statement stmt = conn.createStatement(); |
| 285 | + ResultSet rs = stmt.executeQuery("SELECT CAST(dt AS CHAR) FROM tpch.test_datetime_storage WHERE id = 1")) { |
| 286 | + assertTrue(rs.next(), "Expected one row"); |
| 287 | + String dbValue1 = rs.getString(1); |
| 288 | + assertEquals(dbValue1, "1970-01-01 00:00:00.000000", "JDBC insert should store wall clock time 1970-01-01 00:00:00 in DB"); |
| 289 | + } |
| 290 | + |
| 291 | + Session session = Session.builder(getQueryRunner().getDefaultSession()) |
| 292 | + .setSystemProperty("legacy_timestamp", "false") |
| 293 | + .build(); |
| 294 | + assertQuery(session, |
| 295 | + "SELECT dt FROM mysql.tpch.test_datetime_storage WHERE id = 1", |
| 296 | + "VALUES TIMESTAMP '1970-01-01 00:00:00.000000'"); |
| 297 | + |
| 298 | + // Presto insertion, retrieval via MySQL, and retrieval via Presto all agree on wall clock time |
| 299 | + assertUpdate(session, "INSERT INTO mysql.tpch.test_datetime_storage VALUES (2, TIMESTAMP '2023-06-15 14:30:00.000000', 'presto')", 1); |
| 300 | + |
| 301 | + try (Connection conn = DriverManager.getConnection(jdbcUrlWithCredentials); |
| 302 | + Statement stmt = conn.createStatement(); |
| 303 | + ResultSet rs = stmt.executeQuery("SELECT CAST(dt AS CHAR) FROM tpch.test_datetime_storage WHERE id = 2")) { |
| 304 | + assertTrue(rs.next(), "Expected one row"); |
| 305 | + String dbValue2 = rs.getString(1); |
| 306 | + assertEquals(dbValue2, "2023-06-15 14:30:00.000000", "Presto insert should store wall clock time 2023-06-15 14:30:00 in DB"); |
| 307 | + } |
| 308 | + |
| 309 | + assertQuery(session, |
| 310 | + "SELECT dt FROM mysql.tpch.test_datetime_storage WHERE id = 2", |
| 311 | + "VALUES TIMESTAMP '2023-06-15 14:30:00.000000'"); |
| 312 | + |
| 313 | + for (String timeZoneId : ImmutableList.of("UTC", "America/New_York", "Asia/Tokyo", "Europe/Warsaw")) { |
| 314 | + Session sessionWithTimezone = Session.builder(getQueryRunner().getDefaultSession()) |
| 315 | + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(timeZoneId)) |
| 316 | + .setSystemProperty("legacy_timestamp", "false") |
| 317 | + .build(); |
| 318 | + |
| 319 | + assertQuery(sessionWithTimezone, |
| 320 | + "SELECT dt FROM mysql.tpch.test_datetime_storage WHERE id = 1", |
| 321 | + "VALUES TIMESTAMP '1970-01-01 00:00:00.000000'"); |
| 322 | + |
| 323 | + assertQuery(sessionWithTimezone, |
| 324 | + "SELECT dt FROM mysql.tpch.test_datetime_storage WHERE id = 2", |
| 325 | + "VALUES TIMESTAMP '2023-06-15 14:30:00.000000'"); |
| 326 | + } |
| 327 | + } |
| 328 | + finally { |
| 329 | + jdbcExecutor.execute("DROP TABLE IF EXISTS tpch.test_datetime_storage"); |
| 330 | + } |
260 | 331 | } |
261 | 332 |
|
262 | 333 | @Test |
263 | | - public void testTimestamp() |
| 334 | + public void testDatetimeLegacyUnderlyingStorageVerification() |
| 335 | + throws Exception |
264 | 336 | { |
265 | | - // TODO MySQL timestamp is not correctly read (see comment in StandardColumnMappings.timestampReadMapping), but testing this is hard because of #7122 |
| 337 | + String jdbcUrl = mysqlContainer.getJdbcUrl(); |
| 338 | + String jdbcUrlWithCredentials = format("%s%suser=%s&password=%s", |
| 339 | + jdbcUrl, |
| 340 | + jdbcUrl.contains("?") ? "&" : "?", |
| 341 | + mysqlContainer.getUsername(), |
| 342 | + mysqlContainer.getPassword()); |
| 343 | + JdbcSqlExecutor jdbcExecutor = new JdbcSqlExecutor(jdbcUrlWithCredentials); |
| 344 | + |
| 345 | + try { |
| 346 | + jdbcExecutor.execute("CREATE TABLE tpch.test_datetime_legacy_storage (" + |
| 347 | + "id INT PRIMARY KEY, " + |
| 348 | + "dt DATETIME(6), " + |
| 349 | + "source VARCHAR(10))"); |
| 350 | + |
| 351 | + // MySQL insertion and MySQL retrieval agree, Presto incorrectly interprets DB value due to legacy mode |
| 352 | + jdbcExecutor.execute("INSERT INTO tpch.test_datetime_legacy_storage VALUES (1, '1970-01-01 00:00:00.000000', 'jdbc')"); |
| 353 | + |
| 354 | + // Prove that the value is 1970-01-01 00:00:00 by reading directly from the DB via JDBC |
| 355 | + try (Connection conn = DriverManager.getConnection(jdbcUrlWithCredentials); |
| 356 | + Statement stmt = conn.createStatement(); |
| 357 | + ResultSet rs = stmt.executeQuery("SELECT CAST(dt AS CHAR) FROM tpch.test_datetime_legacy_storage WHERE id = 1")) { |
| 358 | + assertTrue(rs.next(), "Expected one row"); |
| 359 | + String dbValue1 = rs.getString(1); |
| 360 | + assertEquals(dbValue1, "1970-01-01 00:00:00.000000", "JDBC insert should store wall clock time 1970-01-01 00:00:00 in DB"); |
| 361 | + } |
| 362 | + |
| 363 | + // In legacy mode, DB value 1970-01-01 00:00:00 is interpreted as if it's in JVM timezone (America/Bahia_Banderas UTC-7) |
| 364 | + // and then converted to the session timezone. Since both are the same (America/Bahia_Banderas), |
| 365 | + // the offset comes from treating the wall-clock DB time as UTC, resulting in 1969-12-31 20:00:00 |
| 366 | + Session legacySession = Session.builder(getQueryRunner().getDefaultSession()) |
| 367 | + .setSystemProperty("legacy_timestamp", "true") |
| 368 | + .build(); |
| 369 | + assertQuery(legacySession, |
| 370 | + "SELECT dt FROM mysql.tpch.test_datetime_legacy_storage WHERE id = 1", |
| 371 | + "VALUES TIMESTAMP '1969-12-31 20:00:00.000000'"); |
| 372 | + |
| 373 | + // Presto insertion with legacy mode, verify DB storage via JDBC (should apply JVM timezone conversion during write) |
| 374 | + assertUpdate(legacySession, "INSERT INTO mysql.tpch.test_datetime_legacy_storage VALUES (2, TIMESTAMP '2023-06-15 14:30:00.000000', 'presto')", 1); |
| 375 | + |
| 376 | + try (Connection conn = DriverManager.getConnection(jdbcUrlWithCredentials); |
| 377 | + Statement stmt = conn.createStatement(); |
| 378 | + ResultSet rs = stmt.executeQuery("SELECT CAST(dt AS CHAR) FROM tpch.test_datetime_legacy_storage WHERE id = 2")) { |
| 379 | + assertTrue(rs.next(), "Expected one row"); |
| 380 | + String dbValue2 = rs.getString(1); |
| 381 | + // JVM timezone is America/Bahia_Banderas (UTC-7), so 2023-06-15 14:30:00 becomes 2023-06-14 19:30:00.000000 |
| 382 | + assertEquals(dbValue2, "2023-06-14 19:30:00.000000", "Legacy mode applies timezone conversion during write, expected 2023-06-14 19:30:00.000000"); |
| 383 | + } |
| 384 | + |
| 385 | + // Verify Presto reads it back correctly in legacy mode (round-trip should work) |
| 386 | + assertQuery(legacySession, |
| 387 | + "SELECT dt FROM mysql.tpch.test_datetime_legacy_storage WHERE id = 2", |
| 388 | + "VALUES TIMESTAMP '2023-06-15 14:30:00.000000'"); |
| 389 | + |
| 390 | + // DB value 1970-01-01 00:00:00 is interpreted as JVM timezone (America/Bahia_Banderas UTC-7), |
| 391 | + // then converted to the session timezone |
| 392 | + Session legacyUtcSession = Session.builder(getQueryRunner().getDefaultSession()) |
| 393 | + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey("UTC")) |
| 394 | + .setSystemProperty("legacy_timestamp", "true") |
| 395 | + .build(); |
| 396 | + assertQuery(legacyUtcSession, |
| 397 | + "SELECT dt FROM mysql.tpch.test_datetime_legacy_storage WHERE id = 1", |
| 398 | + "VALUES TIMESTAMP '1970-01-01 07:00:00.000000'"); |
| 399 | + |
| 400 | + Session legacyTokyoSession = Session.builder(getQueryRunner().getDefaultSession()) |
| 401 | + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey("Asia/Tokyo")) |
| 402 | + .setSystemProperty("legacy_timestamp", "true") |
| 403 | + .build(); |
| 404 | + assertQuery(legacyTokyoSession, |
| 405 | + "SELECT dt FROM mysql.tpch.test_datetime_legacy_storage WHERE id = 1", |
| 406 | + "VALUES TIMESTAMP '1970-01-01 16:00:00.000000'"); |
| 407 | + } |
| 408 | + finally { |
| 409 | + jdbcExecutor.execute("DROP TABLE IF EXISTS tpch.test_datetime_legacy_storage"); |
| 410 | + } |
266 | 411 | } |
267 | 412 |
|
268 | 413 | private void testUnsupportedDataType(String databaseDataType) |
|
0 commit comments