Skip to content

[FLINK-40936][table] Reject VARIANT elements and keys in functions and operators that compare them - #29416

Merged
raminqaf merged 1 commit into
apache:masterfrom
raminqaf:FLINK-40936-variant-function-equality
Oct 26, 2026
Merged

raminqaf merged 1 commit into
apache:masterfrom
raminqaf:FLINK-40936-variant-function-equality

Conversation

@raminqaf

@raminqaf raminqaf commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What is the purpose of the change

Flink compares VARIANT values by their binary encoding, but one value has many encodings. So functions and operators that compare array elements, map keys or collected values treat equal VARIANT values as different and return wrong results, without an error. #29411 rejects VARIANT in GROUP BY, joins and comparisons. This PR does the same for these functions and operators.

-- t(s STRING) has three rows: {"a":1,"b":2}, {"b":2,"a":1}, {"a":1,"c":3}
SELECT ARRAY_CONTAINS(ARRAY[PARSE_JSON(s)['a']], PARSE_JSON('1')) FROM t;
Query Before After
the ARRAY_CONTAINS above false An array element of type VARIANT cannot be used in ARRAY_CONTAINS, because the type has no equality. Cast the value to a comparable type first.

The same validator rejects VARIANT in GROUP BY and joins, so the message has the same form.

Brief change log

  • ComparableTypeValidator checks the array elements and map keys of ARRAY_CONTAINS, ARRAY_DISTINCT, ARRAY_POSITION, ARRAY_REMOVE, ARRAY_UNION, ARRAY_EXCEPT, ARRAY_INTERSECT, MAP_CONTAINS_KEY and MAP_UNION. It also checks the map key of an item access m[k] and the argument of COLLECT. An index into an array and a field access on a VARIANT compare nothing and stay allowed. The query fails during planning.
  • The validator names what it checks in sets: the functions, the operators that compare elements, and the comparison operators that need equality or order.
  • LogicalTypeChecks#areComparable rejects every type that is not comparable, see LogicalTypeChecks#isComparableType. This also covers a structured type with a VARIANT attribute. GREATEST, LEAST, MAP_FROM_ENTRIES and Table API comparisons reject VARIANT through it.
  • The VARIANT section of the data types page gives a few examples of where a query compares values, instead of a full list that would soon be out of date.
  • KeyTypeValidator from [FLINK-40910][table] Reject VARIANT where a query needs equality or ordering #29411 is now ComparableTypeValidator, and isComparableKeyType is now isComparableType. The planner also uses key for upsert, unique and primary keys, and the validator checks every comparison, not only keys.

An earlier version declared the check in the input type strategy of each function. That needed a new argument strategy and a flag in three collection strategies, about 160 lines of main code, for a check that a future VARIANT equality may remove. The validator does it in a few lines. The trade-off is a function list kept apart from the function definitions. A new function that compares elements or keys has to be added to it.

Ten functions build their equality check again during code generation: ARRAY_CONTAINS, ARRAY_DISTINCT, ARRAY_POSITION, ARRAY_REMOVE, ARRAY_UNION, ARRAY_EXCEPT, ARRAY_INTERSECT, MAP_CONTAINS_KEY, MAP_UNION and MAP_FROM_ENTRIES. That check goes through LogicalTypeChecks#areComparable, not through the validator, which only runs when a query is planned. So a job restored from a compiled plan that calls one of them on VARIANT elements or keys now fails when it is executed, before the job is submitted. Such a plan returned wrong results before. A user SpecializedFunction that builds an = evaluator on VARIANT values now fails too, in new and in restored plans. Other restored plans are not affected. A restored plan with m[k] or COLLECT on VARIANT still runs and matches by bytes, because the validator never sees it. No test restores such a plan. ComparableInputTypeStrategyTest covers the check that makes it fail.

Not in this PR: MAP[...] and MAP_FROM_ARRAYS still build maps with VARIANT keys, and such a map can hold the same key twice. The data types page already says so. A follow-up issue will track them, together with MAP<VARIANT, ...> and MULTISET<VARIANT> as types.

Verifying this change

This change added tests and can be verified as follows:

  • ComparableTypeValidatorTest rejects a call of each of the nine functions with VARIANT elements or keys, m[k] with a VARIANT key, and COLLECT on VARIANT values in an aggregate and in an OVER window. It accepts MAP_CONTAINS_KEY on a map with VARIANT values, an index into an ARRAY<VARIANT> and a field access on a VARIANT.
  • ComparableInputTypeStrategyTest covers VARIANT in areComparable, which GREATEST, LEAST, MAP_FROM_ENTRIES and restored plans go through. LogicalTypeChecksTest covers VARIANT nested in other types, including a structured type with a VARIANT attribute.

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. Calls of the functions and operators above with VARIANT elements or keys now fail, which needs a release note.
  • 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: yes. A restored compiled plan that calls one of the ten functions above on VARIANT elements or keys fails when it is executed.
  • The S3 file system connector: no

