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 00e01d465167 [SPARK-57564][SQL][TEST] Add unit-test coverage for TIME 
to match DATE/TIMESTAMP
00e01d465167 is described below

commit 00e01d465167d32186abb9a01511b29986113701
Author: Vaibhav Garg <[email protected]>
AuthorDate: Thu Jun 25 10:55:29 2026 +0200

    [SPARK-57564][SQL][TEST] Add unit-test coverage for TIME to match 
DATE/TIMESTAMP
    
    ### What changes were proposed in this pull request?
    
    This PR adds unit-test coverage for the TIME data type (`TimeType`, 
`java.time.LocalTime`) to three existing catalyst test suites, bringing TIME to 
parity with the sibling DATE/TIMESTAMP test cases already present in each:
    
    - **ExpressionEncoderSuite** — Added `LocalTime` encoder round-trip tests: 
a plain time value, midnight boundary, max-microseconds boundary 
(23:59:59.999999), an array of times, and `Option`/`Map` variants. Mirrors the 
existing date/timestamp `encodeDecodeTest` cases.
    - **DDLParserSuite** — Added a test covering `TIME` and `TIME(p)` precision 
columns in `CREATE TABLE`, `ALTER TABLE ADD COLUMNS`, `ALTER COLUMN ... TYPE`, 
a column with a TIME typed-literal `DEFAULT`, and `PARTITIONED BY` a TIME 
column. Mirrors the existing SPARK-57164 nanosecond-timestamp DDL test.
    - **DataTypeWriteCompatibilitySuite** — Added a dedicated TIME 
write-compatibility test in the shared base suite so it executes under both the 
strict (`canUpCast`) and ANSI (`canANSIStoreAssign`) store-assignment policies. 
It checks TIME→TIME across precisions and TIME↔DATE/TIMESTAMP/TIMESTAMP_NTZ in 
both directions, deriving the allowed/rejected expectation from the 
policy-bound cast rule. Mirrors the existing SPARK-37707 datetime-compatibility 
test.
    
    No production code is changed. This is test-only.
    
    ### Why are the changes needed?
    
    The TIME data type shipped in Spark 4.1.0 (SPIP SPARK-51162), but catalyst 
unit-test coverage for TIME lags behind DATE and TIMESTAMP. This PR closes that 
gap as part of the SPARK-57550 umbrella (extend TIME type support across the 
codebase). Adequate test coverage is needed to catch regressions as further 
TIME integration work lands.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    All three suites were run individually and pass locally:
    
    - `build/sbt "catalyst/testOnly *ExpressionEncoderSuite"` — 399 tests pass
    - `build/sbt "catalyst/testOnly 
org.apache.spark.sql.catalyst.parser.DDLParserSuite"` — 149 tests pass
    - `build/sbt "catalyst/testOnly *StrictDataTypeWriteCompatibilitySuite 
*ANSIDataTypeWriteCompatibilitySuite"` — 60 tests pass (the new TIME test 
executes under both policies)
    
    No existing tests were modified.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Yes. Generated using Kiro (Claude Opus 4.8).
    
    Closes #56764 from vboo123/SPARK-57564.
    
    Lead-authored-by: Vaibhav Garg <[email protected]>
    Co-authored-by: Vaibhav Garg <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
---
 .../catalyst/encoders/ExpressionEncoderSuite.scala |  9 +++++
 .../spark/sql/catalyst/parser/DDLParserSuite.scala | 33 +++++++++++++++-
 .../types/DataTypeWriteCompatibilitySuite.scala    | 44 ++++++++++++++++++++++
 3 files changed, 85 insertions(+), 1 deletion(-)

diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/encoders/ExpressionEncoderSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/encoders/ExpressionEncoderSuite.scala
index 287b99d10d65..ddc85b9b3ef4 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/encoders/ExpressionEncoderSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/encoders/ExpressionEncoderSuite.scala
@@ -19,6 +19,7 @@ package org.apache.spark.sql.catalyst.encoders
 
 import java.math.BigInteger
 import java.sql.{Date, Timestamp}
