You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: make ToLower Unicode-aware via utf8proc (#760)
Replaces the ASCII-only `StringUtils::ToLower` with a Unicode-aware
implementation backed by
[utf8proc](https://github.com/JuliaStrings/utf8proc), aligning
case-insensitive name handling with Iceberg Java's
`toLowerCase(Locale.ROOT)` for simple (1:1) case mappings. The few code
points where simple and full case mapping differ (e.g. `İ` U+0130) are
tracked in #808.
- `ToLower` lower-cases UTF-8 using utf8proc simple (1:1) case mapping
(e.g. `CAFÉ` → `café`, `GROẞE` → `große`). Pure-ASCII input takes a
byte-wise fast path and never touches utf8proc.
- Invalid UTF-8 is handled as a correctness guarantee (raised in
review): `ToLower` is total — a byte that does not begin a valid UTF-8
sequence is passed through unchanged and decoding resumes at the next
byte, so the valid code points around it are still lower-cased. The
comparisons below therefore compare invalid bytes verbatim.
- `EqualsIgnoreCase` and `StartsWithIgnoreCase` fold through `ToLower`,
so they are case-insensitive for non-ASCII letters too. Both take an
allocation-free ASCII fast path and fall back to `ToLower` only when a
non-ASCII byte is present.
- `StartsWithIgnoreCase` no longer assumes case mapping preserves byte
length — the old byte-slice guard mishandled length-changing maps (e.g.
`İ` U+0130 → `i`). Now `"İx"` starts with `"i"`, and `"i"` starts with
`"İ"`.
- `ToUpper` is intentionally left ASCII-only — it only normalizes ASCII
enum/codec strings, and simple case mapping would be wrong for some
letters (e.g. `ß` would stay unchanged instead of becoming `SS`).
- utf8proc is pinned to 2.10.0 and wired into both the CMake (vendored
via FetchContent / system package) and Meson
(`subprojects/utf8proc.wrap`) builds, with matching source hashes and
correct static-vs-shared linkage on Windows.
## Testing
- `string_util_test.cc`: `ToLowerUnicode` (incl. invalid / truncated /
stray-continuation bytes), `ToUpperAsciiOnly`, and Unicode +
invalid-UTF-8 cases for `EqualsIgnoreCase` / `StartsWithIgnoreCase`.
- `IgnoreCaseAgreesWithToLowerOracle` exhaustively checks both ASCII
fast paths against the `ToLower` oracle over all short strings drawn
from an alphabet with length-changing maps (`İ`→`i`, `K`→`k`) and
invalid UTF-8.
## Remaining work (tracked in #808)
- Full case-mapping parity with Java for the code points where simple ≠
full mapping (e.g. `İ` → `i̇`).
- A streaming (allocation-free) non-ASCII path for `EqualsIgnoreCase` /
`StartsWithIgnoreCase`.
Part of #613 — does not fully fix it; #808 holds the design and
remaining plan.
Follow-up to #748.
0 commit comments