[SPARK-58966][SQL] Resolve SQL variables in UPDATE and MERGE INTO conditions - #58244
[SPARK-58966][SQL] Resolve SQL variables in UPDATE and MERGE INTO conditions#58244joelrobin18 wants to merge 1 commit into
Conversation
…ditions Signed-off-by: joelrobin18 <joelrobin1818@gmail.com>
cloud-fan
left a comment
There was a problem hiding this comment.
0 blocking, 1 non-blocking, 0 nits.
The shared fallback approach is sound, but the MERGE condition enumeration is incomplete.
Design / architecture (1)
- Non-blocking: sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala:1899: Apply last-resort variable resolution to both
WHEN NOT MATCHEDinsert-condition branches and cover the form with a focused test. -- see inline
Verification
I traced every MergeAction condition through ResolveReferences and the downstream merge rewrite. Matched and not-matched-by-source actions now opt into last-resort resolution, while InsertAction and InsertStarAction conditions still use the column-only resolver. Those insert conditions are consumed as normal MERGE predicates, and an unresolved variable reaches CheckAnalysis as UNRESOLVED_COLUMN.
PR metadata suggestions
- Correct the condition-coverage claim, preferably by including and testing
WHEN NOT MATCHED AND <variable>; both insert-condition branches still omit last-resort variable resolution.
|
|
||
| val resolvedMergeCondition = resolveExpressionByPlanChildren(m.mergeCondition, m) | ||
| val resolvedMergeCondition = | ||
| resolveExpressionByPlanChildren(m.mergeCondition, m, includeLastResort = true) |
There was a problem hiding this comment.
Non-blocking:
WHEN NOT MATCHED conditions need the same last-resort resolution. The InsertAction and InsertStarAction branches at lines 1852 and 1860 still call resolveExpressionByPlanOutput without includeLastResort = true, so a declared variable remains unresolved even though the other MERGE condition forms now work. Please update both branches and add a focused WHEN NOT MATCHED AND <variable> test.
What changes were proposed in this pull request?
SQL variables declared with
DECLAREcannot be referenced in the conditions of anUPDATEorMERGE INTOstatement. This passesincludeLastResort = trueat the sevencondition-resolution sites that make up those clauses, matching what SPARK-57260 did for
OverwriteByExpression.deleteExpr.Variable resolution only runs from
resolveColsLastResort, which is reached whenresolveExpressionByPlanOutput/resolveExpressionByPlanChildrenare called withincludeLastResort = true. Both default the flag tofalse. Plans with no dedicatedresolution rule fall through to the generic operator case in
ResolveReferences, whichdoes pass the flag -- which is why
DELETE ... WHEREalready works.UPDATEandMERGE INTOeach have a dedicated rule that omitted it:UPDATEcondition (ResolveReferencesInUpdate)MERGEONconditionMERGEWHEN MATCHEDDELETE/UPDATEconditionsMERGEUPDATE *conditionMERGEWHEN NOT MATCHED BY SOURCEDELETE/UPDATEconditionsAssignment values (
SET col = var,INSERT VALUES (var)) are affected by the same rootcause, but they resolve through
resolveExprInAssignment, which setsincludeLastResort = falseexplicitly rather than by default. That is left unchangedhere pending a decision on whether the explicit
falsewas deliberate, so this PR isscoped to conditions only.
Why are the changes needed?
Variables resolve in
SELECT,INSERT(includingREPLACE WHERE) andDELETE, but inno condition of
UPDATEorMERGE INTO, which fails analysis with:The message itself offers "A column, variable, or function parameter", so the analyzer
reports that variable resolution was attempted. Nothing in
error-conditions.json, thetests, or the docs records a restriction here. Where Spark does intentionally block
variables (SPARK-57360, generated columns) it uses an explicit validation and a dedicated
error class, so the inconsistency looks like an oversight rather than a deliberate
limitation.
This affects both session variables and SQL scripting local variables.
Does this PR introduce any user-facing change?
Yes, a bug fix.
Before, against a table using the built-in DSv2 row-level operation framework:
After, the statement analyzes and executes, resolving
target_depto the declaredvariable. Statements that previously succeeded are unaffected: the flag only enables a
last-resort resolution step that runs after normal column resolution fails, so column
references continue to take precedence over same-named variables.
How was this patch tested?
Four new tests, plus a shared
withSessionVariablehelper inRowLevelOperationSuiteBase:UpdateTableSuiteBase-- a session variable and a SQL scripting local variable in anUPDATEcondition.MergeIntoTableSuiteBase-- a variable in theMERGEONcondition, and in theWHEN MATCHED/WHEN NOT MATCHED BY SOURCEconditions.All four were verified to fail on master without the fix (each raising
UNRESOLVED_COLUMN) and to pass with it.Suites run:
GroupBasedUpdateTableSuite+GroupBasedMergeIntoTableSuite(137 tests)and
DeltaBasedUpdateTableSuite+DeltaBasedMergeIntoTableSuite(148 tests), allpassing.
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code