Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,13 @@ class StreamPhysicalCalc(
}

// Every column in every candidate is, by construction of
// FlinkRelMdUniqueKeys.getProjectUniqueKeys, guaranteed to be a trivial
// pass-through of an input field - never a risky expression to evaluate.
val keyIndices = outputUpsertKeys.flatMap(bitSet => bitSet.map(_.intValue())).toSet.toArray
// FlinkRelMdUniqueKeys.getProjectUniqueKeys, guaranteed to be a pass-through
// of an input field or an injective expression.
val keyIndices = outputUpsertKeys
.flatMap(bitSet => bitSet.map(_.intValue()))
.toSet
.toArray
.sorted
if (keyIndices.nonEmpty) {
keyIndices
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,7 @@ class FlinkChangelogModeInferenceProgram extends FlinkOptimizeProgram[StreamOpti
case calc: StreamPhysicalCalc =>
if (
requiredTrait == DeleteKindTrait.DELETE_BY_KEY &&
isNonUpsertKeyCondition(calc)
(isNonUpsertKeyCondition(calc) || !hasOutputUpsertKey(calc))
) {
None
} else {
Expand Down Expand Up @@ -1697,6 +1697,19 @@ class FlinkChangelogModeInferenceProgram extends FlinkOptimizeProgram[StreamOpti
}
}

/**
* Whether this calc's own output still has an upsert key after its projection. A DELETE_BY_KEY
* tombstone passed through this calc must still carry a key in its output, otherwise nothing
* downstream would know what to delete.
*
* This method is an extra safety net, in case downstream consumers don't require an upsert key.
*/
private def hasOutputUpsertKey(calc: StreamPhysicalCalcBase): Boolean = {
val fmq = FlinkRelMetadataQuery.reuseOrCreate(calc.getCluster.getMetadataQuery)
val upsertKeys = fmq.getUpsertKeys(calc)
upsertKeys != null && upsertKeys.exists(!_.isEmpty)
}

private def isNonUpsertKeyCondition(calc: StreamPhysicalCalcBase): Boolean = {
val program = calc.getProgram
if (program.getCondition == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ public final class DeletesByKeyPrograms {
TableTestProgram.of(
"delete-by-key-delete-by-key-with-expression",
"NOT NULL constraints have no effect. The row constructor expression"
+ "is not evaluated for partial deletion.")
+ " is not evaluated for partial deletion.")
.setupTableSource(
SourceTestStep.newBuilder("source_t")
.addSchema(
Expand Down