This is an automated email from the ASF dual-hosted git repository.

mchades pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new be66ca6d91 [#10002] improvement(function): Improve error messages for 
UDF updateImpl/removeImpl/addImpl operations (#10042)
be66ca6d91 is described below

commit be66ca6d9149445ea1246daa1faeecc621b7d116
Author: mchades <[email protected]>
AuthorDate: Thu Feb 26 17:19:30 2026 +0800

    [#10002] improvement(function): Improve error messages for UDF 
updateImpl/removeImpl/addImpl operations (#10042)
    
    ### What changes were proposed in this pull request?
    
    Improves error messages for `updateImpl`, `removeImpl`, and `addImpl`
    operations in `ManagedFunctionOperations` to provide actionable context
    when operations fail.
    
    Previously, when a user specified parameters with a mismatched
    `dataType` or name, the error message could be misleading. For example,
    the user would see "runtime 'SPARK' not found in the definition" when
    SPARK actually existed — the real problem was that the parameter
    signature didn't match any definition.
    
    **Changes:**
    
    - Added a `formatParams()` helper to format parameter arrays as
    human-readable strings (e.g., `(x: integer, y: string)`).
    - **"no definition found"** errors now list all available parameter
    signatures, so users can compare and spot mismatches in `dataType` or
    parameter names.
    - **"runtime not found"** errors now list the runtimes actually present
    in the matched definition.
    - Applied consistently to `updateImplInDefinition`,
    `removeImplFromDefinition`, and `addImplToDefinition`.
    
    **Before:**
    ```
    Cannot update implementation: runtime 'SPARK' not found in the definition
    ```
    
    **After (runtime not found):**
    ```
    Cannot update implementation: runtime 'TRINO' not found in the definition 
(a: integer). Available runtimes in that definition: [SPARK]
    ```
    
    **After (parameters mismatch):**
    ```
    Cannot update implementation: no definition found with the specified 
parameters (x: integer). Available parameter signatures: [(a: integer)]
    ```
    
    ### Why are the changes needed?
    
    The previous error messages were misleading. A user receiving "runtime
    'SPARK' not found" could not tell whether SPARK was truly absent or
    whether their parameter signature was wrong. This change makes the error
    messages actionable and helps users quickly identify the root cause.
    
    Fixes #10002
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes — error message text for failed `updateImpl`, `removeImpl`, and
    `addImpl` REST API calls is now more descriptive and actionable.
    
    ### How was this patch tested?
    
    - Updated `testAlterFunctionUpdateImplNonExistingRuntime` and
    `testAlterFunctionRemoveImplNonExistingRuntime` to assert that the error
    message includes both the missing and available runtimes.
    - Added `testAlterFunctionUpdateImplWrongParameters` and
    `testAlterFunctionRemoveImplWrongParameters` to verify that the "no
    definition found" error includes both the specified signature and
    available signatures.
    
    Co-authored-by: Copilot <[email protected]>
---
 .gitignore                                         |   1 +
 .../catalog/ManagedFunctionOperations.java         |  46 ++++++++-
 .../catalog/TestManagedFunctionOperations.java     | 106 ++++++++++++++++++---
 3 files changed, 137 insertions(+), 16 deletions(-)

diff --git a/.gitignore b/.gitignore
index d837f81741..f84bb25bd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,3 +60,4 @@ derby.log
 web/node_modules
 web/dist
 web/.next
+.worktrees/
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/ManagedFunctionOperations.java
 
b/core/src/main/java/org/apache/gravitino/catalog/ManagedFunctionOperations.java
index 7341052cce..30418424ba 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/ManagedFunctionOperations.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/ManagedFunctionOperations.java
@@ -451,6 +451,19 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
     }
   }
 
+  private static String formatParams(FunctionParam[] params) {
+    if (params == null || params.length == 0) {
+      return "()";
+    }
+    StringBuilder sb = new StringBuilder("(");
+    for (int i = 0; i < params.length; i++) {
+      if (i > 0) sb.append(", ");
+      sb.append(params[i].name()).append(": 
").append(params[i].dataType().simpleString());
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
   private boolean parametersMatch(FunctionParam[] params1, FunctionParam[] 
params2) {
     if (params1.length != params2.length) {
       return false;
@@ -491,8 +504,13 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
     }
 
     if (!found) {
+      List<String> availableSignatures =
+          definitions.stream().map(d -> 
formatParams(d.parameters())).collect(Collectors.toList());
       throw new IllegalArgumentException(
-          "Cannot add implementation: no definition found with the specified 
parameters");
+          String.format(
+              "Cannot add implementation: no definition found with the 
specified parameters %s. "
+                  + "Available parameter signatures: %s",
+              formatParams(targetParams), availableSignatures));
     }
 
     return result;
@@ -506,12 +524,14 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
     List<FunctionDefinition> result = new ArrayList<>();
     boolean definitionFound = false;
     boolean runtimeFound = false;
+    List<FunctionImpl.RuntimeType> availableRuntimes = new ArrayList<>();
 
     for (FunctionDefinition def : definitions) {
       if (parametersMatch(def.parameters(), targetParams)) {
         definitionFound = true;
         List<FunctionImpl> impls = new ArrayList<>();
         for (FunctionImpl impl : def.impls()) {
+          availableRuntimes.add(impl.runtime());
           if (impl.runtime() == runtime) {
             runtimeFound = true;
             impls.add(newImpl);
@@ -526,14 +546,21 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
     }
 
     if (!definitionFound) {
+      List<String> availableSignatures =
+          definitions.stream().map(d -> 
formatParams(d.parameters())).collect(Collectors.toList());
       throw new IllegalArgumentException(
-          "Cannot update implementation: no definition found with the 
specified parameters");
+          String.format(
+              "Cannot update implementation: no definition found with the 
specified parameters %s. "
+                  + "Available parameter signatures: %s",
+              formatParams(targetParams), availableSignatures));
     }
 
     if (!runtimeFound) {
       throw new IllegalArgumentException(
           String.format(
-              "Cannot update implementation: runtime '%s' not found in the 
definition", runtime));
+              "Cannot update implementation: runtime '%s' not found in the 
definition %s. "
+                  + "Available runtimes in that definition: %s",
+              runtime, formatParams(targetParams), availableRuntimes));
     }
 
     return result;
@@ -546,6 +573,7 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
     List<FunctionDefinition> result = new ArrayList<>();
     boolean definitionFound = false;
     boolean runtimeFound = false;
+    List<FunctionImpl.RuntimeType> availableRuntimes = new ArrayList<>();
 
     for (FunctionDefinition def : definitions) {
       if (parametersMatch(def.parameters(), targetParams)) {
@@ -561,6 +589,7 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
 
         List<FunctionImpl> impls = new ArrayList<>();
         for (FunctionImpl impl : def.impls()) {
+          availableRuntimes.add(impl.runtime());
           if (impl.runtime() == runtime) {
             runtimeFound = true;
           } else {
@@ -574,14 +603,21 @@ public class ManagedFunctionOperations implements 
FunctionCatalog {
     }
 
     if (!definitionFound) {
+      List<String> availableSignatures =
+          definitions.stream().map(d -> 
formatParams(d.parameters())).collect(Collectors.toList());
       throw new IllegalArgumentException(
-          "Cannot remove implementation: no definition found with the 
specified parameters");
+          String.format(
+              "Cannot remove implementation: no definition found with the 
specified parameters %s. "
+                  + "Available parameter signatures: %s",
+              formatParams(targetParams), availableSignatures));
     }
 
     if (!runtimeFound) {
       throw new IllegalArgumentException(
           String.format(
-              "Cannot remove implementation: runtime '%s' not found in the 
definition", runtime));
+              "Cannot remove implementation: runtime '%s' not found in the 
definition %s. "
+                  + "Available runtimes in that definition: %s",
+              runtime, formatParams(targetParams), availableRuntimes));
     }
 
     return result;
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestManagedFunctionOperations.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestManagedFunctionOperations.java
index ff3b6638e2..60d5513f99 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestManagedFunctionOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestManagedFunctionOperations.java
@@ -627,12 +627,54 @@ public class TestManagedFunctionOperations {
     FunctionImpl trinoImpl =
         FunctionImpls.ofJava(FunctionImpl.RuntimeType.TRINO, 
"com.example.TrinoUDF");
 
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            functionOperations.alterFunction(
-                funcIdent,
-                FunctionChange.updateImpl(params, 
FunctionImpl.RuntimeType.TRINO, trinoImpl)));
+    IllegalArgumentException ex =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                functionOperations.alterFunction(
+                    funcIdent,
+                    FunctionChange.updateImpl(params, 
FunctionImpl.RuntimeType.TRINO, trinoImpl)));
+    Assertions.assertTrue(
+        ex.getMessage().contains("TRINO"), "Error message should mention the 
missing runtime");
+    Assertions.assertTrue(
+        ex.getMessage().contains("SPARK"),
+        "Error message should list available runtimes in that definition");
+  }
+
+  @Test
+  public void testAlterFunctionUpdateImplWrongParameters() {
+    NameIdentifier funcIdent = 
getFunctionIdent("func_update_impl_wrongparams");
+    FunctionParam[] params = new FunctionParam[] {FunctionParams.of("a", 
Types.IntegerType.get())};
+    FunctionImpl sparkImpl =
+        FunctionImpls.ofJava(FunctionImpl.RuntimeType.SPARK, 
"com.example.SparkUDF");
+    FunctionDefinition[] definitions =
+        new FunctionDefinition[] {
+          createDefinitionWithImpls(params, Types.StringType.get(), new 
FunctionImpl[] {sparkImpl})
+        };
+
+    functionOperations.registerFunction(
+        funcIdent, "Test function", FunctionType.SCALAR, true, definitions);
+
+    // Try to update with wrong parameter name - definition won't be found
+    FunctionParam[] wrongParams =
+        new FunctionParam[] {FunctionParams.of("x", Types.IntegerType.get())};
+    FunctionImpl newImpl =
+        FunctionImpls.ofJava(FunctionImpl.RuntimeType.SPARK, 
"com.example.NewSparkUDF");
+
+    IllegalArgumentException ex =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                functionOperations.alterFunction(
+                    funcIdent,
+                    FunctionChange.updateImpl(
+                        wrongParams, FunctionImpl.RuntimeType.SPARK, 
newImpl)));
+    Assertions.assertTrue(
+        ex.getMessage().contains("(x: integer)"),
+        "Error message should include the specified parameter signature");
+    Assertions.assertTrue(
+        ex.getMessage().contains("(a: integer)"),
+        "Error message should list available parameter signatures");
   }
 
   @Test
@@ -699,11 +741,53 @@ public class TestManagedFunctionOperations {
         funcIdent, "Test function", FunctionType.SCALAR, true, definitions);
 
     // Try to remove Trino implementation which doesn't exist
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            functionOperations.alterFunction(
-                funcIdent, FunctionChange.removeImpl(params, 
FunctionImpl.RuntimeType.TRINO)));
+    IllegalArgumentException ex =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                functionOperations.alterFunction(
+                    funcIdent, FunctionChange.removeImpl(params, 
FunctionImpl.RuntimeType.TRINO)));
+    Assertions.assertTrue(
+        ex.getMessage().contains("TRINO"), "Error message should mention the 
missing runtime");
+    Assertions.assertTrue(
+        ex.getMessage().contains("SPARK"),
+        "Error message should list available runtimes in that definition");
+  }
+
+  @Test
+  public void testAlterFunctionRemoveImplWrongParameters() {
+    NameIdentifier funcIdent = 
getFunctionIdent("func_remove_impl_wrongparams");
+    FunctionParam[] params = new FunctionParam[] {FunctionParams.of("a", 
Types.IntegerType.get())};
+    FunctionImpl sparkImpl =
+        FunctionImpls.ofJava(FunctionImpl.RuntimeType.SPARK, 
"com.example.SparkUDF");
+    FunctionImpl trinoImpl =
+        FunctionImpls.ofJava(FunctionImpl.RuntimeType.TRINO, 
"com.example.TrinoUDF");
+    FunctionDefinition[] definitions =
+        new FunctionDefinition[] {
+          createDefinitionWithImpls(
+              params, Types.StringType.get(), new FunctionImpl[] {sparkImpl, 
trinoImpl})
+        };
+
+    functionOperations.registerFunction(
+        funcIdent, "Test function", FunctionType.SCALAR, true, definitions);
+
+    // Try to remove with wrong parameter type - definition won't be found
+    FunctionParam[] wrongParams =
+        new FunctionParam[] {FunctionParams.of("a", Types.LongType.get())};
+
+    IllegalArgumentException ex =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                functionOperations.alterFunction(
+                    funcIdent,
+                    FunctionChange.removeImpl(wrongParams, 
FunctionImpl.RuntimeType.SPARK)));
+    Assertions.assertTrue(
+        ex.getMessage().contains("(a: long)"),
+        "Error message should include the specified parameter signature");
+    Assertions.assertTrue(
+        ex.getMessage().contains("(a: integer)"),
+        "Error message should list available parameter signatures");
   }
 
   @Test

Reply via email to