This is an automated email from the ASF dual-hosted git repository.

MaxGekk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 0c689aed7d6c [SPARK-57555][SQL][FOLLOWUP] Add escape-hatch conf to 
keep legacy JDBC TIME-to-timestamp mapping
0c689aed7d6c is described below

commit 0c689aed7d6cd82d20a68ce4faea227e770f71f4
Author: Wenchen Fan <[email protected]>
AuthorDate: Wed Jul 1 09:11:33 2026 +0200

    [SPARK-57555][SQL][FOLLOWUP] Add escape-hatch conf to keep legacy JDBC 
TIME-to-timestamp mapping
    
    ### What changes were proposed in this pull request?
    [SPARK-57555](https://issues.apache.org/jira/browse/SPARK-57555) made the 
built-in JDBC data source read SQL `TIME` columns as `TimeType` (instead of the 
legacy `TimestampType`) when `spark.sql.timeType.enabled` is true. That is a 
schema-inference change for existing JDBC reads: a column that previously came 
back as `TimestampType` now comes back as `TimeType`, with no DDL change by the 
user.
    
    This PR adds an internal escape-hatch conf, 
`spark.sql.legacy.jdbc.timeMapping.enabled` (default `false`), following the 
existing `spark.sql.legacy.<db>.*Mapping.enabled` family. When set to true, 
JDBC `TIME` columns keep the legacy `TimestampType` mapping even when 
`spark.sql.timeType.enabled` is true. It has no effect when 
`spark.sql.timeType.enabled` is false.
    
    ### Why are the changes needed?
    `spark.sql.timeType.enabled` is a single global switch that governs `TIME` 
support across all of Spark. Enabling it flips JDBC `TIME` reads from 
`TimestampType` to `TimeType` in one step, which can silently break workloads 
that depend on the old mapping (downstream casts, comparisons, stored schemas, 
serialization). The escape hatch lets such workloads opt back into the legacy 
JDBC behavior without having to keep the entire `TIME` type disabled.
    
    ### Does this PR introduce any user-facing change?
    No behavioral change by default. The new conf defaults to `false`, 
preserving SPARK-57555 behavior. When explicitly set to `true` (and 
`spark.sql.timeType.enabled` is `true`), JDBC `TIME` columns read as 
`TimestampType` as they did before SPARK-57555.
    
    ### How was this patch tested?
    Added a unit test in `JDBCSuite` asserting that, with 
`spark.sql.timeType.enabled=true`, the column reads as `TimestampType` when the 
escape hatch is on and as `TimeType` when it is off.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Generated-by: Claude Code (Opus 4.8)
    
    Closes #56884 from cloud-fan/dw-jdbc-time-type-legacy-conf.
    
    Authored-by: Wenchen Fan <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
---
 .../org/apache/spark/sql/internal/SQLConf.scala    | 16 +++++++++++++
 .../sql/execution/datasources/jdbc/JdbcUtils.scala |  2 +-
 .../org/apache/spark/sql/jdbc/JDBCSuite.scala      | 26 ++++++++++++++++++++++
 3 files changed, 43 insertions(+), 1 deletion(-)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
index 6776f88ed1ef..91635d346605 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
@@ -6289,6 +6289,19 @@ object SQLConf {
       .booleanConf
       .createWithDefault(false)
 
+  val LEGACY_JDBC_TIME_MAPPING_ENABLED =
+    buildConf("spark.sql.legacy.jdbc.timeMapping.enabled")
+      .internal()
+      .doc("When true, JDBC TIME columns are read as TimestampType (the legacy 
behavior), even " +
+        "when spark.sql.timeType.enabled is true. This is an escape hatch for 
workloads that " +
+        "rely on the old TIME-to-timestamp mapping. When false, JDBC TIME 
columns are read as " +
+        "TimeType if spark.sql.timeType.enabled is true. Has no effect when " +
+        "spark.sql.timeType.enabled is false.")
+      .version("4.3.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
   val PYTHON_FILTER_PUSHDOWN_ENABLED = 
buildConf("spark.sql.python.filterPushdown.enabled")
     .internal()
     .doc("When true, enable filter pushdown to Python datasource, at the cost 
of running " +
@@ -8156,6 +8169,9 @@ class SQLConf extends Serializable with Logging with 
SqlApiConf {
   def legacyPostgresDatetimeMappingEnabled: Boolean =
     getConf(LEGACY_POSTGRES_DATETIME_MAPPING_ENABLED)
 
+  def legacyJdbcTimeMappingEnabled: Boolean =
+    getConf(LEGACY_JDBC_TIME_MAPPING_ENABLED)
+
   override def legacyTimeParserPolicy: LegacyBehaviorPolicy.Value =
     getConf(SQLConf.LEGACY_TIME_PARSER_POLICY)
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala
index e4f2dafa9aad..cebc45002d44 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala
@@ -231,7 +231,7 @@ object JdbcUtils extends Logging with SQLConfHelper {
     case java.sql.Types.SQLXML => StringType
     case java.sql.Types.STRUCT => StringType
     case java.sql.Types.TIME =>
-      if (conf.isTimeTypeEnabled) {
+      if (conf.isTimeTypeEnabled && !conf.legacyJdbcTimeMappingEnabled) {
         // Use reported scale (fractional digits) as precision; TIME(0) is 
valid
         val timePrecision = if (scale >= 0 && scale <= TimeType.MAX_PRECISION) 
scale
           else TimeType.DEFAULT_PRECISION
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
index 4e75e1807cf4..f353f6f2a699 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
@@ -768,6 +768,32 @@ class JDBCSuite extends SharedSparkSession {
     assert(rows(0).getAs[java.time.LocalTime](0) === 
java.time.LocalTime.of(12, 34, 56))
   }
 
+  test("SPARK-57555: legacy.jdbc.timeMapping escape hatch keeps TIME as 
TimestampType") {
+    // The escape hatch forces the legacy TIME-to-timestamp mapping even when 
the TIME type is
+    // enabled, so workloads relying on the old behavior are not silently 
broken.
+    withSQLConf(
+      SQLConf.TIME_TYPE_ENABLED.key -> "true",
+      SQLConf.LEGACY_JDBC_TIME_MAPPING_ENABLED.key -> "true") {
+      val df = spark.read.jdbc(urlWithUserAndPass, "TEST.TIMETYPES", new 
Properties())
+      assert(df.schema("A").dataType === TimestampType)
+    }
+    // Sanity check: without the escape hatch the column is read as TimeType.
+    withSQLConf(
+      SQLConf.TIME_TYPE_ENABLED.key -> "true",
+      SQLConf.LEGACY_JDBC_TIME_MAPPING_ENABLED.key -> "false") {
+      val df = spark.read.jdbc(urlWithUserAndPass, "TEST.TIMETYPES", new 
Properties())
+      assert(df.schema("A").dataType.isInstanceOf[TimeType])
+    }
+    // The escape hatch has no effect when the TIME type is disabled: the 
column is read as
+    // TimestampType regardless of the escape hatch.
+    withSQLConf(
+      SQLConf.TIME_TYPE_ENABLED.key -> "false",
+      SQLConf.LEGACY_JDBC_TIME_MAPPING_ENABLED.key -> "true") {
+      val df = spark.read.jdbc(urlWithUserAndPass, "TEST.TIMETYPES", new 
Properties())
+      assert(df.schema("A").dataType === TimestampType)
+    }
+  }
+
   test("SPARK-57555: JDBC TIME write round-trip") {
     val url = urlWithUserAndPass
     val tableName = "TEST.TIME_ROUNDTRIP"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to