Shuo Cheng created FLINK-40265:
----------------------------------

             Summary: BinaryStringData.compareTo uses inconsistent UTF-16 and 
UTF-8 ordering for SMP characters
                 Key: FLINK-40265
                 URL: https://issues.apache.org/jira/browse/FLINK-40265
             Project: Flink
          Issue Type: Bug
          Components: Table SQL / Runtime
            Reporter: Shuo Cheng


h3. Description

{{BinaryStringData.compareTo}} uses two different comparison paths:

* If both values have a non-null {{javaObject}}, it delegates to 
{{String.compareTo}}, which compares UTF-16 code units.
* Otherwise, it materializes both values and compares their UTF-8 bytes as 
unsigned bytes.

These orders are not equivalent for comparisons between an SMP character and 
BMP characters in the range U+E000-U+FFFF. As a result, the comparison result 
depends on whether a {{BinaryStringData}} is Java-backed or binary-backed and 
can change after a serializer round trip.

For example, compare GRINNING FACE (😀, U+1F600) with FULLWIDTH EXCLAMATION MARK 
(!, U+FF01):

* UTF-16: D83D DE00 < FF01, so 😀 < !
* UTF-8: F0 9F 98 80 > EF BC 81, so 😀 > !

h3. Reproduce

{code:ajava}
@Test
void compareToIsConsistentAfterSerialization() throws IOException {
    BinaryStringData grinningFace = BinaryStringData.fromString("😀");
    BinaryStringData fullWidthExclamationMark = 
BinaryStringData.fromString("!");

    DataOutputSerializer output = new DataOutputSerializer(16);
    StringDataSerializer.INSTANCE.serialize(grinningFace, output);
    BinaryStringData deserializedGrinningFace =
            (BinaryStringData)
                    StringDataSerializer.INSTANCE.deserialize(
                            new 
DataInputDeserializer(output.getCopyOfBuffer()));

    assertThat(deserializedGrinningFace).isEqualTo(grinningFace);
    
assertThat(deserializedGrinningFace.compareTo(fullWidthExclamationMark)).isPositive();
    assertThat(grinningFace.compareTo(fullWidthExclamationMark))
            
.isEqualTo(deserializedGrinningFace.compareTo(fullWidthExclamationMark));
}
{code}

h3. Actual behavior

After serialization:

* {{grinningFace.compareTo(fullWidthExclamationMark)}} is negative because both 
values retain Java objects and use {{String.compareTo}}.
* {{deserializedGrinningFace.compareTo(fullWidthExclamationMark)}} is positive 
because the deserialized value is binary-backed and comparison uses UTF-8 bytes.

The final assertion fails.

h3. Expected behavior

{{BinaryStringData.compareTo}} should produce the same ordering regardless of 
the backing representation and before/after serialization. The class Javadoc 
and {{SortUtil.putStringNormalizedKey}} already describe/use UTF-8 byte 
ordering, so comparison should follow one canonical ordering.

h3. Impact

This violates the {{Comparable}} contract. The original and deserialized values 
compare equal to each other, but have opposite ordering relative to the same 
third value. It can cause representation-dependent results in generated string 
comparisons, sorting, MIN/MAX, or other operations using {{compareTo}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to