This is an automated email from the ASF dual-hosted git repository.
uros-b 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 8ca77e0945d9 [SPARK-58742][SQL][TEST] Add nanosecond-timestamp
coverage for collect_set
8ca77e0945d9 is described below
commit 8ca77e0945d9299d697b184914223ee36cb0b604
Author: Stevo Mitric <[email protected]>
AuthorDate: Sat Aug 15 10:26:34 2026 +0000
[SPARK-58742][SQL][TEST] Add nanosecond-timestamp coverage for collect_set
### What changes were proposed in this pull request?
Add an end-to-end test to `TimestampNanosFunctionsSuiteBase` (run in both
ANSI modes) asserting that collect_set deduplicates on the full nanosecond
value: sub-microsecond-distinct values stay distinct at p=9 but collapse when
floored below the grid at p in [7, 8]. Also add a deterministic golden SQL case
(`sort_array(collect_set(...))`) to `timestamp-ntz-nanos.sql` /
`timestamp-ltz-nanos.sql`.
### Why are the changes needed?
Extend test coverage for timestamp nanosecond precision datatype.
### Does this PR introduce _any_ user-facing change?
No, test only change.
### How was this patch tested?
Test-only change.
### Was this patch authored or co-authored using generative AI tooling?
Co-Authored-By: Claude Code 4.8
Closes #57965 from stevomitric/stevomitric/nanos-collect-set-tests.
Authored-by: Stevo Mitric <[email protected]>
Signed-off-by: Uros Bojanic <[email protected]>
---
.../analyzer-results/timestamp-ltz-nanos.sql.out | 11 +++++++
.../analyzer-results/timestamp-ntz-nanos.sql.out | 11 +++++++
.../sql-tests/inputs/timestamp-ltz-nanos.sql | 10 +++++++
.../sql-tests/inputs/timestamp-ntz-nanos.sql | 9 ++++++
.../sql-tests/results/timestamp-ltz-nanos.sql.out | 11 +++++++
.../sql-tests/results/timestamp-ntz-nanos.sql.out | 11 +++++++
.../sql/TimestampNanosFunctionsSuiteBase.scala | 34 ++++++++++++++++++++++
7 files changed, 97 insertions(+)
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
index 93c3ef991b96..fd2179dd3cdf 100644
---
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
+++
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
@@ -751,6 +751,17 @@ Aggregate [mode(c#x, 0, 0, None) AS mode(c)#x]
+- LocalRelation [c#x]
+-- !query
+SELECT sort_array(collect_set(c)) FROM VALUES
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000999 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC') AS t(c)
+-- !query analysis
+Aggregate [sort_array(collect_set(c#x, 0, 0, true), true) AS
sort_array(collect_set(c), true)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
-- !query
SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789')
-- !query analysis
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
index 02484a99b4b7..5445f457e3ab 100644
---
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
+++
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
@@ -679,6 +679,17 @@ Aggregate [mode(c#x, 0, 0, None) AS mode(c)#x]
+- LocalRelation [c#x]
+-- !query
+SELECT sort_array(collect_set(c)) FROM VALUES
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001'),
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000999'),
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001') AS t(c)
+-- !query analysis
+Aggregate [sort_array(collect_set(c#x, 0, 0, true), true) AS
sort_array(collect_set(c), true)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
-- !query
SELECT unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789')
-- !query analysis
diff --git
a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
index 9a172a2fe2ae..1840ca375ea4 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
@@ -229,6 +229,16 @@ SELECT mode(c) FROM VALUES
(TIMESTAMP_LTZ '2020-01-01 00:00:00.000000999 UTC'),
(TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC') AS t(c);
+-- SPARK-56822: collect_set over nanosecond-precision TIMESTAMP_LTZ. It
deduplicates on the full
+-- sub-microsecond value: the two .000000001 rows collapse to one, the
.000000999 row stays, so the
+-- sorted set has two distinct elements and the element type stays
TIMESTAMP_LTZ(9); values render
+-- in the session time zone (America/Los_Angeles). collect_set order is
non-deterministic, so the
+-- output is stabilized with sort_array.
+SELECT sort_array(collect_set(c)) FROM VALUES
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000999 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC') AS t(c);
+
-- SPARK-57528: unix_timestamp / to_unix_timestamp over nanosecond-precision
values. The result is
-- whole-second BIGINT; the sub-second digits are dropped. A literal without
an explicit zone is
-- read in the session time zone (America/Los_Angeles, UTC-08:00); an
explicit-zone literal fixes
diff --git
a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
index 2a571619cf55..7fb6fdcbc019 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
@@ -201,6 +201,15 @@ SELECT mode(c) FROM VALUES
(TIMESTAMP_NTZ '2020-01-01 00:00:00.000000999'),
(TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001') AS t(c);
+-- SPARK-56822: collect_set over nanosecond-precision TIMESTAMP_NTZ. It
deduplicates on the full
+-- sub-microsecond value: the two .000000001 rows collapse to one, the
.000000999 row stays, so the
+-- sorted set has two distinct elements and the element type stays
TIMESTAMP_NTZ(9). collect_set
+-- order is non-deterministic, so the output is stabilized with sort_array.
+SELECT sort_array(collect_set(c)) FROM VALUES
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001'),
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000999'),
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001') AS t(c);
+
-- SPARK-57528: unix_timestamp / to_unix_timestamp over nanosecond-precision
values. The result is
-- whole-second BIGINT; the sub-second digits are dropped and NTZ applies no
zone shift, so the
-- wall-clock value is read as the epoch instant.
diff --git
a/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
index 4c036133a551..a28831c6b7d8 100644
--- a/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
@@ -843,6 +843,17 @@ struct<mode(c):timestamp_ltz(9)>
2019-12-31 16:00:00.000000001
+-- !query
+SELECT sort_array(collect_set(c)) FROM VALUES
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000999 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC') AS t(c)
+-- !query schema
+struct<sort_array(collect_set(c), true):array<timestamp_ltz(9)>>
+-- !query output
+[2019-12-31 16:00:00.000000001,2019-12-31 16:00:00.000000999]
+
+
-- !query
SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789')
-- !query schema
diff --git
a/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
index e10a25456268..bbec01467ac2 100644
--- a/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
@@ -761,6 +761,17 @@ struct<mode(c):timestamp_ntz(9)>
2020-01-01 00:00:00.000000001
+-- !query
+SELECT sort_array(collect_set(c)) FROM VALUES
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001'),
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000999'),
+ (TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001') AS t(c)
+-- !query schema
+struct<sort_array(collect_set(c), true):array<timestamp_ntz(9)>>
+-- !query output
+[2020-01-01 00:00:00.000000001,2020-01-01 00:00:00.000000999]
+
+
-- !query
SELECT unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789')
-- !query schema
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
b/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
index 20a140491b2f..f6f3d105d462 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
@@ -740,6 +740,40 @@ abstract class TimestampNanosFunctionsSuiteBase extends
SharedSparkSession {
}
}
+ // collect_set over nanosecond-precision timestamps (SPARK-56822).
`CollectSet` deduplicates via a
+ // `HashSet` keyed on the physical `TimestampNanosVal`, whose
`equals`/`hashCode` cover the full
+ // `(epochMicros, nanosWithinMicro)` pair, so sub-microsecond-distinct
values are kept distinct;
+ // the result element type is exactly `child.dataType`.
+
+ test("SPARK-56822: collect_set over nanos deduplicates on the full
sub-microsecond value") {
+ // Two values share the microsecond and differ only in the last nanosecond
digit. Flooring the
+ // input to precision `p` collapses them when p < 9 (the distinguishing
digit is below the
+ // grid), but keeps them apart at p = 9. collect_set must dedup on the
full stored value,
+ // not on micros.
+ Seq(7, 8, 9).foreach { p =>
+ val schema = new StructType().add("ntz", TimestampNTZNanosType(p))
+ val data = Seq(
+ Row(LocalDateTime.parse("2020-01-01T12:34:56.123456780")),
+ Row(LocalDateTime.parse("2020-01-01T12:34:56.123456789")),
+ Row(LocalDateTime.parse("2020-01-01T12:34:56.123456780")))
+ val df = spark.createDataFrame(spark.sparkContext.parallelize(data),
schema)
+
+ val res = df.selectExpr("collect_set(ntz)")
+ assert(res.schema.head.dataType === ArrayType(TimestampNTZNanosType(p),
containsNull = false))
+
+ // Values are floored to `p` on ingestion, so the distinct set depends
on `p`.
+ val expected = p match {
+ case 7 => Set(LocalDateTime.parse("2020-01-01T12:34:56.123456700"))
+ case 8 => Set(LocalDateTime.parse("2020-01-01T12:34:56.123456780"))
+ case _ => Set(
+ LocalDateTime.parse("2020-01-01T12:34:56.123456780"),
+ LocalDateTime.parse("2020-01-01T12:34:56.123456789"))
+ }
+ val collected = res.collect().head.getSeq[LocalDateTime](0).toSet
+ assert(collected === expected, s"collect_set(p=$p) expected $expected,
got $collected")
+ }
+ }
+
test("SPARK-57816: date_format / to_char / to_varchar over
nanosecond-precision timestamps") {
// The 9-`S` pattern is a fixed-width fraction field, so it always emits 9
digits; truncating to
// precision `p` zeros the low digits (floor); it does not drop them. The
session zone is
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]