fix: skip codegen dispatcher null short-circuit when a foldable subtree can raise - #5623
Conversation
…ee can raise The single-ordinal branch of `canShortCircuitNulls` assumed one input ordinal leaves Spark nothing to evaluate ahead of that ordinal's null check. A foldable subtree between the root and the ordinal breaks that assumption: `ConstantFolding` leaves such a subtree in place when evaluating it throws and it sits inside a conditional branch, so the throwing expression survives into the physical plan. Require that no node other than a `Literal` is foldable.
Assert foldability on the throwing node itself rather than on a path that resolved to the string literal, where the check was vacuous. Trim the `noSurvivingFoldableSubtree` scaladoc to sibling length: the worked witness lives in the tests, and the rationale is already stated on `canShortCircuitNulls`.
sunchao
left a comment
There was a problem hiding this comment.
Reviewed ae5eb02ec73c326115b9bb73a34bbbc5ee5a6c78 against 1e10eedd6e0303adcac4573f44d5c07ffb0fbacd. No new P1/P2 findings. The guard preserves evaluation of surviving foldable subtrees before an input's null check while retaining the literal-only fast path. The regression covers conditional constant folding and verifies dispatcher use.
This was a source review, with no local test execution. Current CI has 63 successful checks, 9 skipped and 1 failed Spark 4.1 SQL core shard. I inspected the failed job log: SQLAppStatusListenerMemoryLeakSuite's no memory leak test fails because statusStore.listener.get.noLiveData() is false at SQLAppStatusListenerSuite.scala:1123. ScalaTest reports 12,851 tests passed and 1 failed. That failure remains unresolved, and I have not established whether it is related to this PR.
|
Merged. Thanks @sunchao |
Which issue does this PR close?
Closes #5608.
Rationale for this change
CometBatchKernelCodegen.canShortCircuitNullsallows the pre-ev.codenull short-circuit whenever the dispatched tree reads exactly one input ordinal, on the reasoning that a single ordinal leaves Spark nothing to evaluate ahead of that ordinal's own null check.That is not true. A literal-only subtree between the root and the ordinal can still raise.
ConstantFoldingnormally folds such a subtree away, but it deliberately leaves it in place when evaluating it throws and it sits inside a conditional branch (it tags the nodeFAILED_TO_EVALUATEand moves on), so the throwing expression survives into the physical plan. The kernel then writes NULL beforeev.coderuns, and an ANSI error Spark raises is silently swallowed:Spark raises
[DIVIDE_BY_ZERO]; Comet returned a row. Three ordinary things line up:ConstantFoldingrefuses to fold1L DIV 0Lunder theIfbranch,TernaryExpression.nullSafeCodeGenemitsSubstring'sposcode before it testslen's null, andUpper/Substring/Cast/IntegralDivideare all null-intolerant over a single ordinal.This is the residual hole in #5218: that fix added
rootChildrenAreLeavesfor the multi-ordinal case but left the single-ordinal branch unguarded.upperis just a convenient witness; any dispatched null-intolerant root with a throwing foldable subtree between it and its single input reproduces it.What changes are included in this PR?
Adds a
noSurvivingFoldableSubtreecondition tocanShortCircuitNulls: no node in the tree other than aLiteralmay be foldable. By the time the dispatcher sees the treeConstantFoldinghas already run, so a surviving foldable non-Literalnode is precisely one that threw during folding, which is exactly the dangerous case.Literals are exempt, so the existing fast paths are untouched: none ofupper(substring(s, 1, 2)),pmod(a, b),a + b,conv(a, b, c)ormake_timestamp(...)contains a foldable non-Literalnode. The scaladoc oncanShortCircuitNullsis updated to record the new condition.How are these changes tested?
CometCodegenSuite: a new end-to-end test asserting Comet raises the sameDIVIDE_BY_ZEROSpark does for the query above, and that the codegen dispatcher actually ran for it. Verified to fail onmain(cometErr.isDefined was false) and pass with the fix, on thespark-3.4,spark-3.5,spark-4.0andspark-4.1profiles.CometCodegenSourceSuite: two generated-source tests, one asserting the short-circuit is not emitted for a single-ordinal tree carrying a throwing foldable subtree, and a counterpart asserting it is still emitted when the only foldable nodes areLiterals, so the fix is not over-corrected.CometCodegenSuite,CometCodegenSourceSuite,CometCodegenFuzzSuiteandCometSpecializedGettersDispatchSuitepass (177 tests).The
length/bit_length/octet_lengthwitnesses listed in the issue are left out here because they only apply once #5607 lands.