Commit 6f4a41e
[SPARK-59043][SQL] Fix SimplifyCaseConversionExpressions to preserve Unicode case-conversion semantics
### What changes were proposed in this pull request?
In Catalyst Optimizer, the rule `SimplifyCaseConversionExpressions` previously simplified nested mixed case conversions:
- `Upper(Lower(child))` -> `Upper(child)`
- `Lower(Upper(child))` -> `Lower(child)`
However, in the Unicode standard and Java's case mapping semantics (`UTF8String`), mixed case conversion is not idempotent and not symmetric for several Unicode characters:
- For `ı` (U+0131 LATIN SMALL LETTER DOTLESS I): `upper('ı')` = `'I'`, `lower('I')` = `'i'`. Therefore `lower(upper('ı'))` = `'i'`, but `lower('ı')` = `'ı'`.
- For `µ` (U+00B5 MICRO SIGN): `upper('µ')` = `'Μ'` (U+039C), `lower('Μ')` = `'μ'` (U+03BC). Therefore `lower(upper('µ'))` = `'μ'`, but `lower('µ')` = `'µ'`.
- For `ß` (U+00DF LATIN SMALL LETTER SHARP S): `upper('ß')` = `'SS'`, `lower('SS')` = `'ss'`. Therefore `lower(upper('ß'))` = `'ss'`, but `lower('ß')` = `'ß'`.
- For `K` (U+212A KELVIN SIGN): `lower('K')` = `'k'`, `upper('k')` = `'K'`. Therefore `upper(lower('K'))` = `'K'`, but `upper('K')` = `'K'`.
Consequently, simplifying `Lower(Upper(child))` to `Lower(child)` or `Upper(Lower(child))` to `Upper(child)` leads to silent data correctness bugs and causes queries to return different results depending on whether `SimplifyCaseConversionExpressions` is enabled.
This PR fixes the issue by removing the mixed case simplification rules from `SimplifyCaseConversionExpressions`, retaining only same-case idempotent transformations (`Upper(Upper(child))` -> `Upper(child)` and `Lower(Lower(child))` -> `Lower(child)`), which are 100% idempotent across all Unicode code points.
Fixes [SPARK-59043](https://issues.apache.org/jira/browse/SPARK-59043).
### Why are the changes needed?
To prevent incorrect query results and maintain Unicode case-conversion semantic correctness under SQL expression optimization.
### Does this PR introduce _any_ user-facing change?
Yes. Queries with nested mixed case conversions (e.g. `lower(upper(str))`) on Unicode characters now correctly preserve Unicode semantics and return consistent results regardless of optimizer configuration.
### How was this patch tested?
- Updated optimizer unit tests in `SimplifyStringCaseConversionSuite.scala` verifying that mixed case expressions are preserved and same-case expressions are simplified.
- Added end-to-end SQL regression tests in `StringFunctionsSuite.scala` covering Unicode edge cases (`'ı'`, `'µ'`, `'ß'`, `'K'`) under both default optimizer configuration and with rule excluded.
### Was this patch authored or co-authored using generative AI tooling?
No.
Closes #58376 from AnhTtis/SPARK-59043-fix-case-conversion-unicode-semantics.
Authored-by: AnhTtis <nguyenhuuanhtri866@gmail.com>
Signed-off-by: Chao Sun <chao@openai.com>1 parent a6e7607 commit 6f4a41e
4 files changed
Lines changed: 101 additions & 8 deletions
File tree
- docs
- sql
- catalyst/src
- main/scala/org/apache/spark/sql/catalyst/optimizer
- test/scala/org/apache/spark/sql/catalyst/optimizer
- core/src/test/scala/org/apache/spark/sql
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| |||
Lines changed: 7 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1184 | 1184 | | |
1185 | 1185 | | |
1186 | 1186 | | |
1187 | | - | |
1188 | | - | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
1189 | 1194 | | |
1190 | 1195 | | |
1191 | 1196 | | |
1192 | 1197 | | |
1193 | 1198 | | |
1194 | 1199 | | |
1195 | 1200 | | |
1196 | | - | |
1197 | | - | |
1198 | 1201 | | |
1199 | 1202 | | |
1200 | 1203 | | |
| |||
Lines changed: 36 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
| 65 | + | |
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
| 72 | + | |
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
90 | 122 | | |
Lines changed: 57 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1564 | 1564 | | |
1565 | 1565 | | |
1566 | 1566 | | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
| 1582 | + | |
| 1583 | + | |
| 1584 | + | |
| 1585 | + | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
| 1589 | + | |
| 1590 | + | |
| 1591 | + | |
| 1592 | + | |
| 1593 | + | |
| 1594 | + | |
| 1595 | + | |
| 1596 | + | |
| 1597 | + | |
| 1598 | + | |
| 1599 | + | |
| 1600 | + | |
| 1601 | + | |
| 1602 | + | |
| 1603 | + | |
| 1604 | + | |
| 1605 | + | |
| 1606 | + | |
| 1607 | + | |
| 1608 | + | |
| 1609 | + | |
| 1610 | + | |
| 1611 | + | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
| 1615 | + | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
| 1620 | + | |
| 1621 | + | |
| 1622 | + | |
| 1623 | + | |
1567 | 1624 | | |
0 commit comments