[FLINK-40528][table] Make codegen tolerant to partial deletes - #29060
[FLINK-40528][table] Make codegen tolerant to partial deletes#29060snuyanzin wants to merge 3 commits into
Conversation
d4a27c9 to
9b5d43c
Compare
9b5d43c to
589bd9f
Compare
gustavodemorais
left a comment
There was a problem hiding this comment.
Thanks for looking into this and the fix, Sergey!
I like the fix. As I expected though, we still have more gaps:
generatePrimitiveArrayUpdateCode(ARRAY[...]/MAP[...]literals) still gates onelementType.isNullableonly:
SELECT id, ROW(id, ARRAY[v, 99]) FROM source_t
-D[1, +I[1, [-1, 99]]] <- should be null, writes -1
generateMap hits the same function for fixed-length primitive keys/values.
JsonGenerateUtils.createNullableNodeTermhas the same gate, also untouched
SELECT id, JSON_OBJECT('r' VALUE ROW(v)) FROM source_t
-D[1, {"r":{"EXPR$0":0}}] <- should be null, writes 0
- And separately, we also need to fix the ConstraintEnforcer. FLINK-40477 (
NOT_NULL_ERROR_DELETE_BY_KEY- bare scalar passthrough, no composite at all) is a different bug in a different module (ConstraintEnforcerExecutor). Simple projections without Row fail still because we have partial deletes.
SELECT id, v FROM source_t -- sink column v INT NOT NULL
EnforcerException: Column 'v' is NOT NULL, however, a null value is being written into it.
In general, the partial deletes optimization is broken. I think these three above are doable so I'm +1 into fix forward: we fix them instead of forcing ChangelogNormalize back into the pipelines. I think 1. fits into this PR and 2. maybe, up to you. For 3, I think it makes sense to do it in another PR.
| if (element.literal) { | ||
| "" | ||
| } else if (tpe.isNullable) { | ||
| } else if (tpe.isNullable || element.nullTerm != NEVER_NULL) { |
There was a problem hiding this comment.
Can we extract this as a small helper to slightly improve readability?
/** True only when codegen proved this statically; a dynamic nullTerm may still be null at runtime. */
def isProvenNonNull: Boolean = nullTerm == NEVER_NULL
...
} else if (tpe.isNullable || !element.isProvenNonNull) {
bdfa85d to
749d1c4
Compare
gustavodemorais
left a comment
There was a problem hiding this comment.
Thanks for the improvements, @snuyanzin! We caught many more cases. Now added only some nits
| .build(); | ||
|
|
||
| /** | ||
| * A LEFT JOIN whose probe (left) side produces a delete-by-key tombstone carrying null for a |
There was a problem hiding this comment.
test is inner join, which one did you want?
| * A LEFT JOIN whose probe (left) side produces a delete-by-key tombstone carrying null for a | |
| * A INNER JOIN whose probe (left) side produces a delete-by-key tombstone carrying null for a |
| * is NOT NULL and the target is a primitive Java type, so it reads the primitive default (0) | ||
| * instead of producing null. | ||
| */ | ||
| public static final TableTestProgram INSERT_SELECT_DELETE_BY_KEY_WITH_NOT_NULL_CAST = |
There was a problem hiding this comment.
nit: these are a lot of tests. I'm ok with it if they're really necessary. Maybe we can reduce the number of tests without losing coverage?
For example, I think these two were passing before the change as well. Is there value in keeping them?
INSERT_SELECT_DELETE_BY_KEY_WITH_NOT_NULL_CAST and INSERT_SELECT_DELETE_BY_KEY_WITH_NESTED_NOT_NULL_STRING
There was a problem hiding this comment.
INSERT_SELECT_DELETE_BY_KEY_WITH_NOT_NULL_CAST
this is required
it highlights another finding flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/CodeGenUtils.scala
fhueske
left a comment
There was a problem hiding this comment.
Thanks for the fix @snuyanzin.
The changes look good to me.
Would be nice if you could fix the incorrect comment that Gustavo pointed out.
Thanks, Fabian
| def literal: Boolean = literalValue.isDefined | ||
|
|
||
| /** Whether this expression is statically proven never to be null at runtime. */ | ||
| def isProvenNotNull: Boolean = nullTerm == GeneratedExpression.NEVER_NULL |
There was a problem hiding this comment.
Which expressions are proven not null at runtime if we do not trust the types?
It would only be literals and expressions on literals, no?
What is the purpose of the change
The PR makes codegen tolerant to partial deletes
Brief change log
codegen
Verifying this change
tests
Does this pull request potentially affect one of the following parts:
@Public(Evolving): (no)Documentation
Was generative AI tooling used to co-author this PR?
Generated-by: [Tool Name and Version]