MaxGekk commented on code in PR #56910:
URL: https://github.com/apache/spark/pull/56910#discussion_r3502149007


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/interface.scala:
##########
@@ -1047,6 +1047,7 @@ object CatalogColumnStat extends Logging {
       case TimestampType => getTimestampFormatter(isParsing = true).parse(s)
       case TimestampNTZType =>
         getTimestampFormatter(isParsing = true, forTimestampNTZ = 
true).parse(s)
+      case _: TimeType => TimeFormatter(isParsing = true).parse(s)

Review Comment:
   These two TIME `(de)serialization` arms aren't covered by the new tests — 
the `FilterEstimationSuite` TIME cases use whole-second values and build 
`ColumnStat` in-memory, so they never round-trip through 
`toExternalString`/`fromExternalString` (which is why the truncation on the 
format side goes unnoticed). Worth a sub-second TIME min/max round-trip in the 
existing temporal stats coverage (`StatisticsCollectionSuite` — its type list 
and `checkTimestampStats` already cover `DateType`/`TimestampType`); a 
fractional value there would catch the truncation.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/statsEstimation/FilterEstimationSuite.scala:
##########
@@ -523,6 +533,44 @@ class FilterEstimationSuite extends 
StatsEstimationTestBase {
       expectedRowCount = 3)
   }
 
+  test("ctime = cast('10:00:00' AS TIME)") {
+    val t10 = DateTimeUtils.localTimeToNanos(java.time.LocalTime.of(10, 0, 0))
+    validateEstimatedStats(
+      Filter(EqualTo(attrTime, Literal(t10, TimeType())),
+        childStatsTestPlan(Seq(attrTime), 10L)),
+      Seq(attrTime -> ColumnStat(distinctCount = Some(1),
+        min = Some(t10), max = Some(t10),
+        nullCount = Some(0), avgLen = Some(8), maxLen = Some(8))),
+      expectedRowCount = 1)
+  }
+
+  test("ctime < cast('12:00:00' AS TIME)") {
+    // 12:00 is 43200000000000L nanos. Range is [08:00, 17:00] = 10 distinct 
values.
+    // Fraction: (12:00 - 08:00) / (17:00 - 08:00) = 4/9 hours => ~4.44 => 
rounded to 5

Review Comment:
   Minor: `4/9` is a dimensionless selectivity ratio — the hour spans in 
numerator and denominator cancel, so "hours" is misleading. And `~4.44 => 
rounded to 5` is actually a ceiling (4.44 wouldn't round to 5). Maybe: `(12:00 
- 08:00) / (17:00 - 08:00) = 4/9 of 10 rows => ~4.44 => ceil => 5`.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/interface.scala:
##########
@@ -1073,6 +1074,7 @@ object CatalogColumnStat extends Logging {
       case TimestampNTZType =>
         getTimestampFormatter(isParsing = false, forTimestampNTZ = true)
           .format(v.asInstanceOf[Long])
+      case _: TimeType => TimeFormatter(isParsing = 
false).format(v.asInstanceOf[Long])

Review Comment:
   **Blocking:** this truncates sub-second precision. `TimeFormatter(isParsing 
= false)` resolves to `DefaultTimeFormatter`, which overrides only `parse` (→ 
`stringToTimeAnsi`, preserves nanos) and inherits `format` from 
`Iso8601TimeFormatter` with the fractionless `"HH:mm:ss"` pattern. So a TIME 
min/max of `12:00:00.123456` (nanos `43200123456000`) is serialized as 
`"12:00:00"` and read back by `fromExternalString` as `12:00:00.000000` — a 
wrong, truncated min/max persisted for any fractional TIME column, which 
silently corrupts the range estimates this PR is adding. 
(`fromExternalString`'s parse side is fine; only this format side is lossy.)
   
   Contrast `TimestampType` just above, which formats with the `.SSSSSS` 
pattern (lossless to micros). Use a fraction-preserving formatter here:
   ```suggestion
         case _: TimeType => 
TimeFormatter.getFractionFormatter().format(v.asInstanceOf[Long])
   ```
   `FractionTimeFormatter` preserves up to nanosecond resolution (trailing 
zeros trimmed) and round-trips with the `stringToTimeAnsi` parse on the read 
side.
   
   This path is reachable and net-new: `AnalyzeColumnCommand.supportsType` 
accepts `_: DatetimeType` (TimeType extends it), so `ANALYZE … FOR COLUMNS` 
over a TIME column collects min/max and persists through here. Pre-PR there was 
no TimeType arm, so it hit the `case _ => throw 
columnStatisticsSerializationNotSupportedError` fallthrough — i.e. this PR is 
what enables ANALYZE-on-TIME, so the lossy serialization ships as new behavior. 
(Histogram bins serialize as raw `Double` and are fine; only these `min`/`max` 
companions truncate.)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to