[FLINK-40151][runtime] Support missing YAML Transform predicates and nullable BOOLEAN logical evaluation#4474
[FLINK-40151][runtime] Support missing YAML Transform predicates and nullable BOOLEAN logical evaluation#4474haruki-830 wants to merge 7 commits into
Conversation
|
@lvyanquan Could u PTAL when u have time? Thanks! |
|
Thanks for the work here. The changes split into three buckets with very different compatibility impact. I'd suggest applying buckets 1–2 unconditionally and gating bucket 3 behind an explicit option. 1. New operators (currently unparseable) — keep always-on SQL: These fail validation on the current release, so no existing pipeline can depend on them. Safe to enable unconditionally. 2. Crash → 3-valued logic, non-NULL behavior unchanged — keep always-on SQL: These previously threw an NPE on NULL; results for non-NULL operands are identical. Pure crash fix, safe to enable unconditionally. 3. Silent result changes — must be gated behind an explicit option SQL:
This is a backward-incompatible change to user-facing semantics, and the silent nature makes it dangerous: after the upgrade the YAML is unchanged, the job stays healthy, and no error or warning is emitted, yet the output data changes. For a CDC sync, altered filter results change which Recommendation Introduce Also, to ease review, consider splitting this so buckets 1–2 and bucket 3 land as separate commits (or separate PRs) — the crash/parse fixes can then be merged quickly, while the semantic change is reviewed on its own. |
Thanks for the detailed review and the compatibility analysis. I agree with the split. I will separate this work into two PRs:
This should allow the parse/crash fixes to move forward independently, while keeping the silent result-changing semantics isolated and explicitly gated. |
…nullable BOOLEAN logical evaluation
| throw new ParseException("Unrecognized expression: " + sqlBasicCall.toString()); | ||
| } | ||
| Java.Rvalue rightOperandSupplier = | ||
| new Java.AmbiguousName(Location.NOWHERE, new String[] {"() -> " + atoms[1]}); |
There was a problem hiding this comment.
Could we add an end-to-end compilation test to JaninoCompilerTest?
The existing tests only verify the generated expression string or invoke
LogicalFunctions directly. They do not compile the generated expression with
Janino, so they cannot detect that Janino 3.1.10 does not support lambda
expressions.
There was a problem hiding this comment.
Addressed in the latest commit.
|
Please update both the English and Chinese transform documentation to reflect these changes |
|
Also cc’ing @yuxiqian. |
| | value IS NULL | null == value | 如果 value 为 NULL,返回 TRUE。 | | ||
| | value IS NOT NULL | null != value | 如果 value 不为 NULL,返回 TRUE。 | | ||
| | value IS NULL | isNull(value) | 如果 value 为 NULL,返回 TRUE。 | | ||
| | value IS NOT NULL | isNotNull(value) | 如果 value 不为 NULL,返回 TRUE。 | |
There was a problem hiding this comment.
The following functions are not documented:
- IS DISTINCT FROM
- IS NOT DISTINCT FROM
- LIKE ... ESCAPE
- NOT LIKE ... ESCAPE
- SIMILAR TO
- NOT SIMILAR TO
- SIMILAR TO ... ESCAPE
- NOT SIMILAR TO ... ESCAPE
Moreover, the two forms of LIKE are handled differently:
- Two-argument LIKE: uses Java regular expression semantics.
- LIKE with ESCAPE: uses SQL % and _ wildcard semantics.
| new Java.AmbiguousName( | ||
| Location.NOWHERE, | ||
| new String[] { | ||
| "new java.util.function.Supplier<Boolean>() { public Boolean get() { return " |
There was a problem hiding this comment.
A possible optimization is to generate different code based on operand nullability:
- If both operands are non-null, use native &&/||.
- If only the left operand is non-null, use a conditional expression:
- AND: left ? right : Boolean.FALSE
- OR: left ? Boolean.TRUE : right - If the left operand is nullable, keep the current Supplier fallback to preserve three-valued logic and short-circuit evaluation.
This avoids unnecessary per-record Supplier allocations in common non-null cases.
There was a problem hiding this comment.
We may add a test spec like true OR 1/0, false AND 1/0 to ensure the short-circuit logic is effective.
There was a problem hiding this comment.
Added them in the latest commit. Thanks!
| DataChangeEvent{tableId=foo.bar.baz, before=[1, false, true, true, false], after=[-1, true, false, true, false], op=UPDATE, meta=()} | ||
| DataChangeEvent{tableId=foo.bar.baz, before=[-1, true, false, true, false], after=[], op=DELETE, meta=()} | ||
| DataChangeEvent{tableId=foo.bar.baz, before=[], after=[0, true, false, false, true], op=INSERT, meta=()} | ||
| DataChangeEvent{tableId=foo.bar.baz, before=[0, true, false, false, true], after=[], op=DELETE, meta=()} |
There was a problem hiding this comment.
Lacks end-to-end tests for:
- NOT LIKE ... ESCAPE
- SIMILAR TO ... ESCAPE
- NOT SIMILAR TO ... ESCAPE
- NULL inputs for these expressions
|
@lvyanquan Thanks for the detailed feedback. I split the follow-up work into separate JIRA issues: For this PR, I kept the scope limited to the always-on changes that do not introduce silent behavior changes. The backward-incompatible semantic alignment work, such as comparison / IN / BETWEEN / two-argument LIKE behavior, will be handled under the explicit semantics option in a follow-up PR. For nullable AND / OR, I applied the localized optimization suggested here: native |
yuxiqian
left a comment
There was a problem hiding this comment.
Thanks Haruki for the contribution, just left some minor comments.
| private static boolean isExpressionNullable(Context context, SqlNode sqlNode) { | ||
| if (sqlNode instanceof SqlIdentifier) { | ||
| return isIdentifierNullable(context, (SqlIdentifier) sqlNode); | ||
| } | ||
| if (sqlNode instanceof SqlLiteral) { | ||
| return ((SqlLiteral) sqlNode).getValue() == null; | ||
| } | ||
| if (sqlNode instanceof SqlBasicCall) { | ||
| return isBasicCallNullable(context, (SqlBasicCall) sqlNode); | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Instead of deducing isIdentifierNullable at the Janino compiler level, can we check the corresponding RelNode nullability during Calcite validation?
Current implementation is OK though, as the compilation happens just once.
There was a problem hiding this comment.
Thanks for the suggestion. I agree that deriving nullability from Calcite/RelNode would be cleaner.
Since the current check only runs once during expression compilation and is conservative for unknown expressions, I kept it local in this PR to avoid expanding the scope. I think it would be better to revisit this in the follow-up PR for statement-level Transform expression codegen.
Summary
This commit improves YAML Transform predicate support and nullable BOOLEAN logical evaluation. It adds support for several previously unsupported SQL predicates, and fixes runtime failures when logical expressions evaluate nullable BOOLEAN operands.
Key Changes
Additional Predicate Support
IS UNKNOWN/IS NOT UNKNOWN.IS DISTINCT FROM/IS NOT DISTINCT FROM.LIKE ... ESCAPE.SIMILAR TO/NOT SIMILAR TO.Nullable BOOLEAN Logical Evaluation
AND,OR, andNOTwith nullable BOOLEAN operands.IS TRUE/IS NOT TRUE.IS FALSE/IS NOT FALSE.UNKNOWNdoes not cause Java unboxing failures.Test Coverage
LIKE,LIKE ... ESCAPE, andSIMILAR TO.JIRA Reference
https://issues.apache.org/jira/browse/FLINK-40151