This is an automated email from the ASF dual-hosted git repository.
uros-b 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 ab102b93204e [SPARK-58743][SQL][TEST] Add nanosecond-timestamp
coverage for mode
ab102b93204e is described below
commit ab102b93204e9e73ab8cc167eeaafa47d3f4d838
Author: Stevo Mitric <[email protected]>
AuthorDate: Fri Aug 14 06:26:55 2026 +0000
[SPARK-58743][SQL][TEST] Add nanosecond-timestamp coverage for mode
### What changes were proposed in this pull request?
Add an end-to-end test to `TimestampNanosFunctionsSuiteBase` (run in both
ANSI modes) asserting that mode returns the most-frequent value keyed on the
full nanos value (the frequent and rare values differ only within the
microsecond) and preserves the input precision and family (NTZ/LTZ). Also add a
deterministic golden SQL case (`mode(...)` with a unique most-frequent value)
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 #57966 from stevomitric/stevomitric/nanos-mode-tests.
Authored-by: Stevo Mitric <[email protected]>
Signed-off-by: Uros Bojanic <[email protected]>
(cherry picked from commit f4c3faab6f0e9c7af454a026cca87ffa011811c4)
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 | 9 +++++++
.../sql-tests/inputs/timestamp-ntz-nanos.sql | 8 ++++++
.../sql-tests/results/timestamp-ltz-nanos.sql.out | 11 ++++++++
.../sql-tests/results/timestamp-ntz-nanos.sql.out | 11 ++++++++
.../sql/TimestampNanosFunctionsSuiteBase.scala | 30 ++++++++++++++++++++++
7 files changed, 91 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 cca0a1e7d836..72b4b63b0a55 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
@@ -740,6 +740,17 @@ Sort [c#x ASC NULLS FIRST], true
+- LocalRelation [c#x]
+-- !query
+SELECT mode(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 [mode(c#x, 0, 0, None) AS mode(c)#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 d8ff98fbe760..a2c519731b7c 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
@@ -668,6 +668,17 @@ Sort [c#x ASC NULLS FIRST], true
+- LocalRelation [c#x]
+-- !query
+SELECT mode(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 [mode(c#x, 0, 0, None) AS mode(c)#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 9133dda2e006..859768a514b4 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
@@ -220,6 +220,15 @@ SELECT c, count(*) FROM VALUES
(TIMESTAMP_LTZ '2020-01-01 00:00:00.000000001 UTC') AS t(c)
GROUP BY c ORDER BY c;
+-- SPARK-56822: mode over nanosecond-precision TIMESTAMP_LTZ. Frequencies are
counted on the full
+-- nanos value, so the most-frequent value is selected down to the
sub-microsecond and the result
+-- type stays TIMESTAMP_LTZ(9); the value renders in the session time zone
(America/Los_Angeles).
+-- .000000001 appears twice, .000000999 once.
+SELECT mode(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 d145ecb7146a..cc6638e42494 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
@@ -193,6 +193,14 @@ SELECT c, count(*) FROM VALUES
(TIMESTAMP_NTZ '2020-01-01 00:00:00.000000001') AS t(c)
GROUP BY c ORDER BY c;
+-- SPARK-56822: mode over nanosecond-precision TIMESTAMP_NTZ. Frequencies are
counted on the full
+-- nanos value, so the most-frequent value is selected down to the
sub-microsecond and the result
+-- type stays TIMESTAMP_NTZ(9). .000000001 appears twice, .000000999 once.
+SELECT mode(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 31a40e0d80a0..385e8cecaff5 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
@@ -832,6 +832,17 @@ struct<c:timestamp_ltz(9),count(1):bigint>
2019-12-31 16:00:00.000000999 1
+-- !query
+SELECT mode(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<mode(c):timestamp_ltz(9)>
+-- !query output
+2019-12-31 16:00:00.000000001
+
+
-- !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 a39b70a3d4de..40dc2ddc66c0 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
@@ -750,6 +750,17 @@ struct<c:timestamp_ntz(9),count(1):bigint>
2020-01-01 00:00:00.000000999 1
+-- !query
+SELECT mode(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<mode(c):timestamp_ntz(9)>
+-- !query output
+2020-01-01 00:00:00.000000001
+
+
-- !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 3f15cb2ef3cd..20a140491b2f 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
@@ -710,6 +710,36 @@ abstract class TimestampNanosFunctionsSuiteBase extends
SharedSparkSession {
}
}
+ // mode over nanosecond-precision timestamps (SPARK-56822). `Mode` counts
frequencies in an
+ // `OpenHashMap` keyed on the physical `TimestampNanosVal` (its
`equals`/`hashCode` cover the full
+ // `(epochMicros, nanosWithinMicro)` pair) and returns `child.dataType`, so
the most-frequent
+ // value is selected on the full nanos value and its precision and family
(NTZ/LTZ) are preserved.
+
+ test("SPARK-56822: mode over nanosecond-precision timestamps returns the
most frequent value") {
+ Seq(7, 8, 9).foreach { p =>
+ val schema = new StructType()
+ .add("ntz", TimestampNTZNanosType(p))
+ .add("ltz", TimestampLTZNanosType(p))
+ // The frequent value (3 rows) and the rare one (1 row) differ only
within the microsecond, so
+ // frequency counting must key on the full nanos value; a NULL row is
ignored. The fractions
+ // are multiples of 100ns, exact at every p in [7, 9]. There is a unique
most-frequent value,
+ // so the result is deterministic without a WITHIN GROUP / deterministic
argument.
+ val ldtHot = LocalDateTime.parse("2020-01-01T00:00:00.000000100")
+ val ldtCold = LocalDateTime.parse("2020-01-01T00:00:00.000000900")
+ val insHot = Instant.parse("2020-01-01T00:00:00.000000100Z")
+ val insCold = Instant.parse("2020-01-01T00:00:00.000000900Z")
+ val data = Seq(
+ Row(ldtHot, insHot), Row(ldtHot, insHot), Row(ldtHot, insHot),
+ Row(ldtCold, insCold), Row(null, null))
+ val df = spark.createDataFrame(spark.sparkContext.parallelize(data),
schema)
+
+ val res = df.selectExpr("mode(ntz)", "mode(ltz)")
+ // The result keeps the family (NTZ/LTZ) and precision of the input.
+ assert(res.schema.map(_.dataType) === Seq(TimestampNTZNanosType(p),
TimestampLTZNanosType(p)))
+ checkAnswer(res, Row(ldtHot, insHot))
+ }
+ }
+
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]