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

   [SPARK-XXXXX][SQL] Cast decimal partition values to the user-specified type
   
   ### What changes were proposed in this pull request?
   
   This PR fixes `PartitioningUtils.castPartValueToDesiredType` to convert a 
decimal partition value to the user-specified `DecimalType`.
   
   ```scala
   -    case _: DecimalType => Literal(new JBigDecimal(value)).value
   +    case dt: DecimalType => Cast(Literal(value), dt, ansiEnabled = 
true).eval()
   ```
   
   `Cast` gives the value the declared precision and scale, rounding half up. 
It is in ANSI mode so that a value that the type cannot hold fails here in any 
session. `parsePartitions` already handles that failure according to 
`spark.sql.sources.validatePartitionColumns`.
   
   Alternatives that were not taken:
   - `Cast` in the mode of the session, like the `DATE` and `TIME` cases next 
to it: with `spark.sql.ansi.enabled=false`, a value that overflows or is not a 
number would become `NULL` even when 
`spark.sql.sources.validatePartitionColumns` is `true`. `d=abc` fails today, so 
this would be a regression.
   - Rescale in `ColumnVectorUtils.populate`: this fixes the value for the 
vectorized readers, but a value that overflows is still not validated.
   
   ### Why are the changes needed?
   
   When a file source reads a partition directory with a user-specified decimal 
type, it returns a wrong value without an error if the scale in the directory 
name differs from the declared scale.
   
   ```scala
   spark.range(1).selectExpr("id AS v").write.parquet(s"$base/t/d=1.5")
   spark.read.schema("v LONG, d 
DECIMAL(10,2)").parquet(s"$base/t").select("d").collect()
   // Expected: [1.50], Actual: [0.15]
   ```
   
   `castPartValueToDesiredType` keeps the precision and scale of the directory 
name, so the partition row holds a `Decimal` with the unscaled value `15` and 
scale `1` for a `DECIMAL(10,2)` column. `ColumnVectorUtils.populate` copies 
that unscaled value into a `ConstantColumnVector`, which then applies the 
declared scale when it is read. The other readers are correct only because 
`UnsafeRowWriter` changes the precision and scale of the value when it writes 
the row.
   
   This affects queries as follows.
   - Partition pruning compares the numeric values, so `WHERE d = 1.5` keeps 
the partition and returns `0.15`.
   - A join with a table that has `d = 1.50` returns no rows, with or without a 
runtime bloom filter.
   - A directory that Spark wrote also returns a wrong value if the reader 
declares another scale. For example, `d=1.50` read as `DECIMAL(12,4)` returns 
`0.0150`.
   - A catalog table that does not track its partitions 
(`spark.sql.hive.manageFilesourcePartitions=false`) reads the directories in 
the same way and returns `0.15`.
   
   Partitions tracked in the catalog are not affected, because 
`CatalogTablePartition.toRow` already uses `Cast`.
   
   This is a regression from SPARK-34314 in 
[v3.2.0](https://github.com/apache/spark/releases/tag/v3.2.0) (2021-10-06). By 
code reading, the value went through `Cast` before that, and a `null` result 
failed the validation. I reproduced the wrong values 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 as `DECIMAL(10,2)`.
   
   | Directory | Before, vectorized Parquet and ORC | Before, other readers | 
After, all readers |
   |---|---|---|---|
   | `d=1.5` | `0.15` | `1.50` | `1.50` |
   | `d=2` | `0.02` | `2.00` | `2.00` |
   | `d=1.555` | `15.55` | `1.56` | `1.56` |
   | `d=12345678901.5` | `1234567890.15` | `NULL` | `INVALID_PARTITION_VALUE` |
   
   A value that does not fit the declared precision now fails regardless of 
`spark.sql.ansi.enabled`, and it becomes `NULL` when 
`spark.sql.sources.validatePartitionColumns` is `false`. The reasons are:
   - The SQL migration guide has stated this since Spark 3.0: "An exception is 
thrown if the validation fails".
   - The `INT` and `BOOLEAN` partition values that do not parse already behave 
this way in both ANSI modes, for example `i=99999999999` read as `INT`.
   - It is the behavior before SPARK-34314.
   
   Because the conversion now follows `Cast`, it also trims surrounding 
whitespace in the directory name, and it rounds a value with more fractional 
digits than `DECIMAL(38)` can hold instead of failing.
   
   The SQL migration guide describes this change.
   
   ### How was this patch tested?
   
   Pass the CIs with the newly added test cases. Both of them fail without the 
fix.
   - `ParquetPartitionDiscoverySuite` (V1 and V2) reads `d=1.5`, `d=2` and 
`d=1.555` as `DECIMAL(10,2)`. Without the fix, it returns `0.15`, `0.02` and 
`15.55`.
   - `FileIndexSuite` checks a value that overflows and a value that is not a 
number, in both ANSI modes, with `spark.sql.sources.validatePartitionColumns` 
on and off.
   
   ```
   $ build/sbt "sql/testOnly *ParquetV1PartitionDiscoverySuite 
*ParquetV2PartitionDiscoverySuite *FileIndexSuite -- -z SPARK-XXXXX"
   ```
   
   I also ran a temporary suite, which is not part of this PR. It reads Parquet 
and ORC with the vectorized reader on and off, and JSON and CSV, through both 
V1 and V2, as `DECIMAL(5,2)`, `DECIMAL(10,2)` and `DECIMAL(20,2)`. All 12 
combinations return the same value after the fix. It also shows that catalog 
partitions added by `MSCK REPAIR TABLE` return the same results before and 
after the fix.
   
   ### 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