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 e80f4207726e [SPARK-57556][SQL] Raise a clear error for the TIME data 
type in Hive SerDe interop
e80f4207726e is described below

commit e80f4207726efc9ab465a6ba15c949db7498bd27
Author: Maxim Gekk <[email protected]>
AuthorDate: Tue Jun 30 18:44:55 2026 +0200

    [SPARK-57556][SQL] Raise a clear error for the TIME data type in Hive SerDe 
interop
    
    ### What changes were proposed in this pull request?
    
    Apache Hive has no TIME type, so `TimeType` has no faithful representation 
in Hive SerDe interop. This PR (the Option B / "clear, documented error" path 
from [SPARK-57556](https://issues.apache.org/jira/browse/SPARK-57556)) makes 
`TimeType` produce a clear `AnalysisException` instead of a 
`scala.MatchError`/internal error when it reaches the `HiveInspectors` mapping 
functions, and rejects it in the Hive SerDe write path:
    
    - `HiveInspectors.toInspector(dataType)`, `toInspector(expr)` (TIME 
literal) and `toTypeInfo` now throw `UNSUPPORTED_DATATYPE` via a shared 
`unsupportedHiveType` helper. Previously `toInspector(dataType)` had no 
`TimeType` case and no default branch, so a TIME column hit a raw 
`scala.MatchError`.
    - `HiveFileFormat.supportDataType` rejects `TimeType` (recursing into 
nested struct/array/map/UDT types, preserving the prior default for all other 
types) so Hive SerDe writes raise `UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE` 
(format `Hive`) via `FileFormatWriter.verifySchema`.
    - Documented the limitation on the TIME entry in 
`docs/sql-ref-datatypes.md`.
    
    ### Why are the changes needed?
    
    `HiveInspectors` had no `TimeType` case, so object-inspector creation and 
TypeInfo mapping fell through to a `MatchError`/internal error when a TIME 
column or literal reached Hive SerDe paths (for example, a TIME argument to a 
Hive UDF/UDAF/UDTF). This makes the behavior explicit and documented, 
consistent with the existing TIME rejection for Hive ORC (SPARK-51590).
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Using TIME with Hive UDFs or in a Hive SerDe write now fails with a 
clear error that names the unsupported TIME type, instead of a 
`MatchError`/internal error. For example, `SELECT myHiveUDF(TIME'12:01:02')` 
now reports `[UNSUPPORTED_DATATYPE] Unsupported data type "TIME(6)"` (wrapped 
by the Hive UDF resolver), and writing a TIME column through the Hive SerDe 
write path reports `[UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE] The Hive datasource 
doesn't support the column ... of the type  [...]
    
    ### How was this patch tested?
    
    Added tests and ran them locally (`build/sbt 'hive/testOnly 
*HiveInspectorSuite *HiveUDFSuite *InsertSuite'`):
    - `HiveInspectorSuite`: `toInspector(TimeType())`, a TIME literal, and 
`TimeType().toTypeInfo` raise `UNSUPPORTED_DATATYPE`.
    - `HiveUDFSuite`: passing `TIME'12:01:02'` to a Hive `GenericUDFHash` fails 
with a message naming the unsupported TIME type.
    - `InsertSuite`: `INSERT OVERWRITE LOCAL DIRECTORY ... STORED AS PARQUET 
SELECT TIME'...'` (with `spark.sql.hive.convertMetastoreInsertDir=false`) 
raises `UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE`.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Cursor (Claude Opus 4.8)
    
    Closes #56850 from MaxGekk/time-hive-serde.
    
    Authored-by: Maxim Gekk <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
---
 docs/sql-ref-datatypes.md                          |  1 +
 .../org/apache/spark/sql/hive/HiveInspectors.scala | 13 +++++++++++
 .../spark/sql/hive/execution/HiveFileFormat.scala  | 19 +++++++++++++++-
 .../apache/spark/sql/hive/HiveInspectorSuite.scala | 19 +++++++++++++++-
 .../org/apache/spark/sql/hive/InsertSuite.scala    | 25 ++++++++++++++++++++++
 .../spark/sql/hive/execution/HiveUDFSuite.scala    | 17 +++++++++++++++
 6 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/docs/sql-ref-datatypes.md b/docs/sql-ref-datatypes.md
index 27663763d6bd..a5da7949d059 100644
--- a/docs/sql-ref-datatypes.md
+++ b/docs/sql-ref-datatypes.md
@@ -48,6 +48,7 @@ Spark SQL and DataFrames support the following data types:
   time-zone.
   - `TimeType(precision)`: Represents values comprising values of fields hour, 
minute and second with the number of decimal digits `precision` following the 
decimal point in the seconds field, without a time-zone.
   The range of values is from `00:00:00` to `23:59:59` for min precision `0`, 
and to `23:59:59.999999999` for max precision `9`. The default precision is `6`.
+    - Note: Apache Hive has no TIME type, so `TimeType` is not supported in 
Hive SerDe interop. Storing it in a Hive SerDe table (including `INSERT 
OVERWRITE DIRECTORY ... STORED AS`) or passing it to a Hive UDF/UDAF/UDTF 
raises an error rather than silently converting the value.
   - `TimestampType`: Timestamp with local time zone(TIMESTAMP_LTZ). It 
represents values comprising values of fields year, month, day,
   hour, minute, and second, with the session local time-zone. The timestamp 
value represents an
   absolute point in time.
diff --git 
a/sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveInspectors.scala 
b/sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveInspectors.scala
index 49b9cc798a1b..285548086a09 100644
--- a/sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveInspectors.scala
+++ b/sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveInspectors.scala
@@ -961,6 +961,14 @@ private[hive] trait HiveInspectors {
     case _: UserDefinedType[_] =>
       val sqlType = dataType.asInstanceOf[UserDefinedType[_]].sqlType
       toInspector(sqlType)
+    // Hive has no TIME type, so it cannot be represented by any Hive object 
inspector.
+    case _: TimeType => throw unsupportedHiveType(dataType)
+  }
+
+  private def unsupportedHiveType(dataType: DataType): AnalysisException = {
+    new AnalysisException(
+      errorClass = "UNSUPPORTED_DATATYPE",
+      messageParameters = Map("typeName" -> toSQLType(dataType)))
   }
 
   /**
@@ -1029,6 +1037,9 @@ private[hive] trait HiveInspectors {
       toInspector(dt)
     case Literal(_, dt: UserDefinedType[_]) =>
       toInspector(dt.sqlType)
+    // Hive has no TIME type, so a TIME constant cannot be mapped to a Hive 
object inspector.
+    case Literal(_, dt: TimeType) =>
+      throw unsupportedHiveType(dt)
     // We will enumerate all of the possible constant expressions, throw 
exception if we missed
     case Literal(_, dt) =>
       throw SparkException.internalError(s"Hive doesn't support the constant 
type [$dt].")
@@ -1281,6 +1292,8 @@ private[hive] trait HiveInspectors {
       case NullType => voidTypeInfo
       case _: DayTimeIntervalType => intervalDayTimeTypeInfo
       case _: YearMonthIntervalType => intervalYearMonthTypeInfo
+      // Hive has no TIME type, so there is no Hive TypeInfo to map it to.
+      case _: TimeType => throw unsupportedHiveType(dt)
       case dt =>
         throw new AnalysisException(
           errorClass = "_LEGACY_ERROR_TEMP_3095", messageParameters = Map("dt" 
-> toSQLType(dt)))
diff --git 
a/sql/hive/src/main/scala/org/apache/spark/sql/hive/execution/HiveFileFormat.scala
 
b/sql/hive/src/main/scala/org/apache/spark/sql/hive/execution/HiveFileFormat.scala
index 5506cf8dae07..acbc72fbf7e0 100644
--- 
a/sql/hive/src/main/scala/org/apache/spark/sql/hive/execution/HiveFileFormat.scala
+++ 
b/sql/hive/src/main/scala/org/apache/spark/sql/hive/execution/HiveFileFormat.scala
@@ -41,7 +41,7 @@ import 
org.apache.spark.sql.execution.datasources.{FileFormat, OutputWriter, Out
 import org.apache.spark.sql.hive.{HiveInspectors, HiveTableUtil}
 import org.apache.spark.sql.internal.SessionStateHelper
 import org.apache.spark.sql.sources.DataSourceRegister
-import org.apache.spark.sql.types.StructType
+import org.apache.spark.sql.types.{ArrayType, DataType, MapType, StructType, 
TimeType, UserDefinedType}
 import org.apache.spark.util.SerializableJobConf
 
 /**
@@ -115,6 +115,23 @@ case class HiveFileFormat(fileSinkConf: FileSinkDesc)
     }
   }
 
+  override def supportDataType(dataType: DataType): Boolean = dataType match {
+    // Hive has no TIME type, so it cannot be stored in a Hive serde table. 
Reject it explicitly
+    // (recursing into nested types) while preserving the default behavior for 
all other types.
+    case _: TimeType => false
+
+    case st: StructType => st.forall { f => supportDataType(f.dataType) }
+
+    case ArrayType(elementType, _) => supportDataType(elementType)
+
+    case MapType(keyType, valueType, _) =>
+      supportDataType(keyType) && supportDataType(valueType)
+
+    case udt: UserDefinedType[_] => supportDataType(udt.sqlType)
+
+    case _ => true
+  }
+
   override def supportFieldName(name: String): Boolean = {
     fileSinkConf.getTableInfo.getOutputFileFormatClassName match {
       case "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat" =>
diff --git 
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/HiveInspectorSuite.scala 
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/HiveInspectorSuite.scala
index 8acabd579d44..b7fb506f07b5 100644
--- a/sql/hive/src/test/scala/org/apache/spark/sql/hive/HiveInspectorSuite.scala
+++ b/sql/hive/src/test/scala/org/apache/spark/sql/hive/HiveInspectorSuite.scala
@@ -28,7 +28,7 @@ import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo
 import org.apache.hadoop.io.LongWritable
 
 import org.apache.spark.SparkFunSuite
-import org.apache.spark.sql.{Row, TestUserClassUDT}
+import org.apache.spark.sql.{AnalysisException, Row, TestUserClassUDT}
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.Literal
 import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, 
GenericArrayData, MapData}
@@ -291,4 +291,21 @@ class HiveInspectorSuite extends SparkFunSuite with 
HiveInspectors {
     assert(typeInfo2.precision() === 18)
     assert(typeInfo2.scale() === 10)
   }
+
+  test("SPARK-57556: TIME type is unsupported in Hive object inspectors") {
+    val timeType = TimeType()
+    val expectedParams = Map("typeName" -> s"\"${timeType.sql}\"")
+    checkError(
+      exception = intercept[AnalysisException](toInspector(timeType)),
+      condition = "UNSUPPORTED_DATATYPE",
+      parameters = expectedParams)
+    checkError(
+      exception = 
intercept[AnalysisException](toInspector(Literal.create(null, timeType))),
+      condition = "UNSUPPORTED_DATATYPE",
+      parameters = expectedParams)
+    checkError(
+      exception = intercept[AnalysisException](timeType.toTypeInfo),
+      condition = "UNSUPPORTED_DATATYPE",
+      parameters = expectedParams)
+  }
 }
diff --git 
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/InsertSuite.scala 
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/InsertSuite.scala
index b6f20c42f543..7385410e1a71 100644
--- a/sql/hive/src/test/scala/org/apache/spark/sql/hive/InsertSuite.scala
+++ b/sql/hive/src/test/scala/org/apache/spark/sql/hive/InsertSuite.scala
@@ -683,6 +683,31 @@ class InsertSuite extends QueryTest with TestHiveSingleton 
with BeforeAndAfter {
     }
   }
 
+  test("SPARK-57556: TIME type is unsupported when writing to a Hive serde 
directory") {
+    // Disable native data source conversion so that the write goes through 
the Hive serde
+    // path (HiveFileFormat) instead of a native data source that may support 
TIME.
+    withSQLConf(HiveUtils.CONVERT_METASTORE_INSERT_DIR.key -> "false") {
+      withTempDir { dir =>
+        // InsertIntoHiveDirCommand wraps the failure in a SparkException, so 
assert on the cause.
+        val e = intercept[SparkException] {
+          sql(
+            s"""
+               |INSERT OVERWRITE LOCAL DIRECTORY '${dir.toURI.getPath}'
+               |STORED AS PARQUET
+               |SELECT TIME'12:01:02' AS c
+             """.stripMargin)
+        }
+        checkError(
+          exception = e.getCause.asInstanceOf[AnalysisException],
+          condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+          parameters = Map(
+            "columnName" -> "`c`",
+            "columnType" -> s"\"${TimeType().sql}\"",
+            "format" -> "Hive"))
+      }
+    }
+  }
+
   test("insert overwrite to dir from temp table") {
     withTempView("test_insert_table") {
       spark.range(10).selectExpr("id", "id AS 
str").createOrReplaceTempView("test_insert_table")
diff --git 
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/HiveUDFSuite.scala
 
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/HiveUDFSuite.scala
index 35ae3ea29d6d..53c245394daf 100644
--- 
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/HiveUDFSuite.scala
+++ 
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/HiveUDFSuite.scala
@@ -41,6 +41,7 @@ import org.apache.spark.sql.execution.WholeStageCodegenExec
 import org.apache.spark.sql.functions.{call_function, max}
 import org.apache.spark.sql.hive.test.{TestHiveSingleton, TestUDTFJar}
 import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types.TimeType
 import org.apache.spark.tags.SlowHiveTest
 import org.apache.spark.util.Utils
 
@@ -412,6 +413,22 @@ class HiveUDFSuite extends QueryTest with 
TestHiveSingleton {
     }
   }
 
+  test("SPARK-57556: TIME type is unsupported as a Hive UDF argument") {
+    withUserDefinedFunction("testGenericUDFHash" -> true) {
+      sql(s"CREATE TEMPORARY FUNCTION testGenericUDFHash AS 
'${classOf[GenericUDFHash].getName}'")
+      // The Hive UDF resolver wraps the failure in 
CANNOT_INSTANTIATE_HIVE_FUNCTION and attaches
+      // the underlying failure as the cause, which clearly identifies the 
unsupported TIME type
+      // rather than surfacing a MatchError/internal error.
+      val e = intercept[AnalysisException] {
+        sql("SELECT testGenericUDFHash(TIME'12:01:02')").collect()
+      }
+      checkError(
+        exception = e.getCause.asInstanceOf[AnalysisException],
+        condition = "UNSUPPORTED_DATATYPE",
+        parameters = Map("typeName" -> s"\"${TimeType().sql}\""))
+    }
+  }
+
   test("Hive UDFs with insufficient number of input arguments should trigger 
an analysis error") {
     withTempView("testUDF") {
       Seq((1, 2)).toDF("a", "b").createOrReplaceTempView("testUDF")


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

Reply via email to