Skip to content

[SPARK-58001] [SQL] Improve UNSUPPORTED_EXPR_FOR_OPERATOR error message#57081

Open
mihailoale-db wants to merge 1 commit into
apache:masterfrom
mihailoale-db:re-12384-unsupported-expr-for-operator
Open

[SPARK-58001] [SQL] Improve UNSUPPORTED_EXPR_FOR_OPERATOR error message#57081
mihailoale-db wants to merge 1 commit into
apache:masterfrom
mihailoale-db:re-12384-unsupported-expr-for-operator

Conversation

@mihailoale-db

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

To improve message for UNSUPPORTED_EXPR_FOR_OPERATOR exception.

Why are the changes needed?

In order to improve error message.

Does this PR introduce any user-facing change?

Better error message.

How was this patch tested?

Testing change.

Was this patch authored or co-authored using generative AI tooling?

Yes.

@mihailoale-db

Copy link
Copy Markdown
Contributor Author

@MaxGekk @cloud-fan Could you PTAL at this PR when you find time? Thanks!

"message" : [
"A query operator contains one or more unsupported expressions.",
"Consider to rewrite it to avoid window functions, aggregate functions, and generator functions in the WHERE clause.",
"The query operator <operator> contains one or more unsupported expressions.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether operator is the best term. Here it seems it applies to a mix of different scenarios. Maybe just diluting it to "operation" works better. More generic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I partially agree. Although it would be better to have operations instead of operators but it's already like that all over the place in Spark:

  • SELECT * FROM VALUES (1, 10, 20) AS t(id, a, b) UNPIVOT (val FOR name IN (a, b)) ORDER BY t.a throw [MISSING_ATTRIBUTES.RESOLVED_ATTRIBUTE_MISSING_FROM_INPUT] Resolved attribute(s) "a" missing from "id", "name", "val" in operator Sort
  • SELECT * FROM VALUES(1) GROUP BY * throw [INVALID_USAGE_OF_STAR_OR_REGEX] Invalid usage of '*' in Aggregate
  • etc
    Also it would require more work since we would have to map operators to operations which seem like a unnecessary overhead since we already have same stuff in other exceptions. I would leave it as it is for now if you agree.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume the goal of this PR is to improve an error message. If an error message improvement needs a follow up PR then this sounds more like a check-box PR to me.
We should fix others that expose non-user facing gibberish as well. THAT coudl be a follow up. RIght now let's stop the bleed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be possible to create a map if this was an allowlist but it's not:

def specialExpressionsInUnsupportedOperator(plan: LogicalPlan): Seq[Expression] = {
    val exprs = plan.expressions
    val invalidExpressions = exprs.flatMap { root =>
      root.collect {
        case e: WindowExpression
          if !plan.isInstanceOf[Window] => e
        case e: AggregateExpression
          if !(plan.isInstanceOf[Aggregate] ||
               plan.isInstanceOf[Window] ||
               plan.isInstanceOf[CollectMetrics] ||
               onlyInLateralSubquery(plan)) => e
        case e: Generator
          if !(plan.isInstanceOf[Generate] ||
               plan.isInstanceOf[BaseEvalPythonUDTF]) => e
      }
    }
    invalidExpressions
  }

It doesn't make sense to map every single operator to its operation. I would still keep it as it is.

@srielau srielau Jul 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, then use a nested error condition to "roll them up". And you can always have "OTHER" catch all where you expose the the node.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how it's different. Instead of operator -> operation you propose operator -> sub error class. It would be an overkill. Although I agree that it's the right way to do in majority of cases I would say that this one should use operators.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srielau Should we proceed with merging this? Thanks!

throw new AnalysisException(
errorClass = "UNSUPPORTED_EXPR_FOR_OPERATOR",
messageParameters = Map(
"operator" -> operator.nodeName,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are internal names. When you look at the test results some of them are hard to map back to teh external name.

expectedErrorCondition = "UNSUPPORTED_EXPR_FOR_OPERATOR",
expectedMessageParameters = Map("invalidExprSqls" -> "\"sum(a)\", \"sum(c)\"")
expectedMessageParameters = Map(
"operator" -> "LateralJoin",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should come out as LATERAL (the keyword representing the lateral join)

"messageParameters" : {
"invalidExprSqls" : "\"row_number() OVER (ORDER BY salary ASC NULLS FIRST ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\""
"invalidExprSqls" : "\"row_number() OVER (ORDER BY salary ASC NULLS FIRST ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\"",
"operator" : "Join"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"operator" : "Join"
"operator" : "ON"

"messageParameters" : {
"invalidExprSqls" : "\"RANK() OVER (ORDER BY 1 ASC NULLS FIRST ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\""
"invalidExprSqls" : "\"RANK() OVER (ORDER BY 1 ASC NULLS FIRST ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\"",
"operator" : "Aggregate"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"operator" : "Aggregate"
"operator" : "GROUP BY"

"messageParameters" : {
"invalidExprSqls" : "\"max(col1)\""
"invalidExprSqls" : "\"max(col1)\"",
"operator" : "Sort"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"operator" : "Sort"
"operator" : "ORDER BY"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants