-
Notifications
You must be signed in to change notification settings - Fork 343
refactor: replace hand-coded rollup of expression fallback reasons onto operators #5236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
andygrove
merged 5 commits into
apache:main
from
andygrove:central-fallback-reason-rollup
Aug 4, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
62c8419
refactor: replace hand-coded fallback-reason roll-up with a central t…
andygrove 4abdc98
fix: remove unused val left by the roll-up removal in CometIn
andygrove bad0b27
fix: lift fallback reasons off the rewritten tree in exprToProto
andygrove b7f58df
Merge apache/main into central-fallback-reason-rollup
andygrove 49a4591
test: cover the strict fallback check directly, document its ordering…
andygrove File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -382,7 +382,11 @@ case class CometExecRule(session: SparkSession) | |
| // when COMET_EXPLAIN_FALLBACK_LOG_ENABLED=true) even when the write is fully native. | ||
| op | ||
| case _ => | ||
| // The operator was not converted to a Comet plan. Possible reasons for this happening: | ||
| // The operator was not converted to a Comet plan and no serde handler claimed it, so | ||
| // Comet simply has no support for it. (Operators that do have a handler are reported | ||
| // by `reportUnexplainedFallback` inside `convertToComet`, which is also where the | ||
| // strict check lives - it would be wrong to demand a specific reason here, because | ||
| // nothing ever attempted this operator.) Possible reasons for reaching this point: | ||
| // 1. Comet does not support this operator. | ||
| // 2. The operator could not be supported based on query context and current | ||
| // configs. In this case, it should have already been tagged with fallback | ||
|
|
@@ -698,6 +702,23 @@ case class CometExecRule(session: SparkSession) | |
|
|
||
| /** Convert a Spark plan to a Comet plan using the specified serde handler */ | ||
| private def convertToComet(op: SparkPlan, handler: CometOperatorSerde[_]): Option[SparkPlan] = { | ||
| val converted = tryConvertToComet(op, handler) | ||
| if (converted.isEmpty) { | ||
| // Comet looked at this operator and declined it, so it stays in the Spark plan. Lift any | ||
| // reasons recorded on its expressions onto the operator itself - see | ||
| // `rollUpFallbackReasons` for why this is needed - and then make sure something was | ||
| // recorded. The order is required, not incidental: `reportUnexplainedFallback` inspects only | ||
| // the operator's own tag, so a reason still sitting on an expression would look like no | ||
| // reason at all and trip the strict check. | ||
| rollUpFallbackReasons(op) | ||
| reportUnexplainedFallback(op) | ||
| } | ||
| converted | ||
| } | ||
|
|
||
| private def tryConvertToComet( | ||
| op: SparkPlan, | ||
| handler: CometOperatorSerde[_]): Option[SparkPlan] = { | ||
| val serde = handler.asInstanceOf[CometOperatorSerde[SparkPlan]] | ||
| if (isOperatorEnabled(serde, op)) { | ||
| // For operators that require native children (like writes), check if all data-producing | ||
|
|
@@ -741,6 +762,69 @@ case class CometExecRule(session: SparkSession) | |
| None | ||
| } | ||
|
|
||
| /** | ||
| * Lift fallback reasons recorded on `op`'s expression trees onto `op` itself. | ||
| * | ||
| * Extended explain output only walks plan nodes (`ExtendedExplainInfo.sortup` follows | ||
| * `children` / `innerChildren`, never `expressions`), so a reason tagged on an expression is | ||
| * invisible unless something lifts it onto the enclosing operator. This mirrors what | ||
| * [[rollUpInfoMessages]] already does for the informational tags, and replaces the roll-up that | ||
| * used to be hand-written at every serde call site (see | ||
| * https://github.com/apache/datafusion-comet/issues/5230). | ||
| * | ||
| * Only child *expressions* are collected, not child operators: reasons on a child operator are | ||
| * already reachable by the explain traversal via `children`. | ||
| * | ||
| * Called only when `op` was left in the Spark plan, which scopes the roll-up to the operator | ||
| * that actually failed conversion. That matters because some expression instances | ||
| * (`AttributeReference`s, DPP subquery expressions) are shared across operators, so an unscoped | ||
| * roll-up could surface one expression's reason under several unrelated operators. | ||
| * | ||
| * [[reportUnexplainedFallback]] relies on this having run first; the two must not be separated. | ||
| */ | ||
| private def rollUpFallbackReasons(op: SparkPlan): Unit = { | ||
| val reasons = op.expressions | ||
| .flatMap(_.collect { case e: Expression => e }) | ||
| .flatMap(_.getTagValue(CometExplainInfo.FALLBACK_REASONS)) | ||
| .flatten | ||
| .toSet | ||
| if (reasons.nonEmpty) { | ||
| withFallbackReasons(op, reasons) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We roll up the expression fallback reasons to the operator once in the framework instead of hand-coding it for every single operator |
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Handle an operator that Comet declined without stating why. | ||
| * | ||
| * When every child is already native, Comet had a real opportunity to convert `op`, so the | ||
| * absence of any reason - on `op` or anywhere in its expression trees - means a serde returned | ||
| * `None` and forgot to record one. Under `COMET_STRICT_FALLBACK_REASONS` (enabled for Comet's | ||
| * own test suites) that is a hard failure; otherwise fall back to a generic message so users | ||
| * still see something. The generic message is what used to mask this whole class of bug, which | ||
| * is why the strict check exists. | ||
| * | ||
| * Must run *after* [[rollUpFallbackReasons]] for the same operator. The check reads only `op`'s | ||
| * own tag, because `hasFallbackReason` deliberately does not traverse expressions (it is a | ||
| * planning control signal, not explain output), so an expression-level reason that has not been | ||
| * lifted yet would be mistaken for no reason at all. [[convertToComet]] is the only production | ||
| * caller and keeps the two calls together. | ||
| * | ||
| * Package-visible so `CometExecRuleSuite` can drive the strict failure directly: no serde in | ||
| * the tree reaches this state, which is exactly what the check enforces, so the only way to | ||
| * test it is to construct the shape by hand. | ||
| */ | ||
| private[comet] def reportUnexplainedFallback(op: SparkPlan): Unit = { | ||
| if (op.children.forall(_.isInstanceOf[CometNativeExec]) && !hasFallbackReason(op)) { | ||
| if (CometConf.COMET_STRICT_FALLBACK_REASONS.get(op.conf)) { | ||
| throw new IllegalStateException( | ||
| s"Comet did not convert ${op.nodeName} but recorded no fallback reason on the " + | ||
| "operator or any of its expressions. Add a withFallbackReason call stating why " + | ||
| s"conversion failed. Operator:\n$op") | ||
| } | ||
| withFallbackReason(op, s"${op.nodeName} is not supported") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Lift informational (non-fallback) messages tagged on an operator and its expressions onto the | ||
| * converted Comet plan node so they appear in verbose extended explain output. Expression-level | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct to say that
reportUnexplainedFallbackrelies onrollUpFallbackReasonsto have rolled up the expression tags first? Should we add a comment to make sure a future change from separating them?