Repository navigation
[FLINK-40910][table] Reject VARIANT where a query needs equality or ordering - #29411
Conversation
fbf086c to
edeab20
Compare
|
can we have a query similar to #29285 (comment) (with variants) for instance in this test SortMergeJoinTest |
| * Checks whether values of a (possibly nested) logical type can be used as grouping, join, | ||
| * partition or sort keys and as operands of comparison operators. | ||
| * | ||
| * <p>VARIANT is not a comparable key type, because one VARIANT value has many valid binary | ||
| * encodings. |
There was a problem hiding this comment.
i'm not sure it is true
how about MAP, RAW, STRUCTURED, MULTISET?
There was a problem hiding this comment.
Good point, VARIANT is not the only type with this problem. I checked on this branch. MAP['a', 1, 'b', 2] = MAP['b', 2, 'a', 1] returns true, so comparing two maps is correct. Grouping is not: streaming GROUP BY m returns two groups for these maps, and batch rejects MAP keys during code generation.
MULTISET is stored as a map, so it should behave the same.
Structured keys compare attribute by attribute, so they only break with a VARIANT or MAP attribute, and a VARIANT attribute is already rejected.
RAW keys compare the serialized bytes.
The MAP and MULTISET grouping bug exists without VARIANT, and Spark fixes it by sorting map entries instead of rejecting them.
So I'd keep it out of this PR and open a JIRA for it. I'll reword the Javadoc so it states what every key needs, without claiming the method decides every key type.
| * <p>VARIANT is not a comparable key type, because one VARIANT value has many valid binary | ||
| * encodings. | ||
| */ | ||
| public static boolean isComparableKeyType(LogicalType logicalType) { |
There was a problem hiding this comment.
why do we need one more comparable method instead of reuse?
There was a problem hiding this comment.
areComparable answers a different question: whether a function can compare two values. It says MAP is comparable and even orderable, although batch can't sort MAP keys. It says a structured type with the default EQUALS comparison isn't orderable, although ORDER BY on such a type works today in batch. So using it for keys would reject queries that work today and still let MAP keys through. #29416 goes the other way: areComparable calls this method, so VARIANT has one rule in both places.
| "SELECT PARSE_JSON(l.s)['x'], PARSE_JSON(r.s)['y'] " | ||
| + "FROM l JOIN r ON PARSE_JSON(l.s)['id'] = PARSE_JSON(r.s)['id']", |
There was a problem hiding this comment.
can we also have tests with IS [NOT ] DISTINCT FROM?
Thanks, good check. I ran a I will also add an test to cover this |
edeab20 to
37ddb7a
Compare
…rdering Flink compares VARIANT values by their binary encoding, but one value has many encodings. Object keys can be stored in any order, a field read from an object keeps the key dictionary of its parent, and a number keeps its storage width. So GROUP BY, DISTINCT, joins, set operations, PARTITION BY and = treated equal values as different and silently returned wrong results in streaming. Batch rejected most of these late, during code generation. A batch nested loop join still compared VARIANT keys by their bytes and returned wrong rows. LogicalTypeChecks.isComparableKeyType checks that a type contains no VARIANT, which every grouping, join, partition and sort key and every operand of a comparison operator needs. This includes a VARIANT nested in a ROW, ARRAY, MAP or structured type. A new KeyTypeValidator checks the logical plan before optimization, so it covers SQL and the Table API. It rejects such types in grouping keys, DISTINCT aggregates, set operations other than UNION ALL, ORDER BY, the PARTITION BY and ORDER BY of OVER windows, MATCH_RECOGNIZE and table arguments, comparison operators and IN subqueries. This includes DISTINCT aggregates in OVER windows and comparisons in watermark expressions. Joins are covered by the comparisons in their conditions. A job restored from a compiled plan skips KeyTypeValidator and keeps running. The VARIANT section of the data types page lists where a query cannot compare VARIANT values and shows the cast to use instead.
37ddb7a to
f793b4e
Compare
What is the purpose of the change
Flink compares VARIANT values by their binary encoding, but one value has many encodings. Object keys can be stored in any order, a field read from an object keeps the key dictionary of its parent, and a number keeps its storage width. So queries that group, deduplicate, join or compare VARIANT values return wrong results in streaming, without an error.
GROUP BYabove+I[1, 1]eachColumn 'a' of type VARIANT cannot be used as a grouping key, because the type has no equality. Cast the value to a comparable type first.... WHERE PARSE_JSON(s)['a'] = PARSE_JSON('1')An expression of type VARIANT cannot be used in a comparison with '=', because the type has no equality. Cast the value to a comparable type first.... GROUP BY CAST(PARSE_JSON(s)['a'] AS INT)Batch already rejected most of these queries late, during code generation, with
Type 'VARIANT' cannot be ordered, so it cannot be used as a key for sorting, grouping, or joining. A batch nested loop join still compared VARIANT keys by their bytes and returned wrong rows. Batch now gets the same error as streaming.This follows Spark and Databricks, which also reject VARIANT where equality or ordering is needed. Users cast to a concrete type first, as in the last row.
Brief change log
LogicalTypeChecks#isComparableKeyTypechecks that a type contains no VARIANT, which every grouping, join, partition and sort key and every operand of a comparison needs. This includes a VARIANT nested in a ROW, ARRAY, MAP or structured type.KeyTypeValidatorchecks the logical plan before optimization, so it covers SQL and the Table API. It rejects such types in grouping keys, DISTINCT aggregates, also inside OVER windows, set operations other than UNION ALL, ORDER BY, the PARTITION BY and ORDER BY of OVER windows, MATCH_RECOGNIZE and table arguments, comparison operators, also inside watermark expressions, and IN subqueries. Joins are covered through the comparisons in their conditions. A comparison with a NULL literal is allowed.The check runs on the logical plan rather than on exec nodes. It sees the keys the query asks for, and keys the optimizer adds later, for example during decorrelation, only match a value with copies of itself. A job restored from a compiled plan skips the check and keeps running.
Not in this PR:
ARRAY_CONTAINSandMAP_CONTAINS_KEY, have the same bug. FLINK-40936 covers them in a follow-up PR, because rejecting them also changes how restored compiled plans behave.Verifying this change
This change added tests and can be verified as follows:
KeyTypeValidatorTestruns rejected and allowed queries in streaming and batch mode. It covers grouping, DISTINCT aggregates, set operations, ORDER BY, OVER windows, window TVFs, MATCH_RECOGNIZE, table arguments, comparisons, IN lists and subqueries, joins and watermarks, plus Table API cases. It also runs VARIANT joins with each batch join strategy, SortMergeJoin, ShuffleHashJoin, BroadcastHashJoin and NestedLoopJoin.VariantSemanticTestturns the reproductions from the JIRA issue into failing-SQL checks, includingIS [NOT] DISTINCT FROM, and keeps the CAST workaround as a passing program.LogicalTypeChecksTestcoversisComparableKeyType.Does this pull request potentially affect one of the following parts:
@Public(Evolving): no. Queries that used VARIANT in these places now fail, which needs a release note.Documentation
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code 2.1.292 (Claude Opus 5.5)