Skip to content

[FLINK-37925][table] Support casting from VARIANT to primitive types#28733

Open
raminqaf wants to merge 5 commits into
apache:masterfrom
raminqaf:FLINK-37925
Open

[FLINK-37925][table] Support casting from VARIANT to primitive types#28733
raminqaf wants to merge 5 commits into
apache:masterfrom
raminqaf:FLINK-37925

Conversation

@raminqaf

Copy link
Copy Markdown
Contributor

What is the purpose of the change

Add CAST and TRY_CAST from VARIANT to primitive scalar types (FLIP-521, sub-task FLINK-37925). Previously a VARIANT could 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

  • Add VariantToPrimitiveCastRule (registered in CastRuleProvider) covering BOOLEAN, all integer/floating/decimal numerics,
    BINARY/VARBINARY, DATE, TIMESTAMP and TIMESTAMP_LTZ.
  • Numeric targets are lenient: any numeric variant converts following the regular numeric cast semantics. Non-numeric targets require the stored value to match the target kind.
  • On a type mismatch CAST fails the job and TRY_CAST returns NULL.
  • Permit the cast at planner validation time via a VARIANT source branch in LogicalTypeCasts (explicit only).
  • Out of scope: CHARACTER_STRING (already served by the JSON display rule and 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 (CAST throws), 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-end CAST/TRY_CAST over PARSE_JSON(...).

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): no
  • The public API, i.e., is any changed class annotated with @Public(Evolving): no
  • The serializers: no
  • The runtime per-record code paths (performance sensitive): no
  • Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper: no
  • The S3 file system connector: no

Documentation

  • Does this pull request introduce a new feature? yes
  • If yes, how is the feature documented? docs (VARIANT casting section in data-types.md) and JavaDocs

Was generative AI tooling used to co-author this PR?
  • Yes

Opus 4.8

raminqaf added 3 commits July 13, 2026 14:03
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.
@flinkbot

flinkbot commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands The @flinkbot bot supports the following commands:
  • @flinkbot run azure re-run the last Azure build

case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
return true;
default:
// TIME has no counterpart in the Variant type model. CHARACTER_STRING is handled by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do variants support nullable and non nullable concepts - should a cast of of boolean when the value is null work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is interval intentionally skipped?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +154 to +159
.testResult(
call("PARSE_JSON", "42").cast(BIGINT()),
"CAST(PARSE_JSON('42') AS BIGINT)",
42L,
BIGINT().notNull())
.testResult(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dummy question: what will happen with Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY while transforming to VARIANT and vice versa?

@raminqaf raminqaf Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 [...]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants