[SPARK-56883][SQL] DESCRIBE FUNCTION for SQL UDFs#55915
Closed
srielau wants to merge 3 commits into
Closed
Conversation
4de3ec1 to
d8fb332
Compare
Renders structured DESCRIBE output for SQL user-defined functions instead of the generic Class/Usage dump: Function/Type/Input/Returns, and in EXTENDED mode Comment/Collation/Deterministic/Data Access/Configs/Owner/ Create Time/Body/SQL Path. Ports the formatter from the Databricks runtime. - SQLFunction: add SCALAR/TABLE constants and fromExpressionInfo for reconstructing the function from its ExpressionInfo usage blob (covers both temp and persistent SQL UDFs). - DescribeFunctionCommand: dispatch to describeSQLFunction when the className matches SQLFunction.isSQLFunction; inline the SQL PATH display via SqlPathFormat (replaces DescribeFunctionCommandUtils). - SQLFunctionSuite: port describe tests for scalar/table SQL UDFs and derived routine characteristics.
d8fb332 to
07e70c9
Compare
cloud-fan
reviewed
May 18, 2026
Contributor
cloud-fan
left a comment
There was a problem hiding this comment.
Thanks for the cleanup — the new structured output is a real improvement over the JSON blob, and folding the SQL Path helper back into describeSQLFunction is a nice simplification.
Summary
- Prior state.
DESCRIBE FUNCTION [EXTENDED] <sql_udf>dumpsClass: sqlFunction.and the internalUsage:JSON blob, which is unusable for end users. - Design approach. Dispatch on
SQLFunction.isSQLFunction(info.getClassName)to a newdescribeSQLFunctionhelper that reconstructs theSQLFunctionfromExpressionInfo.usagevia a newSQLFunction.fromExpressionInfo(mirror offromCatalogFunction) and renders column-aligned key/value rows. Temp and persistent UDFs both flow through the sameExpressionInforound trip, so a single rendering path handles both. - Key design decisions.
- Reconstruct from
ExpressionInforather than the catalog, so the same path works for temp UDFs. - Patch
SessionCatalog.registerFunctionfor UDFs so the cachedExpressionInfocarries the structuredusageblob; without this the newfromExpressionInfowould fail after first invocation of a persistent UDF. - Delete
DescribeFunctionCommandUtils— its responsibility is absorbed inline now thatf.propertiesalready carriesfunction.resolutionPath.
- Reconstruct from
- Implementation sketch. Catalyst:
SQLFunction.fromExpressionInfo(new) +SCALAR/TABLEconstants;SessionCatalog.registerFunctionbranches onfuncDefinition.isUserDefinedFunction. sql/core:DescribeFunctionCommand.describeSQLFunction+formatParameters+tabulate+append. Tests: new scalar/table/derived-characteristics tests, plus extending theSPARK-56639test to invoke-then-describe (which exercises the SessionCatalog fix).
Main findings are concentrated in two areas: (a) Create Time: / Owner: are not actually persisted to the catalog, so they display incorrect values for persistent UDFs; (b) the catalog name is now silently dropped from the Function: row for SQL UDFs only. Plus a few smaller items inline.
align Function: qualification, tighten Create Time test - SQLFunction.toCatalogFunction now writes functionMetadataToProps so OWNER and CREATE_TIME survive a session restart; fromProps reads them back with backward-compatible defaults for older catalog payloads. - Extract SQLFunction.fromProps helper so fromCatalogFunction and fromExpressionInfo share a single SQLFunction-construction site. - SessionCatalog.registerFunction now accepts an optional ExpressionInfo; registerUserDefinedFunction's persistent path passes function.toExpressionInfo directly, skipping the toCatalogFunction -> fromCatalogFunction -> toExpressionInfo round trip and preserving the CREATE-time owner/createTime values. - DescribeFunctionCommand qualifies the Function: identifier through qualifyIdentifier for SQL UDFs too, so both paths render the catalog-qualified 3-part name consistently. - Fix two comment typos (functions.scala "into" -> "to"; SQLFunctionSuite "user specified" -> "user-specified"). - Tighten the describe-scalar-functions test: parse the rendered Create Time and assert it falls within a small window around the CREATE FUNCTION call (catches the regression where the cache build time leaked into the displayed timestamp).
allisonwang-db
approved these changes
May 18, 2026
Contributor
|
thanks, merging to master/4.x/4.2! |
cloud-fan
pushed a commit
that referenced
this pull request
May 19, 2026
### What changes were proposed in this pull request? Renders a structured `DESCRIBE FUNCTION [EXTENDED]` output for SQL user-defined functions (temporary and persistent) in place of the generic `Function / Class / Usage:<json blob>` dump that `DescribeFunctionCommand` produces today for any function whose `ExpressionInfo.className != null`. For SQL UDFs the output becomes: - `Function:` qualified name - `Type:` `SCALAR` or `TABLE` - `Input:` parameter list (name + SQL type, column-aligned; `DEFAULT <expr>` and `'comment'` annotations are added in EXTENDED mode) - `Returns:` scalar return type, or the table return columns (column comments and defaults are added in EXTENDED mode) - EXTENDED only: `Comment`, `Collation`, `Deterministic`, `Data Access` (`CONTAINS SQL` / `READS SQL DATA`), `Configs`, `Owner`, `Create Time`, `Body`, and `SQL Path`. `SQL Path:` is emitted only when both `spark.sql.path.enabled = true` and a frozen path was persisted on the function at `CREATE FUNCTION` time (SPARK-56639 / SPARK-56520). The path is read from the function's `function.resolutionPath` property and rendered through `SqlPathFormat.formatForDisplay`, producing the same `` `catalog`.`namespace` `` format used elsewhere in DESCRIBE output. This shows the resolution path that the function will use during analysis — the creator's PATH frozen at CREATE time, *not* the invoker's current `PATH`. Behavior for builtin functions and non-SQL UDFs is unchanged. Class hierarchy / dispatch: - `SQLFunction` (catalyst): adds the `SCALAR` / `TABLE` constants and a new `fromExpressionInfo(info, parser)` constructor that reconstructs a `SQLFunction` from the JSON usage blob produced by `toExpressionInfo`. This is the same path used by both temp UDFs (which are not in the catalog) and persistent UDFs. - `DescribeFunctionCommand` (sql/core): when `SQLFunction.isSQLFunction(info.getClassName)` is true, dispatches to a new `describeSQLFunction(info, parser)` helper that emits the column-aligned key/value rows shown above. The frozen SQL PATH is rendered inline through `SqlPathFormat`; the temporary `DescribeFunctionCommandUtils` helper introduced for that purpose by SPARK-56639 is removed (its single responsibility is now absorbed by `describeSQLFunction`). - `SessionCatalog.registerFunction`: when a persistent SQL UDF is invoked for the first time, the function registry caches it. Previously the cached `ExpressionInfo` was always built via `makeExprInfoForHiveFunction`, which sets `usage = null`. That worked for the pre-existing `DESCRIBE FUNCTION` codepath (which doesn't read `usage`), but breaks the new `describeSQLFunction` path: after a SQL UDF has been invoked once, `DESCRIBE FUNCTION` reads back the cached info and `SQLFunction.fromExpressionInfo` cannot parse `null`. `registerFunction` now branches on `funcDefinition.isUserDefinedFunction` and builds the structured `ExpressionInfo` via `UserDefinedFunction.fromCatalogFunction(funcDefinition, parser).toExpressionInfo` for SQL UDFs (matching the lookup-side build in `lookupPersistentFunction`), so the cached info has the right usage blob for DESCRIBE. ### Why are the changes needed? `DESCRIBE FUNCTION` is intended to give users a human-readable description of a routine, analogous to `DESCRIBE TABLE` for tables. For SQL UDFs the current output instead exposes the internal serialization format: ``` > DESCRIBE FUNCTION EXTENDED area; Function: default.area Class: sqlFunction. Usage: {"sqlFunction.inputParam":"width DOUBLE,height DOUBLE","sqlFunction.returnType":"DOUBLE","sqlFunction.expression":"width * height","sqlFunction.isTableFunc":"false",...} Extended Usage: ``` That JSON blob is not part of any public surface, and the literal string `sqlFunction.` for `Class:` is meaningless to users. All of the structured metadata we need — signature, return type, body, characteristics, frozen SQL PATH — is already serialized in `ExpressionInfo`; this PR just formats it. ### Does this PR introduce _any_ user-facing change? Yes — the rows returned by `DESCRIBE FUNCTION [EXTENDED] <sql_udf>` change. Before: ``` > DESCRIBE FUNCTION EXTENDED area; Function: default.area Class: sqlFunction. Usage: {"sqlFunction.inputParam":"width DOUBLE,height DOUBLE", ...} Extended Usage: ``` After (simple case): ``` > DESCRIBE FUNCTION EXTENDED area; Function: default.area Type: SCALAR Input: width DOUBLE 'width' height DOUBLE 'height' Returns: DOUBLE Comment: compute area Deterministic: true Data Access: CONTAINS SQL Owner: <owner> Create Time: <timestamp> Body: width * height ``` After (function created under `spark.sql.path.enabled = true` with a non-default PATH at CREATE time): ``` > SET spark.sql.path.enabled = true; > SET PATH = spark_catalog.path_func_db_a, system.builtin; > CREATE FUNCTION frozen_fn() RETURNS INT RETURN (SELECT MAX(id) FROM frozen_t); > SET PATH = spark_catalog.path_func_db_b, system.builtin; > DESCRIBE FUNCTION EXTENDED default.frozen_fn; Function: default.frozen_fn Type: SCALAR Input: () Returns: INT ... Body: (SELECT MAX(id) FROM frozen_t) SQL Path: `spark_catalog`.`path_func_db_a`, `system`.`builtin` ``` `SQL Path` reflects the creator's frozen PATH, not the session's current `PATH` at describe time. Output for builtin functions, Hive UDFs, and other non-SQL UDFs is unchanged. ### How was this patch tested? Added four unit tests to `SQLFunctionSuite` (sql/core): - `describe SQL scalar functions` — temporary and persistent scalar UDFs with comments, defaults, and `EXTENDED` mode. Asserts `Function`, `Type`, `Input` (column-aligned, with `DEFAULT` and `'comment'` in extended mode), `Returns`, `Deterministic`, `Data Access`, `Comment`, `Create Time`, `Body`. - `describe SQL table functions` — table UDFs with explicit return columns; asserts `Type: TABLE`, `Returns` columns, and the EXTENDED-only fields. - `describe SQL functions with derived routine characteristics` — checks that `Deterministic` and `Data Access` reflect derived values for functions that read tables / call non-deterministic builtins, and that user-supplied characteristics are preserved. - The existing `SPARK-56639: SQL function uses frozen SQL path` test is extended: after switching `PATH` to a different namespace it invokes `default.frozen_fn` (populating the function-registry cache) and then runs `DESCRIBE FUNCTION EXTENDED default.frozen_fn`, asserting the `SQL Path:` row shows the *creator's* frozen path (`` `spark_catalog`.`path_func_db_a`, `system`.`builtin` ``) and does *not* mention the invoker's current path namespace. This extension also exercises the `SessionCatalog.registerFunction` fix above: prior to the fix, the DESCRIBE after the invocation hit `CORRUPTED_CATALOG_FUNCTION` because the cached `ExpressionInfo` had `usage = null`. Each describe test uses `checkKeywordsExist` against `DESCRIBE FUNCTION [EXTENDED] <name>` output. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude (claude-opus-4-7) Closes #55915 from srielau/SPARK-56883-describe-sql-udf. Authored-by: Serge Rielau <serge@rielau.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com> (cherry picked from commit cfed631) Signed-off-by: Wenchen Fan <wenchen@databricks.com>
cloud-fan
pushed a commit
that referenced
this pull request
May 19, 2026
### What changes were proposed in this pull request? Renders a structured `DESCRIBE FUNCTION [EXTENDED]` output for SQL user-defined functions (temporary and persistent) in place of the generic `Function / Class / Usage:<json blob>` dump that `DescribeFunctionCommand` produces today for any function whose `ExpressionInfo.className != null`. For SQL UDFs the output becomes: - `Function:` qualified name - `Type:` `SCALAR` or `TABLE` - `Input:` parameter list (name + SQL type, column-aligned; `DEFAULT <expr>` and `'comment'` annotations are added in EXTENDED mode) - `Returns:` scalar return type, or the table return columns (column comments and defaults are added in EXTENDED mode) - EXTENDED only: `Comment`, `Collation`, `Deterministic`, `Data Access` (`CONTAINS SQL` / `READS SQL DATA`), `Configs`, `Owner`, `Create Time`, `Body`, and `SQL Path`. `SQL Path:` is emitted only when both `spark.sql.path.enabled = true` and a frozen path was persisted on the function at `CREATE FUNCTION` time (SPARK-56639 / SPARK-56520). The path is read from the function's `function.resolutionPath` property and rendered through `SqlPathFormat.formatForDisplay`, producing the same `` `catalog`.`namespace` `` format used elsewhere in DESCRIBE output. This shows the resolution path that the function will use during analysis — the creator's PATH frozen at CREATE time, *not* the invoker's current `PATH`. Behavior for builtin functions and non-SQL UDFs is unchanged. Class hierarchy / dispatch: - `SQLFunction` (catalyst): adds the `SCALAR` / `TABLE` constants and a new `fromExpressionInfo(info, parser)` constructor that reconstructs a `SQLFunction` from the JSON usage blob produced by `toExpressionInfo`. This is the same path used by both temp UDFs (which are not in the catalog) and persistent UDFs. - `DescribeFunctionCommand` (sql/core): when `SQLFunction.isSQLFunction(info.getClassName)` is true, dispatches to a new `describeSQLFunction(info, parser)` helper that emits the column-aligned key/value rows shown above. The frozen SQL PATH is rendered inline through `SqlPathFormat`; the temporary `DescribeFunctionCommandUtils` helper introduced for that purpose by SPARK-56639 is removed (its single responsibility is now absorbed by `describeSQLFunction`). - `SessionCatalog.registerFunction`: when a persistent SQL UDF is invoked for the first time, the function registry caches it. Previously the cached `ExpressionInfo` was always built via `makeExprInfoForHiveFunction`, which sets `usage = null`. That worked for the pre-existing `DESCRIBE FUNCTION` codepath (which doesn't read `usage`), but breaks the new `describeSQLFunction` path: after a SQL UDF has been invoked once, `DESCRIBE FUNCTION` reads back the cached info and `SQLFunction.fromExpressionInfo` cannot parse `null`. `registerFunction` now branches on `funcDefinition.isUserDefinedFunction` and builds the structured `ExpressionInfo` via `UserDefinedFunction.fromCatalogFunction(funcDefinition, parser).toExpressionInfo` for SQL UDFs (matching the lookup-side build in `lookupPersistentFunction`), so the cached info has the right usage blob for DESCRIBE. ### Why are the changes needed? `DESCRIBE FUNCTION` is intended to give users a human-readable description of a routine, analogous to `DESCRIBE TABLE` for tables. For SQL UDFs the current output instead exposes the internal serialization format: ``` > DESCRIBE FUNCTION EXTENDED area; Function: default.area Class: sqlFunction. Usage: {"sqlFunction.inputParam":"width DOUBLE,height DOUBLE","sqlFunction.returnType":"DOUBLE","sqlFunction.expression":"width * height","sqlFunction.isTableFunc":"false",...} Extended Usage: ``` That JSON blob is not part of any public surface, and the literal string `sqlFunction.` for `Class:` is meaningless to users. All of the structured metadata we need — signature, return type, body, characteristics, frozen SQL PATH — is already serialized in `ExpressionInfo`; this PR just formats it. ### Does this PR introduce _any_ user-facing change? Yes — the rows returned by `DESCRIBE FUNCTION [EXTENDED] <sql_udf>` change. Before: ``` > DESCRIBE FUNCTION EXTENDED area; Function: default.area Class: sqlFunction. Usage: {"sqlFunction.inputParam":"width DOUBLE,height DOUBLE", ...} Extended Usage: ``` After (simple case): ``` > DESCRIBE FUNCTION EXTENDED area; Function: default.area Type: SCALAR Input: width DOUBLE 'width' height DOUBLE 'height' Returns: DOUBLE Comment: compute area Deterministic: true Data Access: CONTAINS SQL Owner: <owner> Create Time: <timestamp> Body: width * height ``` After (function created under `spark.sql.path.enabled = true` with a non-default PATH at CREATE time): ``` > SET spark.sql.path.enabled = true; > SET PATH = spark_catalog.path_func_db_a, system.builtin; > CREATE FUNCTION frozen_fn() RETURNS INT RETURN (SELECT MAX(id) FROM frozen_t); > SET PATH = spark_catalog.path_func_db_b, system.builtin; > DESCRIBE FUNCTION EXTENDED default.frozen_fn; Function: default.frozen_fn Type: SCALAR Input: () Returns: INT ... Body: (SELECT MAX(id) FROM frozen_t) SQL Path: `spark_catalog`.`path_func_db_a`, `system`.`builtin` ``` `SQL Path` reflects the creator's frozen PATH, not the session's current `PATH` at describe time. Output for builtin functions, Hive UDFs, and other non-SQL UDFs is unchanged. ### How was this patch tested? Added four unit tests to `SQLFunctionSuite` (sql/core): - `describe SQL scalar functions` — temporary and persistent scalar UDFs with comments, defaults, and `EXTENDED` mode. Asserts `Function`, `Type`, `Input` (column-aligned, with `DEFAULT` and `'comment'` in extended mode), `Returns`, `Deterministic`, `Data Access`, `Comment`, `Create Time`, `Body`. - `describe SQL table functions` — table UDFs with explicit return columns; asserts `Type: TABLE`, `Returns` columns, and the EXTENDED-only fields. - `describe SQL functions with derived routine characteristics` — checks that `Deterministic` and `Data Access` reflect derived values for functions that read tables / call non-deterministic builtins, and that user-supplied characteristics are preserved. - The existing `SPARK-56639: SQL function uses frozen SQL path` test is extended: after switching `PATH` to a different namespace it invokes `default.frozen_fn` (populating the function-registry cache) and then runs `DESCRIBE FUNCTION EXTENDED default.frozen_fn`, asserting the `SQL Path:` row shows the *creator's* frozen path (`` `spark_catalog`.`path_func_db_a`, `system`.`builtin` ``) and does *not* mention the invoker's current path namespace. This extension also exercises the `SessionCatalog.registerFunction` fix above: prior to the fix, the DESCRIBE after the invocation hit `CORRUPTED_CATALOG_FUNCTION` because the cached `ExpressionInfo` had `usage = null`. Each describe test uses `checkKeywordsExist` against `DESCRIBE FUNCTION [EXTENDED] <name>` output. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude (claude-opus-4-7) Closes #55915 from srielau/SPARK-56883-describe-sql-udf. Authored-by: Serge Rielau <serge@rielau.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com> (cherry picked from commit cfed631) Signed-off-by: Wenchen Fan <wenchen@databricks.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this pull request?
Renders a structured
DESCRIBE FUNCTION [EXTENDED]output for SQL user-defined functions (temporary and persistent) in place of the genericFunction / Class / Usage:<json blob>dump thatDescribeFunctionCommandproduces today for any function whoseExpressionInfo.className != null.For SQL UDFs the output becomes:
Function:qualified nameType:SCALARorTABLEInput:parameter list (name + SQL type, column-aligned;DEFAULT <expr>and'comment'annotations are added in EXTENDED mode)Returns:scalar return type, or the table return columns (column comments and defaults are added in EXTENDED mode)Comment,Collation,Deterministic,Data Access(CONTAINS SQL/READS SQL DATA),Configs,Owner,Create Time,Body, andSQL Path.SQL Path:is emitted only when bothspark.sql.path.enabled = trueand a frozen path was persisted on the function atCREATE FUNCTIONtime (SPARK-56639 / SPARK-56520). The path is read from the function'sfunction.resolutionPathproperty and rendered throughSqlPathFormat.formatForDisplay, producing the same`catalog`.`namespace`format used elsewhere in DESCRIBE output. This shows the resolution path that the function will use during analysis — the creator's PATH frozen at CREATE time, not the invoker's currentPATH.Behavior for builtin functions and non-SQL UDFs is unchanged.
Class hierarchy / dispatch:
SQLFunction(catalyst): adds theSCALAR/TABLEconstants and a newfromExpressionInfo(info, parser)constructor that reconstructs aSQLFunctionfrom the JSON usage blob produced bytoExpressionInfo. This is the same path used by both temp UDFs (which are not in the catalog) and persistent UDFs.DescribeFunctionCommand(sql/core): whenSQLFunction.isSQLFunction(info.getClassName)is true, dispatches to a newdescribeSQLFunction(info, parser)helper that emits the column-aligned key/value rows shown above. The frozen SQL PATH is rendered inline throughSqlPathFormat; the temporaryDescribeFunctionCommandUtilshelper introduced for that purpose by SPARK-56639 is removed (its single responsibility is now absorbed bydescribeSQLFunction).SessionCatalog.registerFunction: when a persistent SQL UDF is invoked for the first time, the function registry caches it. Previously the cachedExpressionInfowas always built viamakeExprInfoForHiveFunction, which setsusage = null. That worked for the pre-existingDESCRIBE FUNCTIONcodepath (which doesn't readusage), but breaks the newdescribeSQLFunctionpath: after a SQL UDF has been invoked once,DESCRIBE FUNCTIONreads back the cached info andSQLFunction.fromExpressionInfocannot parsenull.registerFunctionnow branches onfuncDefinition.isUserDefinedFunctionand builds the structuredExpressionInfoviaUserDefinedFunction.fromCatalogFunction(funcDefinition, parser).toExpressionInfofor SQL UDFs (matching the lookup-side build inlookupPersistentFunction), so the cached info has the right usage blob for DESCRIBE.Why are the changes needed?
DESCRIBE FUNCTIONis intended to give users a human-readable description of a routine, analogous toDESCRIBE TABLEfor tables. For SQL UDFs the current output instead exposes the internal serialization format:That JSON blob is not part of any public surface, and the literal string
sqlFunction.forClass:is meaningless to users. All of the structured metadata we need — signature, return type, body, characteristics, frozen SQL PATH — is already serialized inExpressionInfo; this PR just formats it.Does this PR introduce any user-facing change?
Yes — the rows returned by
DESCRIBE FUNCTION [EXTENDED] <sql_udf>change.Before:
After (simple case):
After (function created under
spark.sql.path.enabled = truewith a non-default PATH at CREATE time):SQL Pathreflects the creator's frozen PATH, not the session's currentPATHat describe time. Output for builtin functions, Hive UDFs, and other non-SQL UDFs is unchanged.How was this patch tested?
Added four unit tests to
SQLFunctionSuite(sql/core):describe SQL scalar functions— temporary and persistent scalar UDFs with comments, defaults, andEXTENDEDmode. AssertsFunction,Type,Input(column-aligned, withDEFAULTand'comment'in extended mode),Returns,Deterministic,Data Access,Comment,Create Time,Body.describe SQL table functions— table UDFs with explicit return columns; assertsType: TABLE,Returnscolumns, and the EXTENDED-only fields.describe SQL functions with derived routine characteristics— checks thatDeterministicandData Accessreflect derived values for functions that read tables / call non-deterministic builtins, and that user-supplied characteristics are preserved.SPARK-56639: SQL function uses frozen SQL pathtest is extended: after switchingPATHto a different namespace it invokesdefault.frozen_fn(populating the function-registry cache) and then runsDESCRIBE FUNCTION EXTENDED default.frozen_fn, asserting theSQL Path:row shows the creator's frozen path (`spark_catalog`.`path_func_db_a`, `system`.`builtin`) and does not mention the invoker's current path namespace. This extension also exercises theSessionCatalog.registerFunctionfix above: prior to the fix, the DESCRIBE after the invocation hitCORRUPTED_CATALOG_FUNCTIONbecause the cachedExpressionInfohadusage = null.Each describe test uses
checkKeywordsExistagainstDESCRIBE FUNCTION [EXTENDED] <name>output.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude (claude-opus-4-7)