You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by do...@apache.org on 2026/08/04 19:58:49 UTC

(spark-connect-swift) branch main updated: [SPARK-58574] Support `TIME` type literals in `lit` and SQL parameter binding

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 434c5b9  [SPARK-58574] Support `TIME` type literals in `lit` and SQL parameter binding
434c5b9 is described below

commit 434c5b9c72f7488eb80fbb899c63ee16dc083941
Author: Dongjoon Hyun <do...@apache.org>
AuthorDate: Tue Aug 4 12:58:43 2026 -0700

    [SPARK-58574] Support `TIME` type literals in `lit` and SQL parameter binding
    
    ### What changes were proposed in this pull request?
    
    This PR aims to support `TIME` type literals in `lit` and SQL parameter binding.
    
    - Add `lit(_ value: LocalTime)` which builds `Expression.Literal.Time` with `nano = nanoOfDay` and `precision = 6`, matching the Scala client (`TimeTypeConnectOps`) which always uses `DEFAULT_PRECISION`.
    - Conform `LocalTime` to `SparkLiteral` so it can be used directly as an operand of `Column` operators, e.g. `col("t") == LocalTime(hour: 12, minute: 34, second: 56)!`.
    - Handle `LocalTime` in `ExpressionLiteral.init(_:)` so it can be bound as a positional (`?`) or named (`:name`) parameter of `spark.sql`.
    
    ```swift
    let time = LocalTime(hour: 12, minute: 34, second: 56)!
    spark.range(1).select(lit(time))
    try await spark.sql("SELECT ?", time)
    try await spark.sql("SELECT :t", args: ["t": time])
    ```
    
    ### Why are the changes needed?
    
    SPARK-58571 covered only the read path (`DataFrame.collect`). This completes the write path for expressions so that Swift users can send `TIME` values to the server via `lit`, `Column` operators, and parameterized SQL, on par with the Scala client.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, `LocalTime` is newly accepted by `lit`, `Column` operators, and `spark.sql` parameters. There is no behavior change for existing code.
    
    ### How was this patch tested?
    
    Pass the CIs with newly added test cases.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Fable 5
    
    Closes #482 from dongjoon-hyun/SPARK-58574.
    
    Authored-by: Dongjoon Hyun <do...@apache.org>
    Signed-off-by: Dongjoon Hyun <do...@apache.org>
---
 Sources/SparkConnect/Extension.swift            |  3 +++
 Sources/SparkConnect/Functions.swift            | 14 ++++++++++++++
 Tests/SparkConnectTests/DataFrameTests.swift    | 13 +++++++++++++
 Tests/SparkConnectTests/FunctionsTests.swift    |  7 +++++++
 Tests/SparkConnectTests/SparkSessionTests.swift | 14 ++++++++++++++
 5 files changed, 51 insertions(+)

diff --git a/Sources/SparkConnect/Extension.swift b/Sources/SparkConnect/Extension.swift
index b56ecf4..f7e038f 100644
--- a/Sources/SparkConnect/Extension.swift
+++ b/Sources/SparkConnect/Extension.swift
@@ -197,6 +197,9 @@ extension ExpressionLiteral {
       // `Date` is an absolute point in time, so it maps to Spark's `TIMESTAMP`
       // (microseconds since the UNIX epoch), not `DATE`.
       self.timestamp = Int64(value.timeIntervalSince1970 * 1_000_000)
+    case let value as LocalTime:
+      self.time.nano = value.nanoOfDay
+      self.time.precision = 6
     default:
       if case Optional<Any>.none = value as Any {
         var dataType = DataType()
diff --git a/Sources/SparkConnect/Functions.swift b/Sources/SparkConnect/Functions.swift
index edf6ab6..d9c5631 100644
--- a/Sources/SparkConnect/Functions.swift
+++ b/Sources/SparkConnect/Functions.swift
@@ -112,6 +112,16 @@ public func lit(_ value: String) -> Column {
   return litColumn(literal)
 }
 
+/// Creates a ``Column`` of literal value.
+/// - Parameter value: A literal value.
+/// - Returns: A ``Column``.
+public func lit(_ value: LocalTime) -> Column {
+  var literal = ExpressionLiteral()
+  literal.time.nano = value.nanoOfDay
+  literal.time.precision = 6
+  return litColumn(literal)
+}
+
 /// A type that can be used as a literal operand of ``Column`` operators.
 ///
 /// This allows Swift literals to be mixed with ``Column`` expressions directly
@@ -161,6 +171,10 @@ extension String: SparkLiteral {
   public var toLiteralColumn: Column { lit(self) }
 }
 
+extension LocalTime: SparkLiteral {
+  public var toLiteralColumn: Column { lit(self) }
+}
+
 /// Returns a sort expression based on the ascending order of the given column name.
 /// - Parameter name: A column name.
 /// - Returns: A ``Column`` with ascending sort order.
diff --git a/Tests/SparkConnectTests/DataFrameTests.swift b/Tests/SparkConnectTests/DataFrameTests.swift
index e4d0858..3ee9e67 100644
--- a/Tests/SparkConnectTests/DataFrameTests.swift
+++ b/Tests/SparkConnectTests/DataFrameTests.swift
@@ -197,6 +197,19 @@ struct DataFrameTests {
     await spark.stop()
   }
 
+  @Test
+  func selectTimeLiteral() 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.range(1).select(lit(time))
+      #expect(try await df.dtypes[0].1 == "time(6)")
+      #expect(try await df.collect() == [Row(time)])
+    }
+    await spark.stop()
+  }
+
   @Test
   func array() async throws {
     let spark = try await SparkSession.builder.getOrCreate()
diff --git a/Tests/SparkConnectTests/FunctionsTests.swift b/Tests/SparkConnectTests/FunctionsTests.swift
index cd5e456..92df869 100644
--- a/Tests/SparkConnectTests/FunctionsTests.swift
+++ b/Tests/SparkConnectTests/FunctionsTests.swift
@@ -57,6 +57,9 @@ struct FunctionsTests {
     #expect(lit(Float(1.0)).expr.literal.float == 1.0)
     #expect(lit(1.0).expr.literal.double == 1.0)
     #expect(lit("a").expr.literal.string == "a")
+    let time = try #require(LocalTime(hour: 12, minute: 34, second: 56))
+    #expect(lit(time).expr.literal.time.nano == 45_296_000_000_000)
+    #expect(lit(time).expr.literal.time.precision == 6)
   }
 
   @Test
@@ -253,6 +256,10 @@ struct FunctionsTests {
     #expect((col("a") == Float(1.0)).expr.unresolvedFunction.arguments[1].literal.float == 1.0)
     #expect((col("a") == 1.0).expr.unresolvedFunction.arguments[1].literal.double == 1.0)
     #expect((col("a") == "x").expr.unresolvedFunction.arguments[1].literal.string == "x")
+    let time = try #require(LocalTime(hour: 12, minute: 34, second: 56))
+    #expect(
+      (col("a") == time).expr.unresolvedFunction.arguments[1].literal.time.nano
+        == 45_296_000_000_000)
   }
 
   @Test
diff --git a/Tests/SparkConnectTests/SparkSessionTests.swift b/Tests/SparkConnectTests/SparkSessionTests.swift
index 64ab712..e514750 100644
--- a/Tests/SparkConnectTests/SparkSessionTests.swift
+++ b/Tests/SparkConnectTests/SparkSessionTests.swift
@@ -171,6 +171,20 @@ struct SparkSessionTests {
     await spark.stop()
   }
 
+  @Test
+  func sqlWithTimeParameter() async throws {
+    await SparkSession.builder.clear()
+    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))
+      #expect(try await spark.sql("SELECT typeof(?)", time).collect() == [Row("time(6)")])
+      #expect(try await spark.sql("SELECT ?", time).collect() == [Row(time)])
+      #expect(try await spark.sql("SELECT :t", args: ["t": time]).collect() == [Row(time)])
+    }
+    await spark.stop()
+  }
+
   @Test
   func sqlWithUnsupportedParameterType() async throws {
     await SparkSession.builder.clear()


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org