Ramin Gharib created FLINK-41180:
------------------------------------

             Summary: VARIANT objects sort and search field names in UTF-16 
order instead of UTF-8 byte order
                 Key: FLINK-41180
                 URL: https://issues.apache.org/jira/browse/FLINK-41180
             Project: Flink
          Issue Type: Bug
          Components: API / Core
            Reporter: Ramin Gharib
             Fix For: 2.4.0


The Parquet Variant spec orders the fields of an object by their names, "sorted 
lexicographically (using unsigned byte ordering for UTF-8)". Flink sorts and 
searches them with {{String.compareTo}}, which is UTF-16 order:

{code:java}
// BinaryVariantInternalBuilder.FieldEntry
public int compareTo(FieldEntry other) {
    return key.compareTo(other.key);
}

// BinaryVariant.getFieldByKey, binary search for objects with 32 or more fields
int cmp = getMetadataKey(metadata, id).compareTo(key);
{code}

The two orders differ when, at the first differing character, one name has a 
character from U+10000 up and the other one from U+E000 to U+FFFF. U+FF61 is 
{{EF BD A1}} in UTF-8 and U+1F600 is {{F0 9F 98 80}}, so the spec puts U+FF61 
first. UTF-16 compares {{FF61}} with {{D83D}} and puts U+1F600 first.

h3. Impact

An object in spec order, for example one written by another engine, can hide a 
field from {{getField}}. Probe on master: ASCII keys {{k00}}, {{k01}}, ... plus 
U+FF61 = 100 and U+1F600 = 200, with the last two fields swapped into spec 
order:

||Fields||getField(U+FF61)||getField(U+1F600)||
|31|100|200|
|32|100|null|
|40|100|null|

Below 32 fields the lookup is a linear scan, so the order does not matter there.

The other direction has the same cause. Flink writes objects that are not in 
spec order, so a reader that binary-searches in byte order can miss their 
fields. Spark switched to UTF-8 order in SPARK-58949 and retries in UTF-16 
order on a miss, so it still reads older values. parquet-java switched in 
apache/parquet-java#3746. Not tested against a specific reader.

h3. Proposal

* Sort object fields by their stored UTF-8 key bytes, unsigned, in the builder.
* Binary-search in the same byte order.
* Keep values that are already written in UTF-16 order readable, since they 
live in state, savepoints and external systems. On a miss, fall back to a 
linear scan, but only when the wanted key has a byte of 0xEE or above. For any 
other key both orders agree, so a miss is a real miss.

Sorting by the stored bytes also fixes keys with an unpaired surrogate in 
objects with 32 or more fields. The builder stores {{"a\uD800"}} as the bytes 
of {{"a?"}} but sorts it as {{"a\uD800"}}, so today neither spelling finds the 
field.

h3. Compatibility

Objects written after the change can have a different field order. A Flink 
version without the change can miss such keys in them.

Related: FLINK-41110 compares UTF-8 bytes in the linear search.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to