You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by vi...@apache.org on 2026/07/29 14:52:50 UTC

(spark) branch branch-4.x updated: [SPARK-58414][SQL][TEST] Add e2e coverage for nanosecond timestamps nested in complex types in the Arrow cache

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

viirya pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new c1ce87471c11 [SPARK-58414][SQL][TEST] Add e2e coverage for nanosecond timestamps nested in complex types in the Arrow cache
c1ce87471c11 is described below

commit c1ce87471c1104d285a9fc82fa45084d2f6cc583
Author: Liang-Chi Hsieh <vi...@gmail.com>
AuthorDate: Wed Jul 29 07:52:25 2026 -0700

    [SPARK-58414][SQL][TEST] Add e2e coverage for nanosecond timestamps nested in complex types in the Arrow cache
    
    ### What changes were proposed in this pull request?
    
    Add a test to `ArrowCachedBatchSerializerSuite` that round-trips nanosecond timestamps nested inside an array, a struct field, and a map value through the Arrow cache, using a value outside the int64 epoch-nanos window (year 3000 at nanosecond precision) that the standard interchange encoding cannot represent, alongside an in-window value.
    
    ### Why are the changes needed?
    
    The suite covers top-level nanosecond timestamps and nested `CalendarInterval`, but had no round trip for nanosecond timestamps nested in complex types. The nested path relies on recursion in two places: `ArrowWriter`'s field writers dispatch on `(type, vector)` recursively on the write side, and on the read side every container accessor in `ArrowColumnVector` (struct children, `ArrayAccessor`'s data vector, `MapAccessor`'s keys/values) wraps its element vector through the constructor [...]
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, test-only.
    
    ### How was this patch tested?
    
    The new test; full `ArrowCachedBatchSerializerSuite` and `ArrowCachedBatchKryoRegistrationSuite` pass (76 tests).
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Yes, this pull request and its description were written by Claude Code.
    
    Closes #57619 from viirya/arrow-cache-nested-nanos-test.
    
    Authored-by: Liang-Chi Hsieh <vi...@gmail.com>
    Signed-off-by: Liang-Chi Hsieh <vi...@gmail.com>
    (cherry picked from commit 5ceafeda5c512457d012fef81cf5b5154c41b2c9)
    Signed-off-by: Liang-Chi Hsieh <vi...@gmail.com>
---
 .../columnar/ArrowCachedBatchSerializerSuite.scala | 48 ++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/ArrowCachedBatchSerializerSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/ArrowCachedBatchSerializerSuite.scala
index 621fc4d48130..07672ad3e5cc 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/ArrowCachedBatchSerializerSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/ArrowCachedBatchSerializerSuite.scala
@@ -2276,6 +2276,54 @@ class ArrowCachedBatchSerializerSuite extends QueryTest with SharedSparkSession
     }
   }
 
+  test("nanosecond timestamps round-trip inside complex types") {
+    // Nested nanosecond timestamps go through the same recursive machinery as top-level ones:
+    // ArrowWriter's field writers dispatch recursively on (type, vector), and every container
+    // accessor in ArrowColumnVector wraps its element vector through the constructor that runs
+    // the tagged-struct recognizers -- so the lossless struct representation must round-trip at
+    // any nesting depth, including values outside the int64 epoch-nanos window (~1677-2262)
+    // that the standard interchange encoding cannot represent.
+    val outOfWindow = java.time.LocalDateTime.of(3000, 1, 6, 12, 30, 45, 123456789)
+    val inWindow = java.time.LocalDateTime.of(2025, 1, 6, 12, 30, 45, 987654321)
+    val nanosType = TimestampNTZNanosType(9)
+
+    val arrayDf = singlePartDf(
+      Seq(Seq(outOfWindow, inWindow)), ArrayType(nanosType)).cache()
+    try {
+      assert(arrayDf.count() == 1)
+      val read = arrayDf.collect().head.getSeq[java.time.LocalDateTime](0)
+      assert(read == Seq(outOfWindow, inWindow),
+        s"expected nested nanos to round-trip through an array, got: $read")
+    } finally {
+      arrayDf.unpersist()
+      InMemoryRelation.clearSerializer()
+    }
+
+    val structDf = singlePartDf(
+      Seq(Row(outOfWindow)), StructType(Seq(StructField("ts", nanosType)))).cache()
+    try {
+      assert(structDf.count() == 1)
+      val read = structDf.collect().head.getStruct(0).getAs[java.time.LocalDateTime](0)
+      assert(read == outOfWindow,
+        s"expected nested nanos to round-trip through a struct, got: $read")
+    } finally {
+      structDf.unpersist()
+      InMemoryRelation.clearSerializer()
+    }
+
+    val mapDf = singlePartDf(
+      Seq(Map(1 -> outOfWindow)), MapType(IntegerType, nanosType)).cache()
+    try {
+      assert(mapDf.count() == 1)
+      val read = mapDf.collect().head.getMap[Int, java.time.LocalDateTime](0)
+      assert(read == Map(1 -> outOfWindow),
+        s"expected nested nanos to round-trip through a map value, got: $read")
+    } finally {
+      mapDf.unpersist()
+      InMemoryRelation.clearSerializer()
+    }
+  }
+
   test("nonpositive maxRecordsPerBatch caches all rows in a single batch") {
     // A nonpositive maxRecordsPerBatch means unlimited; without the `<= 0` guard the write
     // iterator would emit zero-row batches forever instead of finishing.


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