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

dongjoon-hyun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/spark-connect-swift.git


The following commit(s) were added to refs/heads/main by this push:
     new 84a6cc5  [SPARK-58579] Support `TIME` type in 
`SparkSession.createDataFrame`
84a6cc5 is described below

commit 84a6cc5de6759da44406e573de856c82d094f64a
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Tue Aug 4 17:58:24 2026 -0700

    [SPARK-58579] Support `TIME` type in `SparkSession.createDataFrame`
    
    ### What changes were proposed in this pull request?
    
    This PR aims to support `TIME` type in `SparkSession.createDataFrame`.
    
    ```swift
    let time = LocalTime(hour: 12, minute: 34, second: 56, nanosecond: 
123_456_000)!
    let df = try await spark.createDataFrame([[time], [nil]], "t TIME")
    try await df.collect()  // [Row(12:34:56.123456), Row(nil)]
    ```
    
    - `ConvertToArrow` maps the `time` DDL type to an Arrow 
`time64(NANOSECOND)` column and
      writes `LocalTime.nanoOfDay` values as-is, symmetrically with the 
`collect()` decoding
      path added by SPARK-58571. `nil` is mapped to `NULL` like the other types.
    - Fix the vendored `ArrowWriterHelper` to serialize the `bitWidth` field 
(64) of the
      FlatBuffers `Time` type for `time64`. Without it, the field defaults to 
32 and the
      server rejects the uploaded column with
      `[UNSUPPORTED_ARROWTYPE] Unsupported arrow type Time(NANOSECOND, 32)`.
      (The same omission exists in upstream `apache/arrow-swift`; `time32` is 
unaffected
      because 32 is the correct default there.)
    
    ### Why are the changes needed?
    
    This is the last missing piece of `TIME` type support. 
`DataType.simpleString`
    (SPARK-58568), `collect()` (SPARK-58571), and `lit`/SQL parameter binding 
(SPARK-58574)
    already support `TIME`, but `createDataFrame` rejected `t TIME` schemas with
    `SparkConnectError.InvalidType`.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, `createDataFrame` now accepts `TIME` columns with `LocalTime` values.
    Previously it threw `SparkConnectError.InvalidType`.
    
    ### How was this patch tested?
    
    Pass the CIs with a newly added test case, `CreateDataFrameTests.timeType`, 
which
    verifies a `createDataFrame` → `collect` round trip including `LocalTime` 
values and
    `nil`, and the `time(6)`/`time(0)` schema mapping. The test is guarded by
    `spark.version >= "4.2"` and `spark.sql.timeType.enabled`, and was verified 
manually
    against both Apache Spark 4.2.0 (runs) and 4.1.3 (skips) Spark Connect 
servers.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Fable 5
    
    Closes #483 from dongjoon-hyun/dongjoon/silly-lederberg-905677.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 Sources/SparkConnect/ArrowWriterHelper.swift       |  1 +
 Sources/SparkConnect/ConvertToArrow.swift          |  4 ++++
 Sources/SparkConnect/SparkSession.swift            |  4 ++--
 Tests/SparkConnectTests/CreateDataFrameTests.swift | 15 +++++++++++++++
 4 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/Sources/SparkConnect/ArrowWriterHelper.swift 
b/Sources/SparkConnect/ArrowWriterHelper.swift
index 9a05f29..2e20d27 100644
--- a/Sources/SparkConnect/ArrowWriterHelper.swift
+++ b/Sources/SparkConnect/ArrowWriterHelper.swift
@@ -116,6 +116,7 @@ func toFBType(  // swiftlint:disable:this 
cyclomatic_complexity function_body_le
     if let timeType = arrowType as? ArrowTypeTime64 {
       org_apache_arrow_flatbuf_Time.add(
         unit: timeType.unit == .microseconds ? .microsecond : .nanosecond, 
&fbb)
+      org_apache_arrow_flatbuf_Time.add(bitWidth: 64, &fbb)
       return .success(org_apache_arrow_flatbuf_Time.endTime(&fbb, start: 
startOffset))
     }
 
diff --git a/Sources/SparkConnect/ConvertToArrow.swift 
b/Sources/SparkConnect/ConvertToArrow.swift
index 6473282..c626e7e 100644
--- a/Sources/SparkConnect/ConvertToArrow.swift
+++ b/Sources/SparkConnect/ConvertToArrow.swift
@@ -91,6 +91,10 @@ enum ConvertToArrow {
       return try fill(
         ArrowArrayBuilders.loadTimestampArrayBuilder(.microseconds, timezone: 
"UTC"), column
       ) { ($0 as? Date).map { Int64(($0.timeIntervalSince1970 * 
1_000_000).rounded()) } }
+    case .time:
+      return try fill(ArrowArrayBuilders.loadTime64ArrayBuilder(.nanoseconds), 
column) {
+        ($0 as? LocalTime)?.nanoOfDay
+      }
     default:
       throw SparkConnectError.InvalidType
     }
diff --git a/Sources/SparkConnect/SparkSession.swift 
b/Sources/SparkConnect/SparkSession.swift
index 4b99641..aeaf480 100644
--- a/Sources/SparkConnect/SparkSession.swift
+++ b/Sources/SparkConnect/SparkSession.swift
@@ -121,8 +121,8 @@ public actor SparkSession {
   ///
   /// The data is serialized into an `Apache Arrow` IPC stream and embedded 
into the plan as a
   /// `LocalRelation`. The supported schema types are `BOOLEAN`, `TINYINT`, 
`SMALLINT`, `INT`,
-  /// `BIGINT`, `FLOAT`, `DOUBLE`, `STRING`, `BINARY`, `DATE`, and 
`TIMESTAMP`. `nil` values are
-  /// mapped to `NULL`. When the serialized data is equal to or larger than
+  /// `BIGINT`, `FLOAT`, `DOUBLE`, `STRING`, `BINARY`, `DATE`, `TIMESTAMP`, 
and `TIME`.
+  /// `nil` values are mapped to `NULL`. When the serialized data is equal to 
or larger than
   /// `spark.sql.session.localRelationCacheThreshold` (1MiB by default), it is 
uploaded to the
   /// server as a `cache/` artifact and referenced by a `CachedLocalRelation` 
plan instead, so
   /// it is not limited by the gRPC message size limit.
diff --git a/Tests/SparkConnectTests/CreateDataFrameTests.swift 
b/Tests/SparkConnectTests/CreateDataFrameTests.swift
index e2dd454..11947b8 100644
--- a/Tests/SparkConnectTests/CreateDataFrameTests.swift
+++ b/Tests/SparkConnectTests/CreateDataFrameTests.swift
@@ -60,6 +60,21 @@ struct CreateDataFrameTests {
     await spark.stop()
   }
 
+  @Test
+  func timeType() async throws {
+    let spark = try await SparkSession.builder.getOrCreate()
+    if await spark.version >= "4.2" {
+      try await spark.conf.set("spark.sql.timeType.enabled", "true")
+      let time = try #require(LocalTime(hour: 12, minute: 34, second: 56, 
nanosecond: 123_456_000))
+      let df = try await spark.createDataFrame([[time], [nil]], "t TIME")
+      #expect(try await df.dtypes.map { $0.1 } == ["time(6)"])
+      #expect(try await df.collect() == [Row(time), Row(nil)])
+      #expect(
+        try await spark.createDataFrame([[time]], "t TIME(0)").dtypes.map { 
$0.1 } == ["time(0)"])
+    }
+    await spark.stop()
+  }
+
   @Test
   func binaryType() async throws {
     let spark = try await SparkSession.builder.getOrCreate()


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

Reply via email to