[ 
https://issues.apache.org/jira/browse/FLINK-40218?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Ramin Gharib updated FLINK-40218:
---------------------------------
    Description: 
*Description*

The only way to build a \{{Variant}} from JSON today is from a \{{String}}. 
\{{PARSE_JSON}} and the internal 
\{{BinaryVariantInternalBuilder.parseJson(String)}} both create a Jackson 
parser over the text and walk its tokens in \{{buildJson}}.

A caller that already holds parsed JSON cannot use that work. It must write its 
data back to a \{{String}}, which flink-core then parses a second time:
{code}
bytes --(caller parses)--> tree --(toString)--> String --(flink-core parses 
again)--> Variant
{code}
Two things keep callers on this path:
 # *There is no entry point that takes a parser.* \{{parseJson(JsonParser, 
boolean)}} exists but is private.
 # *Jackson types do not cross shading boundaries.* The builder uses Flink's 
shaded \{{org.apache.flink.shaded.jackson2...JsonParser}}. A caller with its 
own, unshaded Jackson cannot pass in its parser or tree. A \{{String}} is the 
only type both sides share.

*Proposed change*

Two entry points on the \{{@Internal}} \{{BinaryVariantInternalBuilder}}:
||The caller holds||Entry point||
|Flink's shaded Jackson \{{JsonParser}}|\{{parseJson(JsonParser, boolean)}}, 
now public|
|JSON in another form, such as a tree from its own Jackson|walk it with the 
existing \{{append*}}, \{{addKey}} and \{{finishWritingObject}} methods, and 
call the new \{{appendJsonNumber(String)}} for numbers|

{\{parseJson(JsonParser, boolean)}} expects the parser on the value's first 
token and leaves it on the value's last token, so a format can read a VARIANT 
field in the middle of a record.

{\{appendJsonNumber(String)}} reads the literal with the same Jackson factory 
and number code as \{{PARSE_JSON}}. A number is therefore stored byte for byte 
like \{{PARSE_JSON}} stores it: the smallest integer, else a decimal, else a 
finite double. Anything that is not exactly one JSON number fails.

A walker over an unshaded Jackson tree then looks like this:
{code:java}
case NUMBER:
    if (node.isIntegralNumber() && node.canConvertToLong()) {
        builder.appendNumeric(node.longValue());
    } else {
        builder.appendJsonNumber(node.asText());
    }
{code}

*Design notes*
 * *Push instead of a token-source interface.* An earlier proposal added a 
pull-based \{{VariantJsonSource}} interface that the builder reads from. It is 
replaced by one push method. The other VARIANT producers push into the builder 
too, such as \{{ToVariantConverter}} for \{{CAST(... AS VARIANT)}}. One method 
is a smaller surface than an interface with an enum. \{{PARSE_JSON}} keeps its 
own code path unchanged.
 * *A String literal, not a \{{Number}}.* \{{PARSE_JSON}} picks the type from 
how a number is written, for example an exponent means DOUBLE. A 
\{{BigDecimal}} cannot tell \{{1e-7}} from \{{0.0000001}}, which 
\{{PARSE_JSON}} stores as DOUBLE and DECIMAL. The text keeps that information.
 * *No hand-written number grammar.* \{{appendJsonNumber}} lets Jackson 
validate the literal, so it rejects \{{+5}}, \{{0x1p4}}, \{{1.5f}} and \{{1,5}} 
exactly like \{{PARSE_JSON}} doe

*Scope*

Moving the \{{json}} format onto the new parser entry-XXXXX. That change is not 
behavior-neutral: theformat rounds VARIANT floats through a \{{double}} today, 
so for example \{{1e400}} becomes the string \{{"Infinity"}}. It needs its
own release note.

*Compatibility*

Additive and \{{@Internal}}. \{{PARSE_JSON}}, \{{TRY_PA} format do not change. 
\{{addKey}} now looks a key up once instead of twice.

*Verifying this change*
 * \{{appendJsonNumber}} is byte-for-byte equal to {{tegers at every width, 
values beyond a long,decimals, exponents and surrounding whitespace.
 * It rejects non-JSON literals, other JSON values s trailing content such as 
\{{5 6}}, and numbersoutside the double range.
 * A parser read in the middle of a document is left.

  was:
The only way to build a \{{Variant}} from JSON today is from a \{{String}}. 
\{{PARSE_JSON}} and the internal 
\{{BinaryVariantInternalBuilder.parseJson(String)}} both create a Jackson 
parser over the text and walk its tokens in \{{buildJson}}.

A caller that already holds parsed JSON cannot use that work. It must write its 
data back to a \{{String}}, which flink-core then parses a second time:
{code}
bytes --(caller parses)--> tree --(toString)--> String --(flink-core parses 
again)--> Variant
{code}
Two things keep callers on this path:
 # *There is no entry point that takes a parser.* \{{parseJson(JsonParser, 
boolean)}} exists but is private.
 # *Jackson types do not cross shading boundaries.* The builder uses Flink's 
