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