m0g3r opened a new pull request, #3880:
URL: https://github.com/apache/iceberg-python/pull/3880

   # Rationale for this change
   
   `truncate_upper_bound_text_string()` (`pyiceberg/utils/truncate.py`) builds 
a truncated upper bound
   by incrementing the last character of the truncated value. The increment is 
unconditional:
   
   ```python
   to_inc = ord(chars[i])
   # will raise exception if the highest unicode code is reached
   _next = chr(to_inc + 1)
   ```
   
   The comment covers only the `chr()` overflow case. It misses the surrogate 
range: when the
   character being incremented is `U+D7FF`, the successor is `U+D800`, a lone 
surrogate. `chr()`
   accepts it without raising, so an unencodable string is returned and the 
failure surfaces later,
   at serialization:
   
   ```
   UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in 
position 15: surrogates not allowed
   ```
   
   Surrogates are not Unicode scalar values and have no UTF-8 encoding, so the 
bound cannot be
   written.
   
   This is reachable from ordinary writes. `StatsAggregator.max_as_bytes()` 
calls this helper and
   then serializes the result (`pyiceberg/io/pyarrow.py`), and it runs under 
the default metrics mode
   (`DEFAULT_TRUNCATION_LENGTH = 16`). Any string value longer than the 
truncation length whose
   character at the truncation boundary is `U+D7FF` aborts the write. Both 
stats call sites are
   affected — the Parquet writer's `close()` on the append/overwrite path, and
   `parquet_file_to_data_file()` used by `add_files`.
   
   The fix skips the surrogate range when incrementing, so the successor of 
`U+D7FF` is `U+E000`.
   Since `U+E000 > U+D7FF`, the result is still a valid upper bound, and it is 
encodable. Existing
   behavior is otherwise unchanged: the `chr()` overflow case that previously 
raised `ValueError` and
   fell through to the next character now returns `None` from the helper and 
falls through
   identically.
   
   `truncate_upper_bound_binary_string()` is not affected — it increments a 
byte guarded by `< 255`.
   
   ## Are these changes tested?
   
   Yes.
   
   - `tests/utils/test_truncate.py` — two regression tests: incrementing at the 
surrogate boundary,
     and the case where the last character is at the maximum code point so the 
increment falls back
     to an earlier character that is itself on the surrogate boundary. Both 
assert the result is a
     genuine upper bound and encodes as UTF-8.
   - 
`tests/io/test_pyarrow_stats.py::test_metrics_surrogate_boundary_upper_bound` — 
covers the
     write path that actually breaks, computing statistics from real Parquet 
metadata via
     `data_file_statistics_from_parquet_metadata()`, mirroring the existing
     `test_metrics_invalid_upper_bound` case.
   
   All three fail on `main` with the `UnicodeEncodeError` above and pass with 
the change; this was
   checked in both directions by reverting only `pyiceberg/utils/truncate.py` 
and re-running
   (`3 failed, 19 passed` reverted; `22 passed` with the fix).
   
   I also swept all 1,112,064 Unicode scalar values through the helper and 
confirmed every returned
   bound both encodes as UTF-8 and compares greater than or equal to the input 
value.
   
   - `make lint` — passes, including mypy
   - `make test` — 3972 passed, 3 skipped
   
   Not run: the integration suites (`make test-integration` and the 
cloud-storage suites), which need
   Docker and credentials unavailable here.
   
   ## Are there any user-facing changes?
   
   No API change. Writes that previously failed with `UnicodeEncodeError` now 
succeed. Upper bounds
   for affected values change from unencodable to `U+E000`-terminated; bounds 
for all other values
   are unchanged.
   
   ## AI assistance
   
   Claude Code was used to find the bug, write the change and the tests, and 
run the verification
   described above.
   


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