andygrove commented on code in PR #4763:
URL: https://github.com/apache/datafusion-comet/pull/4763#discussion_r3538630122


##########
native/spark-expr/src/utils.rs:
##########
@@ -373,6 +374,123 @@ pub fn unlikely(b: bool) -> bool {
     b
 }
 
+/// Decode `bytes` as UTF-8 the way Spark renders `StringType` -- `new 
String(bytes, UTF_8)` on the
+/// JVM -- replacing each ill-formed sequence with a single `U+FFFD` and 
skipping the same number of
+/// bytes the JDK's UTF-8 `CharsetDecoder` (action REPLACE) would. Valid UTF-8 
is returned as a
+/// zero-cost borrow.
+///
+/// This intentionally differs from `str::from_utf8_lossy` for surrogate-range 
three-byte sequences
+/// (`ED A0..BF ..`, e.g. CESU-8 / Java modified-UTF-8 supplementary chars) 
and for some other
+/// ill-formed multi-byte units: `from_utf8_lossy` follows the Unicode 
"maximal subpart" rule and
+/// can emit one `U+FFFD` per byte, whereas the JDK collapses certain 
ill-formed units into a single
+/// `U+FFFD`. Matching the JDK byte-for-byte means Comet renders arbitrary 
bytes identically to
+/// Spark -- whether they arrive via a columnar shuffle or a `CAST(binary AS 
string)`. The
+/// per-class malformed lengths below (E0/ED overlong & surrogate handling, 
F0/F4 range checks)
+/// match the observable replacement behavior of the JDK UTF-8 decoder; they 
were determined from
+/// observed `new String(bytes, UTF_8)` output, not by reviewing the OpenJDK 
source.
+pub fn decode_utf8_spark_lossy(bytes: &[u8]) -> Cow<'_, str> {

Review Comment:
   Done in bdf9491. `decode_utf8_spark_lossy` and its JVM-oracle tests now live 
in `datafusion-comet-common` next to `bytes_to_i128`, and the shuffle crate 
imports it from there instead of reaching into `datafusion-comet-spark-expr`.



##########
native/spark-expr/src/utils.rs:
##########
@@ -373,6 +374,123 @@ pub fn unlikely(b: bool) -> bool {
     b
 }
 
+/// Decode `bytes` as UTF-8 the way Spark renders `StringType` -- `new 
String(bytes, UTF_8)` on the
+/// JVM -- replacing each ill-formed sequence with a single `U+FFFD` and 
skipping the same number of
+/// bytes the JDK's UTF-8 `CharsetDecoder` (action REPLACE) would. Valid UTF-8 
is returned as a
+/// zero-cost borrow.
+///
+/// This intentionally differs from `str::from_utf8_lossy` for surrogate-range 
three-byte sequences
+/// (`ED A0..BF ..`, e.g. CESU-8 / Java modified-UTF-8 supplementary chars) 
and for some other
+/// ill-formed multi-byte units: `from_utf8_lossy` follows the Unicode 
"maximal subpart" rule and
+/// can emit one `U+FFFD` per byte, whereas the JDK collapses certain 
ill-formed units into a single
+/// `U+FFFD`. Matching the JDK byte-for-byte means Comet renders arbitrary 
bytes identically to
+/// Spark -- whether they arrive via a columnar shuffle or a `CAST(binary AS 
string)`. The
+/// per-class malformed lengths below (E0/ED overlong & surrogate handling, 
F0/F4 range checks)
+/// match the observable replacement behavior of the JDK UTF-8 decoder; they 
were determined from
+/// observed `new String(bytes, UTF_8)` output, not by reviewing the OpenJDK 
source.
+pub fn decode_utf8_spark_lossy(bytes: &[u8]) -> Cow<'_, str> {
+    // Fast path: well-formed UTF-8 borrows with zero copy (the overwhelmingly 
common case).
+    if let Ok(s) = std::str::from_utf8(bytes) {
+        return Cow::Borrowed(s);
+    }
+
+    const RC: char = '\u{FFFD}';
+    let n = bytes.len();
+    let mut out = String::with_capacity(n);
+    let mut i = 0;
+    while i < n {

Review Comment:
   Added the `// guards above keep cp a valid scalar` note on each of the three 
`char::from_u32(cp).unwrap()` calls in bdf9491.
   
   On the `bytes.get(i + 1)` suggestion, I left the explicit `i + 1 >= n` style 
as is. Several of those bounds checks do double duty: on a truncated lead they 
also drive the `i = n` skip-to-EOF behavior, so folding them into `bytes.get()` 
would only apply cleanly to a subset of branches. Converting just those would 
make the decoder mix two bounds-checking idioms, and for a delicate 
byte-for-byte parity function I would rather keep it uniform. Happy to revisit 
if you feel strongly.



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