Documentation

  • Does this pull request introduce a new feature? no
  • If yes, how is the feature documented? not applicable

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code 2.1.310 (Claude Opus 5.5)

@flinkbot

flinkbot commented Oct 7, 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

@raminqaf
raminqaf force-pushed the FLINK-40936-variant-function-equality branch 4 times, most recently from 784bb5a to ba1fc84 Compare October 8, 2026 15:04
@raminqaf
raminqaf force-pushed the FLINK-40936-variant-function-equality branch 2 times, most recently from 7b3c4f1 to 6ab4620 Compare October 23, 2026 14:30

@VasShabu VasShabu left a comment

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.

Hi Ramin

First review so please let me know if my logic is flawed

Noticed that your comment key type of a MAP are the first child however if this changes in the future so that "key type of a MAP are the second child" some builtin functions may not give the intended output like Map_Contains_Key.

@github-actions github-actions Bot added the community-reviewed PR has been reviewed by the community. label Oct 23, 2026
@raminqaf
raminqaf force-pushed the FLINK-40936-variant-function-equality branch 2 times, most recently from 7fd3775 to 2491ce9 Compare October 25, 2026 19:45
@raminqaf raminqaf changed the title [FLINK-40936][table] Reject VARIANT elements and keys in functions that compare them [FLINK-40936][table] Reject VARIANT elements and keys in functions and operators that compare them Oct 25, 2026
@raminqaf
raminqaf force-pushed the FLINK-40936-variant-function-equality branch from 2491ce9 to 1c96af0 Compare October 26, 2026 07:05

@twalthr twalthr left a comment

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.

LGTM, I just had one last doc simplification comment.

…d operators that compare them

Flink compares VARIANT values by their binary encoding, but one value has many encodings. So functions and operators that compare array elements, map keys or collected values treated equal VARIANT values as different and silently returned wrong results.

ComparableTypeValidator now checks the array elements and map keys of ARRAY_CONTAINS, ARRAY_DISTINCT, ARRAY_POSITION, ARRAY_REMOVE, ARRAY_UNION, ARRAY_EXCEPT, ARRAY_INTERSECT, MAP_CONTAINS_KEY and MAP_UNION, the map key of an item access m[k], and the argument of COLLECT, also in an OVER window. An index into an array and a field access on a VARIANT compare nothing and stay allowed. The validator names what it checks in sets: the functions, the operators that compare elements, and the comparison operators that need equality or order. The comparison operator sets hold the same kinds that the validator matched before.

The check lives in the validator and not in the input type strategy of each function. That would need a new argument strategy and a flag in three collection strategies, for a check that a future VARIANT equality may remove.

LogicalTypeChecks.areComparable rejects every type that is not comparable, see LogicalTypeChecks.isComparableType. This also covers a structured type with a VARIANT attribute. GREATEST, LEAST, MAP_FROM_ENTRIES and Table API comparisons reject VARIANT through it during type inference.

Ten functions build their equality check again during code generation: ARRAY_CONTAINS, ARRAY_DISTINCT, ARRAY_POSITION, ARRAY_REMOVE, ARRAY_UNION, ARRAY_EXCEPT, ARRAY_INTERSECT, MAP_CONTAINS_KEY, MAP_UNION and MAP_FROM_ENTRIES. That check goes through LogicalTypeChecks.areComparable, not through the validator, which only runs when a query is planned. So a job restored from a compiled plan that calls one of them on VARIANT elements or keys now fails when it is executed, before the job is submitted. Such a plan returned wrong results before. A user SpecializedFunction that builds an = evaluator on VARIANT values now fails too, in new and in restored plans.

The VARIANT section of the data types page gives a few examples of where a query compares values, instead of a full list that would soon be out of date.

KeyTypeValidator is now ComparableTypeValidator. The planner also uses key for upsert, unique and primary keys, and the validator rejects every comparison of a type that is not comparable, for equality or for order. LogicalTypeChecks.isComparableKeyType is now isComparableType, and the Javadocs say which comparisons need equality and which need order.
@raminqaf
raminqaf force-pushed the FLINK-40936-variant-function-equality branch from 1c96af0 to 25e6104 Compare October 26, 2026 09:15
@raminqaf
raminqaf merged commit 5c9455b into apache:master Oct 26, 2026
1 check passed
@raminqaf
raminqaf deleted the FLINK-40936-variant-function-equality branch October 26, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-reviewed PR has been reviewed by the community.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants