-
Notifications
You must be signed in to change notification settings - Fork 28.3k
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
[SPARK-43922] Add named parameter support in parser for function calls #41429
Conversation
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.
Thanks for implementing it! Except the ones Daniel have posted, add one nit comment.
...alyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/NamedArgumentExpression.scala
Show resolved
Hide resolved
if (e.namedArgumentExpression != null) { | ||
val key = e.namedArgumentExpression.key.strictIdentifier | ||
val value = e.namedArgumentExpression.value | ||
NamedArgumentExpression(key.getText, expression(value)) |
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.
+1
…essions/NamedArgumentExpression.scala Co-authored-by: Xinyi <xyyu15@gmail.com>
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.
The implementation generally LGTM, I tried to come up with testing ideas.
|
||
override def dataType: DataType = value.dataType | ||
|
||
override def toString: String = s"""$key => $value""" |
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.
override def toString: String = s"""$key => $value""" | |
override def toString: String = s"$key => $value" |
@dtenedor Thanks for the comments! I've added the tests. |
@@ -921,7 +930,7 @@ primaryExpression | |||
| LEFT_PAREN namedExpression (COMMA namedExpression)+ RIGHT_PAREN #rowConstructor | |||
| LEFT_PAREN query RIGHT_PAREN #subqueryExpression | |||
| IDENTIFIER_KW LEFT_PAREN expression RIGHT_PAREN #identifierClause | |||
| functionName LEFT_PAREN (setQuantifier? argument+=expression (COMMA argument+=expression)*)? RIGHT_PAREN | |||
| functionName LEFT_PAREN (setQuantifier? argument+=functionArgument (COMMA argument+=functionArgument)*)? RIGHT_PAREN |
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.
can you maybe fix formatting of these three lines so they break before the 94th column that the above labeks (e.g. #identifierClause
) start on? This could make the parser logic easier to read.
...alyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/NamedArgumentExpression.scala
Outdated
Show resolved
Hide resolved
...alyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/NamedArgumentExpression.scala
Outdated
Show resolved
Hide resolved
@@ -1544,8 +1544,17 @@ class AstBuilder extends SqlBaseParserBaseVisitor[AnyRef] with SQLConfHelper wit | |||
if (name.length > 1) { | |||
throw QueryParsingErrors.invalidTableValuedFunctionNameError(name, ctx) | |||
} | |||
val args = func.functionArgument.asScala.map { e => | |||
if (e.namedArgumentExpression != null) { |
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.
You can simplify using Option
and map
:
Option(e.namedArgumentExpression).map { n =>
NamedArgumentExpression(n.get.getText, expression(nvalue))
}.getOrElse {
expression(e)
}
Same on L2188 below.
if (e.namedArgumentExpression != null) { | ||
val key = e.namedArgumentExpression.key.getText | ||
val value = e.namedArgumentExpression.value | ||
NamedArgumentExpression(key, expression(value)) |
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.
Can you control this behavior with a SQLConf? You can create a new entry like ALLOW_NAMED_FUNCTION_ARGUMENTS
and return an error here if the config is not set to true:
…essions/NamedArgumentExpression.scala Co-authored-by: Daniel Tenedorio <daniel.tenedorio@databricks.com>
…essions/NamedArgumentExpression.scala Co-authored-by: Daniel Tenedorio <daniel.tenedorio@databricks.com>
@dtenedor The configuration has been added. Other comments has been addressed. |
NamedArgumentExpression(n.key.getText, expression(n.value)) | ||
} else { | ||
throw new ParseException( | ||
errorClass = "Named arguments not enabled.", |
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.
This is not quite how the error classes work :) instead you need to:
- Add a new entry in error-classes.json [1]
- Add a corresponding function in QueryCompilationErrors.scala [2]
- Call that function here like
throw QueryCompilationErrors.namedArgumentsNotEnabled(n.key.getText)
- Exercise this behavior in a unit test to make sure we really get this error message if the configuration is not enabled.
sql/core/src/test/scala/org/apache/spark/sql/errors/QueryParsingErrorsSuite.scala
Show resolved
Hide resolved
@@ -862,6 +862,15 @@ expression | |||
: booleanExpression | |||
; | |||
|
|||
namedArgumentExpression | |||
: key=identifier FAT_ARROW value=expression |
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.
identifier can be quoted and unquoted
Shall we make it IDENTIFIER
(unquoted) only?
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.
@gengliangwang After looking at the design doc, would it be fine to keep it like this?
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
Outdated
Show resolved
Hide resolved
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala
Outdated
Show resolved
Hide resolved
Co-authored-by: Maxim Gekk <max.gekk@gmail.com>
@MaxGekk Sorry about the confusion. I realized your suggestion for quoting the spark.sql.allowNamedFunctionArguments config was accurate. That was my mistake. I committed your suggestion. Let me know what you think! |
*/ | ||
case class NamedArgumentExpression(key: String, value: Expression) | ||
extends UnaryExpression with Unevaluable { | ||
override def nullable: Boolean = value.nullable |
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.
nit: we can remove this line as UnaryExpression
already provides an implementation of nullable
@@ -1527,6 +1527,18 @@ class AstBuilder extends SqlBaseParserBaseVisitor[AnyRef] with SQLConfHelper wit | |||
} | |||
} | |||
|
|||
private def extractExpression(expr: FunctionArgumentContext, funcName: String) : Expression = { |
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.
how about extractFuncArgument
?
@@ -875,6 +875,18 @@ class QueryExecutionErrorsSuite | |||
sqlState = "XX000") | |||
} | |||
|
|||
test("INTERNAL_ERROR: Calling eval on Unevaluable NamedArgumentExpression") { |
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.
I don't think we need to test it. Unevaluable
is not added by this PR and has been there for a long time.
@cloud-fan Thanks for the review! Addressed the comments. |
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.
@learningchess2003 Are there any end-to-end tests? If there are some, could you point them out. If not, could you add something to sql/core/src/test/resources/sql-tests/inputs
@MaxGekk Sounds good. I've added the end-to-end tests. In the next PR after this, most of the error messages will be replaced with the intended results. Right now, nothing is really supported yet. |
What changes were proposed in this pull request?
We plan on adding two new tokens called
namedArgumentExpression
andfunctionArgument
which would enable this feature. When parsing this logic, we also make changes to ASTBuilder such that it can detect if the argument passed is a named argument or a positional one.Here is the link for the design document:
https://docs.google.com/document/d/1uOTX0MICxqu8fNanIsiyB8FV68CceGGpa8BJLP2u9o4/edit
Why are the changes needed?
This is part of a larger project to implement named parameter support for user defined functions, built-in functions, and table valued functions.
Does this PR introduce any user-facing change?
Yes, the user would be able to call functions with argument lists that contain named arguments.
How was this patch tested?
We add tests in the PlanParserSuite that will verify that the plan parsed is as intended.