twalthr commented on code in PR #28403:
URL: https://github.com/apache/flink/pull/28403#discussion_r3412663375


##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/optimize/program/FlinkChangelogModeInferenceProgram.scala:
##########
@@ -114,9 +114,24 @@ class FlinkChangelogModeInferenceProgram extends 
FlinkOptimizeProgram[StreamOpti
 
     // step4: sanity check and return non-empty root
     if (finalRoot.isEmpty) {
-      val plan = FlinkRelOptUtil.toString(root, withChangelogTraits = true)
-      throw new TableException(
-        "Can't generate a valid execution plan for the given query:\n" + plan)
+      val errorMessage =
+        if (containsUpdates(rootWithModifyKindSet)) {
+          // Point at the failing node and its inputs instead of dumping the 
whole plan.
+          val conflict = new 
StringBuilder(describeChangelog(rootWithModifyKindSet))
+          rootWithModifyKindSet.getInputs.foreach(
+            input => conflict.append("\n  +- 
").append(describeChangelog(input)))
+          "Can't generate a valid execution plan for the given query.\n\n" +
+            "There is a changelog mismatch between two operators. One produces 
an upsert " +
+            "changelog (UPDATE_AFTER without UPDATE_BEFORE). The other 
requires a retract " +
+            "changelog (UPDATE_BEFORE and UPDATE_AFTER), for example a sink 
without a primary " +

Review Comment:
   Just declaring a PRIMARY KEY won't always solve the problem. I would 
generalize the message to something like:
   ```
   In such cases, ensure that the sink is able to digest upserts where the 
PRIMARY KEY serves as the upsert key, or make the input produce UPDATE_BEFORE.
   ```



##########
flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/optimize/program/FlinkChangelogModeInferenceProgram.scala:
##########
@@ -1604,6 +1619,21 @@ class FlinkChangelogModeInferenceProgram extends 
FlinkOptimizeProgram[StreamOpti
     modifyKindSetTrait.modifyKindSet
   }
 
+  /** Whether the node or any node in its input subtree produces UPDATE 
changes. */
+  private def containsUpdates(rel: RelNode): Boolean =
+    getModifyKindSet(rel).contains(ModifyKind.UPDATE) ||
+      rel.getInputs.exists(input => containsUpdates(input))
+
+  /** Renders a node's type and changelog mode, for example 
"Sink(changelogMode=[NONE])". */

Review Comment:
   > Sink(changelogMode=[NONE])
   
   I always found this `NONE` very confusing. For this error message, could we 
instead print the expected mode by the sink?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to