diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 767d6a436..15274e58b 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -9,6 +9,8 @@ ### Updated - Added validation for positive integer configuration properties (RowsFetchedPerBlock, BatchInsertSize, etc.) to prevent hangs and errors when set to zero or negative values. - Updated Circuit breaker to be triggered by 429 errors too. +- Log timestamps now explicitly display timezone. +- **[Breaking Change]** `PreparedStatement.setTimestamp(int, Timestamp, Calendar)` now properly applies Calendar timezone conversion using LocalDateTime pattern (inline with `getTimestamp`). Previously Calendar parameter was ineffective. - Refactored chunk download to keep a sliding window of chunk links. The window advances as the main thread consumes chunks. These changes can be enabled using the connection property EnableStreamingChunkProvider=1. The changes are expected to make chunk download faster and robust. - Added separate circuit breaker to handle 429 from SEA connection creation calls, and fall back to Thrift. @@ -17,6 +19,7 @@ - Fix driver crash when using `INTERVAL` types. - Fix connection failure in restricted environments when `LogLevel.OFF` is used. - Fix U2M by including SDK OAuth HTML callback resources. +- Removes dangerous global timezone modification that caused race conditions. - Fix microsecond precision loss in `PreparedStatement.setTimestamp(int,Timestamp, Calendar)` and address thread-safety issues with global timezone modification. - Fix metadata methods (`getColumns`, `getFunctions`, `getPrimaryKeys`, `getImportedKeys`) to return empty ResultSets instead of throwing exceptions when catalog parameter is NULL, for SEA. diff --git a/src/main/java/com/databricks/client/jdbc/Driver.java b/src/main/java/com/databricks/client/jdbc/Driver.java index 5cee61264..83cb1861f 100644 --- a/src/main/java/com/databricks/client/jdbc/Driver.java +++ b/src/main/java/com/databricks/client/jdbc/Driver.java @@ -21,7 +21,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Properties; -import java.util.TimeZone; import java.util.concurrent.CompletableFuture; import java.util.logging.Logger; @@ -39,8 +38,6 @@ public class Driver implements IDatabricksDriver { } public static void main(String[] args) { - TimeZone.setDefault( - TimeZone.getTimeZone("UTC")); // Logging, timestamps are in UTC across the application System.out.printf("The driver {%s} has been initialized.%n", Driver.class); } diff --git a/src/main/java/com/databricks/jdbc/log/Slf4jFormatter.java b/src/main/java/com/databricks/jdbc/log/Slf4jFormatter.java index 3434ba25b..b5f429c99 100644 --- a/src/main/java/com/databricks/jdbc/log/Slf4jFormatter.java +++ b/src/main/java/com/databricks/jdbc/log/Slf4jFormatter.java @@ -1,8 +1,8 @@ package com.databricks.jdbc.log; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.logging.Formatter; import java.util.logging.LogRecord; @@ -12,19 +12,13 @@ */ public class Slf4jFormatter extends Formatter { - private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - private static final SimpleDateFormat dateFormat; - - static { - dateFormat = new SimpleDateFormat(DATE_FORMAT); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } + private static final DateTimeFormatter DATE_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(ZoneId.systemDefault()); /** {@inheritDoc} */ @Override public String format(LogRecord record) { - String timestamp = dateFormat.format(new Date(record.getMillis())); + String timestamp = DATE_FORMATTER.format(Instant.ofEpochMilli(record.getMillis())); String level = record.getLevel().getLocalizedName(); String className = record.getSourceClassName(); String methodName = record.getSourceMethodName(); diff --git a/src/test/java/com/databricks/jdbc/log/Slf4jFormatterTest.java b/src/test/java/com/databricks/jdbc/log/Slf4jFormatterTest.java index 8c2d04548..f564d654e 100644 --- a/src/test/java/com/databricks/jdbc/log/Slf4jFormatterTest.java +++ b/src/test/java/com/databricks/jdbc/log/Slf4jFormatterTest.java @@ -23,24 +23,29 @@ public void setUp() { @Test public void testFormat() { - // Create a sample LogRecord LogRecord record = new LogRecord(Level.INFO, "Test message"); record.setSourceClassName("TestClass"); record.setSourceMethodName("testMethod"); - // Set a specific timestamp for testing Instant instant = Instant.parse("2021-07-01T00:00:00Z"); record.setInstant(instant); - // Format the log record String formattedLog = formatter.format(record); - // Expected format: "yyyy-MM-dd HH:mm:ss LEVEL ClassName#methodName - message" + // Use system default timezone (matches formatter) + TimeZone formatterZone = TimeZone.getDefault(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + dateFormat.setTimeZone(formatterZone); + String expectedTimestamp = dateFormat.format(Date.from(instant)); + + // Use SHORT display name instead of ID (Asia/Kolkata → IST) + String expectedZone = formatterZone.getDisplayName(false, TimeZone.SHORT); + String expected = - String.format("%s INFO TestClass#testMethod - Test message%n", expectedTimestamp); + String.format( + "%s %s INFO TestClass#testMethod - Test message%n", expectedTimestamp, expectedZone); assertEquals(expected, formattedLog); }