Ramin Gharib created FLINK-40845:
------------------------------------

             Summary: VARIANT toString() and result printing still fail on an 
out-of-range TIME value
                 Key: FLINK-40845
                 URL: https://issues.apache.org/jira/browse/FLINK-40845
             Project: Flink
          Issue Type: Sub-task
          Components: API / Core, Table SQL / Runtime
            Reporter: Ramin Gharib
            Assignee: Moritz Manner


FLINK-40828 made \{{Variant#toString()}} and result printing never fail. A 
\{{TIME}} node whose value is outside one day still makes both of them throw.

A \{{TIME}} node stores microseconds since midnight. \{{VariantBuilder}} only 
writes values in \{{[0, 86_400_000_000)}}, but a variant read from bytes 
written by another engine can hold any 8-byte value.

||TIME value (micros)||\{{Variant#toString()}}||Printing 
(\{{VariantCastUtils#toPrintString}})||
|\{{-1}}|throws \{{DateTimeException}}|throws \{{DateTimeException}}|
|\{{Long.MAX_VALUE}}|throws \{{DateTimeException}}|throws 
\{{DateTimeException}}|
|\{{Long.MIN_VALUE}}|\{{"00:00:00"}}|\{{00:00:00.0}}|

The exception is \{{DateTimeException: Invalid value for NanoOfDay (valid 
values 0 - 86399999999999): -1000}}. \{{Long.MIN_VALUE}} does not throw, but it 
renders a wrong value, because \{{micros * 1000}} overflows to \{{0}}.

h3. Cause

{\{TIME}} is decoded as \{{LocalTime.ofNanoOfDay(micros * 1000)}} in 
\{{BinaryVariant#getTime}} and in \{{JsonVariantFormatter#appendNode}}. Nothing 
checks the range, so a bad value surfaces as a \{{DateTimeException}}. The two 
lenient paths only catch \{{VariantTypeException}}:
* \{{JsonVariantFormatter#append}}, which backs \{{BinaryVariant#toString()}}.
* \{{VariantCastUtils#renderValue}} with \{{printing = true}}, which backs 
\{{VariantToStringCastRule}} when printing.

The printing path has a second gap. \{{VariantCastUtils#renderScalar}} throws 
\{{TableRuntimeException}} from its \{{default}} branch, also when printing. 
Every current \{{Variant.Type}} has a case, but a type added later fails 
printing until its case is added.

h3. Proposed fix

# Range-check \{{TIME}} where it is decoded, and throw 
\{{BinaryVariantUtil#malformedVariant()}} for a value outside \{{[0, 
86_400_000_000)}}. Both lenient paths then render \{{<INVALID>}}. Strict 
\{{toJson()}} and \{{CAST}} fail with \{{MALFORMED_VARIANT}} instead of a 
\{{DateTimeException}}. This also fixes the silent \{{Long.MIN_VALUE}} overflow.
# When printing, render a type without a case as \{{<UNKNOWN>}} instead of 
throwing. \{{CAST}} keeps throwing.

h3. Tests

* \{{JsonVariantFormatterTest}}: \{{toString()}} of an array holding \{{1}} and 
a \{{TIME}} of \{{-1}} micros returns \{{[1,"<INVALID>"]}}, and \{{toJson()}} 
throws \{{MALFORMED_VARIANT}}.
* \{{VariantCastUtilsTest}}: \{{toPrintString}} of the same array returns 
\{{[1, <INVALID>]}}.
* \{{BinaryVariantTest}}: \{{getTime()}} of that node throws 
\{{MALFORMED_VARIANT}}.



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

Reply via email to