-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-32677][SQL] Load function resource before create #29502
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
Changes from 16 commits
9a81c65
ce83487
c75cff4
def8c4e
97dcadf
3e6320d
ba248c9
9fb3c13
fe98b71
8b3a509
fe77698
4750e2e
882942f
d55b8bc
75e61e9
bb6d742
438291b
6584f39
701c25d
3cf971b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1359,16 +1359,23 @@ class SessionCatalog( | |
| val info = new ExpressionInfo(funcDefinition.className, func.database.orNull, func.funcName) | ||
| val builder = | ||
| functionBuilder.getOrElse { | ||
| val className = funcDefinition.className | ||
| if (!Utils.classIsLoadable(className)) { | ||
| throw new AnalysisException(s"Can not load class '$className' when registering " + | ||
| s"the function '$func', please make sure it is on the classpath") | ||
| } | ||
| makeFunctionBuilder(func.unquotedString, className) | ||
| requireFunctionClassExists(funcDefinition) | ||
| makeFunctionBuilder(func.unquotedString, funcDefinition.className) | ||
| } | ||
| functionRegistry.registerFunction(func, info, builder) | ||
| } | ||
|
|
||
| /** | ||
| * Make sure function class is on the classpath. | ||
| */ | ||
| def requireFunctionClassExists(funcDefinition: CatalogFunction): Unit = { | ||
| val className = funcDefinition.className | ||
| if (!Utils.classIsLoadable(className)) { | ||
| throw new AnalysisException(s"Can not load class '$className' when registering " + | ||
| s"the function '${funcDefinition.identifier}', please make sure it is on the classpath") | ||
|
Contributor
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. how about
Contributor
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. sounds good.
Contributor
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.
|
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Unregister a temporary or permanent function from a session-specific [[FunctionRegistry]] | ||
| * Return true if function exists. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,14 +76,15 @@ case class CreateFunctionCommand( | |
| override def run(sparkSession: SparkSession): Seq[Row] = { | ||
| val catalog = sparkSession.sessionState.catalog | ||
| val func = CatalogFunction(FunctionIdentifier(functionName, databaseName), className, resources) | ||
| catalog.loadFunctionResources(resources) | ||
| // We fail fast if function class is not exists. | ||
| catalog.requireFunctionClassExists(func) | ||
|
cloud-fan marked this conversation as resolved.
Outdated
Contributor
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. I think we only need to do this for non-temp functions.
Contributor
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. Yeah, I also think about it. |
||
| if (isTemp) { | ||
| // We first load resources and then put the builder in the function registry. | ||
| catalog.loadFunctionResources(resources) | ||
| catalog.registerFunction(func, overrideIfExists = replace) | ||
| } else { | ||
| // Handles `CREATE OR REPLACE FUNCTION AS ... USING ...` | ||
| if (replace && catalog.functionExists(func.identifier)) { | ||
| // alter the function in the metastore | ||
| // Alter the function in the metastore | ||
| catalog.alterFunction(func) | ||
| } else { | ||
| // For a permanent, we will store the metadata into underlying external catalog. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,7 +42,8 @@ CREATE FUNCTION udaf1 AS 'test.non.existent.udaf' | |
| -- !query schema | ||
| struct<> | ||
| -- !query output | ||
|
|
||
| org.apache.spark.sql.AnalysisException | ||
| Can not load class 'test.non.existent.udaf' when registering the function 'udaf1', please make sure it is on the classpath; | ||
|
|
||
|
|
||
| -- !query | ||
|
|
@@ -51,7 +52,7 @@ SELECT default.udaf1(udf(int_col1)) as udaf1, udf(default.udaf1(udf(int_col1))) | |
| struct<> | ||
| -- !query output | ||
| org.apache.spark.sql.AnalysisException | ||
| Can not load class 'test.non.existent.udaf' when registering the function 'default.udaf1', please make sure it is on the classpath; line 1 pos 94 | ||
|
Contributor
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. Here it was Do you know why?
Contributor
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. For permanent, the old code path is: So the previously msg always contains database name. Now the code path is:
Contributor
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. then shall we fill in the default database when creating permanent functions, to make the error message better?
Contributor
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. Permanent function always need a database, it's ok to fill database name when creating. |
||
| Undefined function: 'udaf1'. This function is neither a registered temporary function nor a permanent function registered in the database 'default'.; line 1 pos 7 | ||
|
|
||
|
|
||
| -- !query | ||
|
|
@@ -67,4 +68,6 @@ DROP FUNCTION udaf1 | |
| -- !query schema | ||
| struct<> | ||
| -- !query output | ||
| org.apache.spark.sql.catalyst.analysis.NoSuchPermanentFunctionException | ||
| Function 'default.udaf1' not found in database 'default'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -156,7 +156,9 @@ class HiveUDFDynamicLoadSuite extends QueryTest with SQLTestUtils with TestHiveS | |
|
|
||
| sql(s"CREATE FUNCTION ${udfInfo.funcName} AS '${udfInfo.className}' USING JAR '$jarUrl'") | ||
|
|
||
| assert(Thread.currentThread().getContextClassLoader eq sparkClassLoader) | ||
|
Contributor
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 need load resource during create function.
Contributor
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. This test was created in #27025. |
||
| assert(Thread.currentThread().getContextClassLoader ne sparkClassLoader) | ||
| assert(Thread.currentThread().getContextClassLoader eq | ||
| spark.sqlContext.sharedState.jarClassLoader) | ||
|
|
||
| // JAR will be loaded at first usage, and it will change the current thread's | ||
| // context classloader to jar classloader in sharedState. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.