dongjoon-hyun opened a new pull request, #60043:
URL: https://github.com/apache/spark/pull/60043

   ### What changes were proposed in this pull request?
   
   This PR fixes `PartitioningUtils.castPartValueToDesiredType` to validate a 
`DATE`, `TIME` or timestamp partition value regardless of 
`spark.sql.ansi.enabled`, as SPARK-60797 did for `DECIMAL`.
   
   ```scala
        case DateType =>
   -      Cast(Literal(value), DateType, Some(zoneId.getId)).eval()
   -    case tt: TimeType => Cast(Literal(unescapePathName(value)), tt).eval()
   +      Cast(Literal(value), DateType, Some(zoneId.getId), ansiEnabled = 
true).eval()
   +    case tt: TimeType => Cast(Literal(unescapePathName(value)), tt, 
ansiEnabled = true).eval()
        // Timestamp types
        case dt if AnyTimestampType.acceptsType(dt) || 
AnyTimestampNanoType.acceptsType(dt) =>
   -      Try {
   -        Cast(Literal(unescapePathName(value)), dt, 
Some(zoneId.getId)).eval()
   -      }.getOrElse {
   -        Cast(Cast(Literal(value), DateType, Some(zoneId.getId)), dt).eval()
   -      }
   +      Cast(Literal(unescapePathName(value)), dt, Some(zoneId.getId), 
ansiEnabled = true).eval()
   ```
   
   `Cast` is in ANSI mode so that a value that cannot be cast fails in any 
session. `parsePartitions` already handles that failure according to 
`spark.sql.sources.validatePartitionColumns`. For a valid value, the ANSI cast 
returns the same value as before, because each ANSI string parser is the 
non-ANSI one that throws an error instead of returning `NULL`.
   
   The timestamp case no longer falls back to a cast to `DATE`. A cast from a 
string to a timestamp type already accepts a date without a time, such as 
`2020-01-01`, so the fallback mattered only for a value with a valid date and 
an invalid time. For `TIMESTAMP` and `TIMESTAMP_LTZ(p)`, it failed anyway, 
because the outer cast from `DATE` has no time zone. For `TIMESTAMP_NTZ` and 
`TIMESTAMP_NTZ(p)`, it returned the start of the day, so a value such as 
`2020-01-01 25:00:00` passed the validation in an ANSI session.
   
   ### Why are the changes needed?
   
   When a file source reads a partition directory with a user-specified `DATE`, 
`TIME` or timestamp type in a session with `spark.sql.ansi.enabled=false`, a 
value that cannot be cast to the type becomes `NULL`, even when 
`spark.sql.sources.validatePartitionColumns` is `true`, the default. In such a 
session, `Cast` returns `NULL` instead of failing, so `parsePartitions` never 
sees an error.
   
   ```scala
   spark.conf.set("spark.sql.ansi.enabled", false)
   spark.range(1).selectExpr("id AS v").write.parquet(s"$base/a=abc")
   spark.read.schema("v LONG, a DATE").parquet(base).select("a").collect()
   // Expected: INVALID_PARTITION_VALUE, Actual: [null]
   ```
   
   The SQL migration guide has stated since Spark 3.0 that "An exception is 
thrown if the validation fails". `INT`, `BOOLEAN`, `DECIMAL` and interval 
partition values already fail in both modes.
   
   This is a regression from SPARK-34314 in 
[v3.2.0](https://github.com/apache/spark/releases/tag/v3.2.0) (2021-10-06), 
which removed the `null` check after the cast. I reproduced it for `DATE`, 
`TIMESTAMP` and `TIMESTAMP_NTZ` with 
[v4.0.4](https://github.com/apache/spark/releases/tag/v4.0.4) (2026-07-12), 
[v4.1.3](https://github.com/apache/spark/releases/tag/v4.1.3) (2026-07-11), 
[v4.2.0](https://github.com/apache/spark/releases/tag/v4.2.0) (2026-07-11) and 
[v4.3.0](https://github.com/apache/spark/releases/tag/v4.3.0) (2026-10-27).
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. These are the results of reading each directory with 
`spark.sql.sources.validatePartitionColumns=true`.
   
   | Directory | Type | Before, ANSI on | Before, ANSI off | After, both modes |
   |---|---|---|---|---|
   | `a=abc` | `DATE`, `TIME(6)`, `TIMESTAMP`, `TIMESTAMP_NTZ` | 
`INVALID_PARTITION_VALUE` | `NULL` | `INVALID_PARTITION_VALUE` |
   | `a=2020-01-01 25%3A00%3A00` | `TIMESTAMP` | `INVALID_PARTITION_VALUE` | 
`NULL` | `INVALID_PARTITION_VALUE` |
   | `a=2020-01-01 25%3A00%3A00` | `TIMESTAMP_NTZ` | `2020-01-01 00:00:00` | 
`NULL` | `INVALID_PARTITION_VALUE` |
   
   With `spark.sql.sources.validatePartitionColumns=false`, all of them are 
`NULL` after this PR. Valid values, including a date without a time read as a 
timestamp type, are read as before in both modes. Partitions tracked in the 
catalog are not affected, because `CatalogTablePartition.toRow` casts their 
values separately.
   
   The SQL migration guide describes this change.
   
   ### How was this patch tested?
   
   Pass the CIs with the newly added test case, which fails without the fix. 
`FileIndexSuite` reads `a=foo` as `DATE` and `TIME(6)`, and `a=2020-01-01 
25%3A00%3A00` as `TIMESTAMP_NTZ`, in both ANSI modes, with 
`spark.sql.sources.validatePartitionColumns` on and off.
   
   ```
   $ build/sbt "sql/testOnly *FileIndexSuite -- -z SPARK-60825"
   ```
   
   I also ran a temporary suite, which is not part of this PR. It reads 19 
directory names as `DATE`, `TIME(6)`, `TIMESTAMP`, `TIMESTAMP_NTZ`, 
`TIMESTAMP_LTZ(9)`, `TIMESTAMP_NTZ(9)`, two interval types, `INT`, `BOOLEAN` 
and `DECIMAL(5,2)`, in both ANSI modes, with the validation on and off.
   - After this PR, the two ANSI modes return the same result for all 418 
combinations of directory, type and validation. Before this PR, they differed 
only for the datetime types.
   - With ANSI mode on, the only results that changed are those of 
`TIMESTAMP_NTZ` and `TIMESTAMP_NTZ(9)` for the four directories with a valid 
date and an invalid time, such as `a=2020-01-01 xyz`.
   - With ANSI mode off, the only results that changed are `NULL` values that 
became `INVALID_PARTITION_VALUE` with the validation on.
   
   It also casts 32 values to each timestamp type, with the cast in this PR and 
with the removed fallback alone. The fallback returned a value only for 
`TIMESTAMP_NTZ` and `TIMESTAMP_NTZ(9)`. The cast in this PR accepts each of 
those values, such as `2020`, `2020-1-1` or `2020-01-01 12%3A34`, except the 
values whose time part does not parse, such as `2020-01-01T` or `2020-01-01 
UTC`.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Opus 5.5


-- 
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