shaded \{{org.apache.flink.shaded.jackson2...JsonParser}}. A caller with its 
own, unshaded Jackson cannot pass in its parser or tree. A \{{String}} is the 
only type both sides share.

*Proposed change*

Two entry points on the \{{@Internal}} \{{BinaryVariantInternalBuilder}}:
||The caller holds||Entry point||
|Flink's shaded Jackson \{{JsonParser}}|\{{parseJson(JsonParser, boolean)}}, 
now public|
|JSON in another form, such as a tree from its own Jackson|walk it with the 
existing \{{append*}}, \{{addKey}} and \{{finishWritingObject}} methods, and 
call the new \{{appendJsonNumber(String)}} for numbers|

{\{parseJson(JsonParser, boolean)}} expects the parser on the value's first 
token and leaves it on the value's last token, so a format can read a VARIANT 
field in the middle of a record.

{\{appendJsonNumber(String)}} reads the literal with the same Jackson factory 
and number code as \{{PARSE_JSON}}. A number is therefore stored byte for byte 
like \{{PARSE_JSON}} stores it: the smallest integer, else a decimal, else a 
finite double. Anything that is not exactly one JSON number fails.

A walker over an unshaded Jackson tree then looks li
{code:java}
case NUMBER:
    if (node.isIntegralNumber() && node.canConvertToLong()) {                   
                                                   
builder.appendNumeric(node.longValue());
    } else {                                                                    
                                                   
builder.appendJsonNumber(node.asText());
    }                                                                           
                                           \{code}
                                                                                
                                           *Design notes*
 * *Push instead of a token-source interface.* An earlier proposal added a 
