[FLINK-37925][table] Support casting from VARIANT to primitive types#28733
[FLINK-37925][table] Support casting from VARIANT to primitive types#28733raminqaf wants to merge 5 commits into
Conversation
Add CAST/TRY_CAST from a VARIANT to primitive scalar types. Numeric targets are lenient: a variant holding any numeric kind converts to the requested numeric type with regular numeric cast semantics, so a JSON integer stored in the smallest fitting type still widens (e.g. CAST(PARSE_JSON('42') AS INT)). Non-numeric targets require the stored value to be of the matching kind. On a type mismatch CAST fails the job and TRY_CAST returns NULL.
Character strings and TIME are out of scope: strings are handled by the display-oriented VariantToStringCastRule and via JSON_STRING, and the variant type model has no TIME kind. Constructed types (ARRAY, ROW, STRUCTURED) are left to FLINK-37926.
…types Cover cast validation (LogicalTypeCastsTest), rule resolution (CastRuleProviderTest), the generated cast executor including numeric widening/narrowing and type-mismatch failures (CastRulesTest), and an end-to-end CAST/TRY_CAST over PARSE_JSON (CastFunctionITCase).
Mark VARIANT as an explicit cast source for scalar targets in the casting matrix and describe the lenient numeric behavior. Note that TIME and character-string targets are unsupported.
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: | ||
| return true; | ||
| default: | ||
| // TIME has no counterpart in the Variant type model. CHARACTER_STRING is handled by |
There was a problem hiding this comment.
I wonder if the cast is to a string then we could issue a helpful exception message indicating which SQL functions the user should use.
There was a problem hiding this comment.
Even though this is a good idea and helpful for the user. Implementing it is a bit messy. Still I kept the changes for you to review. Since it adds lots of nose in the method. But let me know what you think. See getUnsupportedCastHint and CastInputTypeStrategy.
| final CodeWriter writer = new CastRuleUtils.CodeWriter(); | ||
| switch (targetLogicalType.getTypeRoot()) { | ||
| case BOOLEAN: | ||
| writer.assignStmt(returnVariable, methodCall(inputTerm, "getBoolean")); |
There was a problem hiding this comment.
Do variants support nullable and non nullable concepts - should a cast of of boolean when the value is null work?
There was a problem hiding this comment.
As far as I can see in the implementation of VARIANT type the get methods (here getBoolean) return a boolean and not an nullable Boolean.
There was a problem hiding this comment.
Whereas NULL type is supported in VARIANT type. I have added logic for this and more tests to cover NULL casts and nullability.
| Arguments.of(new VariantType(), new DateType(), false, true), | ||
| Arguments.of(new VariantType(), new TimestampType(), false, true), | ||
| Arguments.of(new VariantType(), new LocalZonedTimestampType(), false, true), | ||
| Arguments.of( |
There was a problem hiding this comment.
is interval intentionally skipped?
There was a problem hiding this comment.
Variant.Type has no interval kind. Please refer to: https://cwiki.apache.org/confluence/spaces/FLINK/pages/349637099/FLIP-521+Integrating+Variant+Type+into+Flink+Enabling+Efficient+Semi-Structured+Data+Processing#FLIP521:IntegratingVariantTypeintoFlink:EnablingEfficientSemiStructuredDataProcessing-Casting
| .testResult( | ||
| call("PARSE_JSON", "42").cast(BIGINT()), | ||
| "CAST(PARSE_JSON('42') AS BIGINT)", | ||
| 42L, | ||
| BIGINT().notNull()) | ||
| .testResult( |
There was a problem hiding this comment.
dummy question: what will happen with Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY while transforming to VARIANT and vice versa?
There was a problem hiding this comment.
I got an exception on the Double.NEGATIVE_INFINITY
Caused by: org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonParseException: Non-standard token '-Infinity': enable `JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS` to allow
at [...]
There was a problem hiding this comment.
I like it in every places it fails differently 🙈
…_STRING for VARIANT-to-string casts A variant that stores a JSON null now casts to SQL NULL instead of failing in the type-specific accessor, when the cast target is nullable (a nullable variant source, or TRY_CAST which forces the target nullable). A NOT NULL target still fails on a null-valued variant because the result cannot carry NULL; making VARIANT casts unconditionally nullable would require changes to Calcite return-type inference and is left out of scope. Casting a VARIANT to a character string now reports a helpful validation error from the Table API type inference that points to JSON_STRING. The SQL path keeps Calcite's generic cast error.
What is the purpose of the change
Add
CASTandTRY_CASTfromVARIANTto primitive scalar types (FLIP-521, sub-task FLINK-37925). Previously aVARIANTcould only be read as another variant or rendered to a JSON string, with no way to extract a typed scalar out of it.Brief change log
VariantToPrimitiveCastRule(registered inCastRuleProvider) covering BOOLEAN, all integer/floating/decimal numerics,BINARY/VARBINARY, DATE, TIMESTAMP and TIMESTAMP_LTZ.
CASTfails the job andTRY_CASTreturnsNULL.VARIANTsource branch inLogicalTypeCasts(explicit only).JSON_STRING),TIME(no counterpart in the variant type model), and constructed types ARRAY/ROW/STRUCTURED (tracked in FLINK-37926).Verifying this change
This change added tests and can be verified as follows:
CastRulesTest: variant → each supported scalar, covering success, type mismatch (CASTthrows), and null inputs.CastRuleProviderTest: the rule resolves for supported targets.LogicalTypeCastsTest: explicit-cast support matrix, including that TIME/STRING/ARRAY and all implicit casts are rejected.CastFunctionITCase: end-to-endCAST/TRY_CASToverPARSE_JSON(...).Does this pull request potentially affect one of the following parts:
@Public(Evolving): noDocumentation
data-types.md) and JavaDocsWas generative AI tooling used to co-author this PR?
Opus 4.8