+import java.time.LocalTime
 import java.util.Arrays
 
 import scala.collection.mutable
@@ -210,6 +211,10 @@ class ExpressionEncoderSuite extends 
CodegenInterpretedPlanTest with AnalysisTes
   encodeDecodeTest(Date.valueOf("2012-12-23"), "date")
   encodeDecodeTest(Timestamp.valueOf("2016-01-29 10:00:00"), "timestamp")
   encodeDecodeTest(Array(Timestamp.valueOf("2016-01-29 10:00:00")), "array of 
timestamp")
+  encodeDecodeTest(LocalTime.of(12, 34, 56), "SPARK-57564: time")
+  encodeDecodeTest(LocalTime.MIDNIGHT, "SPARK-57564: midnight time")
+  encodeDecodeTest(LocalTime.of(23, 59, 59, 999999000), "SPARK-57564: max 
micros time")
+  encodeDecodeTest(Array(LocalTime.of(12, 34, 56)), "SPARK-57564: array of 
time")
   encodeDecodeTest(Array[Byte](13, 21, -23), "binary")
 
   encodeDecodeTest(Seq(31, -123, 4), "seq of int")
@@ -454,6 +459,10 @@ class ExpressionEncoderSuite extends 
CodegenInterpretedPlanTest with AnalysisTes
     "SPARK-45896: seq of option of date")
   encodeDecodeTest(Map(0 -> Some(Date.valueOf("2023-01-01"))),
     "SPARK-45896: map of option of date")
+  encodeDecodeTest(Seq(Some(LocalTime.of(12, 34, 56))),
+    "SPARK-57564: seq of option of time")
+  encodeDecodeTest(Map(0 -> Some(LocalTime.of(12, 34, 56))),
+    "SPARK-57564: map of option of time")
   encodeDecodeTest(Seq(Some(BigDecimal(200))), "SPARK-45896: seq of option of 
bigdecimal")
   encodeDecodeTest(Map(0 -> Some(BigDecimal(200))), "SPARK-45896: map of 
option of bigdecimal")
 
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala
index 2e8132926dcd..4edeb3176798 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala
@@ -30,7 +30,7 @@ import 
org.apache.spark.sql.connector.catalog.TableChange.ColumnPosition.{after,
 import org.apache.spark.sql.connector.expressions.{ApplyTransform, 
BucketTransform, ClusterByTransform, DaysTransform, FieldReference, 
HoursTransform, IdentityTransform, LiteralValue, MonthsTransform, Transform, 
YearsTransform}
 import org.apache.spark.sql.connector.expressions.LogicalExpressions.bucket
 import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.types.{DataType, Decimal, IntegerType, LongType, 
StringType, StructType, TimestampLTZNanosType, TimestampNTZNanosType, 
TimestampType}
+import org.apache.spark.sql.types.{DataType, Decimal, IntegerType, LongType, 
StringType, StructType, TimestampLTZNanosType, TimestampNTZNanosType, 
TimestampType, TimeType}
 import org.apache.spark.storage.StorageLevelMapper
 import org.apache.spark.unsafe.types.{CalendarInterval, UTF8String}
 
@@ -133,6 +133,37 @@ class DDLParserSuite extends AnalysisTest {
     }
   }
 
+  test("SPARK-57564: TIME type in CREATE TABLE / ALTER TABLE columns") {
+    Seq(
+      "TIME" -> TimeType(),
+      "TIME(0)" -> TimeType(0),
+      "TIME(3)" -> TimeType(3),
+      "TIME(6)" -> TimeType(6)).foreach {
+      case (spelling, expected) =>
+        // CREATE TABLE column.
+        val created = parsePlan(s"CREATE TABLE t (c $spelling) USING parquet")
+        assert(created.asInstanceOf[CreateTable].columns.head.dataType === 
expected)
+        // ALTER TABLE ... ADD COLUMNS.
+        val added = parsePlan(s"ALTER TABLE t ADD COLUMNS (c $spelling)")
+        assert(added.asInstanceOf[AddColumns].columnsToAdd.head.dataType === 
expected)
+        // ALTER TABLE ... ALTER COLUMN ... TYPE.
+        val altered = parsePlan(s"ALTER TABLE t ALTER COLUMN c TYPE $spelling")
+        assert(altered.asInstanceOf[AlterColumns].specs.head.newDataType === 
Some(expected))
+    }
+    // A column DEFAULT declared with a TIME type and a TIME typed-literal 
default.
+    val withDefault = parsePlan(
+      "CREATE TABLE t (c TIME DEFAULT TIME '12:34:56') USING parquet")
+    val colDef = withDefault.asInstanceOf[CreateTable].columns.head
+    assert(colDef.dataType === TimeType())
+    assert(colDef.defaultValue.isDefined)
+    // PARTITIONED BY a TIME column.
+    val partitioned = parsePlan(
+      "CREATE TABLE t (id INT, c TIME) USING parquet PARTITIONED BY (c)")
+    val createTable = partitioned.asInstanceOf[CreateTable]
+    assert(createTable.columns.exists(col => col.name == "c" && col.dataType 
=== TimeType()))
+    assert(createTable.partitioning === 
Seq(IdentityTransform(FieldReference("c"))))
+  }
+
   test("create/replace table - with IF NOT EXISTS") {
     val sql = "CREATE TABLE IF NOT EXISTS my_tab(a INT, b STRING) USING 
parquet"
     testCreateOrReplaceDdl(
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeWriteCompatibilitySuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeWriteCompatibilitySuite.scala
index ba3eaf46a559..131eab34762f 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeWriteCompatibilitySuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeWriteCompatibilitySuite.scala
@@ -320,6 +320,50 @@ abstract class DataTypeWriteCompatibilityBaseSuite extends 
SparkFunSuite {
     }
   }
 
+  test("SPARK-57564: Check TIME type write compatibility") {
+    // Expectations are derived from the policy-bound `canCast` (canUpCast for 
the strict suite,
+    // canANSIStoreAssign for the ANSI suite), so this single test stays 
correct under both
+    // subclasses: strict allows only TIME -> identical TIME, while ANSI 
additionally allows
+    // writes across datetime types and across TIME precisions.
+    def checkPair(write: DataType, read: DataType): Unit = {
+      if (canCast(write, read)) {
+        assertAllowed(write, read, "t",
+          s"Should allow writing $write to $read because cast is safe")
+      } else {
+        val errs = new mutable.ArrayBuffer[String]()
+        checkError(
+          exception = intercept[AnalysisException] (
+            DataTypeUtils.canWrite("", write, read, true, 
analysis.caseSensitiveResolution,
+              "t", storeAssignmentPolicy, errMsg => errs += errMsg)
+          ),
+          condition = "INCOMPATIBLE_DATA_FOR_TABLE.CANNOT_SAFELY_CAST",
+          parameters = Map(
+            "tableName" -> "``",
+            "colName" -> "`t`",
+            "srcType" -> toSQLType(write),
+            "targetType" -> toSQLType(read)
+          )
+        )
+      }
+    }
+
+    val timeTypes = Seq(TimeType(0), TimeType(3), TimeType(6))
+    // TIME -> TIME across all precision combinations (both directions via 
full cross product).
+    timeTypes.foreach { w =>
+      timeTypes.foreach { r =>
+        checkPair(w, r)
+      }
+    }
+    // TIME <-> other datetime types, both directions.
+    val otherDateTimeTypes = Seq(DateType, TimestampType, TimestampNTZType)
+    timeTypes.foreach { t =>
+      otherDateTimeTypes.foreach { o =>
+        checkPair(t, o)
+        checkPair(o, t)
+      }
+    }
+  }
+
   test("Check struct types: missing required field") {
     val missingRequiredField = StructType(Seq(StructField("x", FloatType, 
nullable = false)))
     val errs = new mutable.ArrayBuffer[String]()


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

Reply via email to