pull-based \{{VariantJsonSource}} interface that builder reads from. It is 
replaced by one push methocers push into the builder too, such 
as\{{ToVariantConverter}} for \{{CAST(... AS VARIANT)}}. One method is a 
smaller surface than an interface with an enum. {{PARSkeeps its own code path 
unchanged.
 * *A String literal, not a \{{Number}}.* \{{PARSE_JSON}} picks the type from 
how a number is written, for example an exponenDOUBLE. A \{{BigDecimal}} cannot 
tell \{{1e-7}} from \{E_JSON}} stores as DOUBLE and DECIMAL. The text keeps 
that information.                                                               
                                            * *No hand-written number grammar.* 
\{{appendJsonNume the literal, so it rejects {{+5}}, \{{0x1p4}},\{{1.5f}} and 
\{{1,5}} exactly like \{{PARSE_JSON}} does.                                     
                                
*Scope*                                                                         
                                           
Moving the \{{json}} format onto the new parser entry point is split into 
FLINK-XXXXX. That change is not behavior-neutral: format rounds VARIANT floats 
through a \{{double}} to0}} becomes the string \{{"Infinity"}}. It needs itsown 
release note.                                                                   
                                       
*Compatibility*                                                                 
                                           
Additive and \{{@Internal}}. \{{PARSE_JSON}}, \{{TRY_PARSE_JSON}} and the 
\{{json}} format do not change. \{{addKey}} now looks once instead of twice.
                                                                                
                                           *Verifying this change*
 * \{{appendJsonNumber}} is byte-for-byte equal to \{{parseJson(String)}} for 
integers at every width, values beyond a long, decimals, exponents and 
surrounding whitespace.
 * It rejects non-JSON literals, other JSON values such as \{{"5"}} or 
\{{[1]}}, trailing content such as \{{5 6}}, and numbers outside the double 
range.
 * A parser read in the middle of a document is left on the value's last token.


> Allow building a Variant from a token source, without a String round-trip
> -------------------------------------------------------------------------
>
>                 Key: FLINK-40218
>                 URL: https://issues.apache.org/jira/browse/FLINK-40218
>             Project: Flink
>          Issue Type: Improvement
>          Components: API / Type Serialization System
>            Reporter: Ramin Gharib
>            Assignee: Ramin Gharib
>            Priority: Major
>              Labels: pull-request-available
>
> *Description*
> The only way to build a \{{Variant}} from JSON today is from a \{{String}}. 
> \{{PARSE_JSON}} and the internal 
> \{{BinaryVariantInternalBuilder.parseJson(String)}} both create a Jackson 
> parser over the text and walk its tokens in \{{buildJson}}.
> A caller that already holds parsed JSON cannot use that work. It must write 
> its data back to a \{{String}}, which flink-core then parses a second time:
> {code}
> bytes --(caller parses)--> tree --(toString)--> String --(flink-core parses 
> again)--> Variant
> {code}
> Two things keep callers on this path:
>  # *There is no entry point that takes a parser.* \{{parseJson(JsonParser, 
> boolean)}} exists but is private.
>  # *Jackson types do not cross shading boundaries.* The builder uses Flink's 
> shaded \{{org.apache.flink.shaded.jackson2...JsonParser}}. A caller with its 
> own, unshaded Jackson cannot pass in its parser or tree. A \{{String}} is the 
> only type both sides share.
> *Proposed change*
> Two entry points on the \{{@Internal}} \{{BinaryVariantInternalBuilder}}:
> ||The caller holds||Entry point||
> |Flink's shaded Jackson \{{JsonParser}}|\{{parseJson(JsonParser, boolean)}}, 
> now public|
> |JSON in another form, such as a tree from its own Jackson|walk it with the 
> existing \{{append*}}, \{{addKey}} and \{{finishWritingObject}} methods, and 
> call the new \{{appendJsonNumber(String)}} for numbers|
> {\{parseJson(JsonParser, boolean)}} expects the parser on the value's first 
> token and leaves it on the value's last token, so a format can read a VARIANT 
> field in the middle of a record.
> {\{appendJsonNumber(String)}} reads the literal with the same Jackson factory 
> and number code as \{{PARSE_JSON}}. A number is therefore stored byte for 
> byte like \{{PARSE_JSON}} stores it: the smallest integer, else a decimal, 
> else a finite double. Anything that is not exactly one JSON number fails.
> A walker over an unshaded Jackson tree then looks like this:
> {code:java}
> case NUMBER:
>     if (node.isIntegralNumber() && node.canConvertToLong()) {
>         builder.appendNumeric(node.longValue());
>     } else {
>         builder.appendJsonNumber(node.asText());
>     }
> {code}
> *Design notes*
>  * *Push instead of a token-source interface.* An earlier proposal added a 
> pull-based \{{VariantJsonSource}} interface that the builder reads from. It 
> is replaced by one push method. The other VARIANT producers push into the 
> builder too, such as \{{ToVariantConverter}} for \{{CAST(... AS VARIANT)}}. 
> One method is a smaller surface than an interface with an enum. 
> \{{PARSE_JSON}} keeps its own code path unchanged.
>  * *A String literal, not a \{{Number}}.* \{{PARSE_JSON}} picks the type from 
> how a number is written, for example an exponent means DOUBLE. A 
> \{{BigDecimal}} cannot tell \{{1e-7}} from \{{0.0000001}}, which 
> \{{PARSE_JSON}} stores as DOUBLE and DECIMAL. The text keeps that information.
>  * *No hand-written number grammar.* \{{appendJsonNumber}} lets Jackson 
> validate the literal, so it rejects \{{+5}}, \{{0x1p4}}, \{{1.5f}} and 
> \{{1,5}} exactly like \{{PARSE_JSON}} doe
> *Scope*
> Moving the \{{json}} format onto the new parser entry-XXXXX. That change is 
> not behavior-neutral: theformat rounds VARIANT floats through a \{{double}} 
> today, so for example \{{1e400}} becomes the string \{{"Infinity"}}. It needs 
> its
> own release note.
> *Compatibility*
> Additive and \{{@Internal}}. \{{PARSE_JSON}}, \{{TRY_PA} format do not 
> change. \{{addKey}} now looks a key up once instead of twice.
> *Verifying this change*
>  * \{{appendJsonNumber}} is byte-for-byte equal to {{tegers at every width, 
> values beyond a long,decimals, exponents and surrounding whitespace.
>  * It rejects non-JSON literals, other JSON values s trailing content such as 
> \{{5 6}}, and numbersoutside the double range.
>  * A parser read in the middle of a document is left.



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

Reply via email to