This is an automated email from the ASF dual-hosted git repository.
jerryshao 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 64cb57422a [#10659] fix(core): Correctly handle tag rename conflict in
TagManager (#10661)
64cb57422a is described below
commit 64cb57422a67882db012473e245fcf9bef9ad406
Author: Chisom Uma <[email protected]>
AuthorDate: Thu Apr 23 13:36:23 2026 +0100
[#10659] fix(core): Correctly handle tag rename conflict in TagManager
(#10661)
### What changes were proposed in this pull request?
This PR improves the error handling in `TagManager.alterTag` when a tag
is renamed to a name that already exists in the same metalake.
Specifically:
- Caught `EntityAlreadyExistsException` during the [alterTag]
TagManager.java operation.
- Added logic to extract the intended `newName` from the [TagChange]
TagChange.java array.
- Now throws a domain-specific `TagAlreadyExistsException` with a clear
message, ensuring the server returns an HTTP 409 (Conflict) instead of
an HTTP 500 (Internal Server Error).
### Why are the changes needed?
Previously, renaming a tag to an existing name caused the server to
return a generic `RuntimeException`, which surfaced as an HTTP 500 error
to the client. This is a valid user mistake that should be handled as a
conflict (HTTP 409) with a descriptive error message.
Fix: #10659
### Does this PR introduce _any_ user-facing change?
Yes, it changes the error response when a tag rename conflict occurs:
- **Old behavior**: Returns HTTP 500 (Internal Server Error).
- **New behavior**: Returns HTTP 409 (Conflict) with the message: `"Tag
with name <newName> under metalake <metalake> already exists"`.
### How was this patch tested?
Added a new unit test [testAlterTagRenameToExistingTag]
TestTagManager.java
`core/src/test/java/org/apache/gravitino/tag/TestTagManager.java` that
specifically covers this scenario:
1. Creates two tags.
2. Attempts to rename the first tag to the second's name.
3. Asserts that `TagAlreadyExistsException` is thrown with the correct
message.
4. Verifies that the state of both tags remains unchanged.
Verified the fix by running:
`./gradlew :core:test --tests "org.apache.gravitino.tag.TestTagManager"
-PskipITs`
---------
Co-authored-by: Jerry Shao <[email protected]>
---
.../java/org/apache/gravitino/tag/TagOperations.java | 1 +
.../org/apache/gravitino/client/GravitinoClient.java | 2 +-
.../org/apache/gravitino/client/GravitinoMetalake.java | 2 +-
.../client-python/gravitino/api/tag/tag_operations.py | 2 ++
.../client-python/gravitino/client/gravitino_client.py | 2 ++
.../gravitino/client/gravitino_metalake.py | 2 ++
.../org/apache/gravitino/hook/TagHookDispatcher.java | 4 +++-
.../apache/gravitino/listener/TagEventDispatcher.java | 4 +++-
.../java/org/apache/gravitino/tag/TagDispatcher.java | 3 +++
.../main/java/org/apache/gravitino/tag/TagManager.java | 12 ++++++++----
.../java/org/apache/gravitino/tag/TestTagManager.java | 18 ++++++++++++++++++
design-docs/gravitino-function-privilege.md | 1 +
12 files changed, 45 insertions(+), 8 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
b/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
index 1745efb236..c58ceeda8e 100644
--- a/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
+++ b/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
@@ -78,6 +78,7 @@ public interface TagOperations {
* @return The altered tag.
* @throws NoSuchTagException If the tag does not exist.
* @throws IllegalArgumentException If the changes cannot be applied to the
tag.
+ * @throws TagAlreadyExistsException If a tag with the new name already
exists.
*/
Tag alterTag(String name, TagChange... changes)
throws NoSuchTagException, IllegalArgumentException;
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
index d47c88882a..c7e19ba73c 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
@@ -565,7 +565,7 @@ public class GravitinoClient extends GravitinoClientBase
@Override
public Tag alterTag(String name, TagChange... changes)
- throws NoSuchTagException, IllegalArgumentException {
+ throws NoSuchTagException, IllegalArgumentException,
TagAlreadyExistsException {
return getMetalake().alterTag(name, changes);
}
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
index 1d3c9c2a23..2be2b88d92 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
@@ -537,7 +537,7 @@ public class GravitinoMetalake extends MetalakeDTO
*/
@Override
public Tag alterTag(String name, TagChange... changes)
- throws NoSuchTagException, IllegalArgumentException {
+ throws NoSuchTagException, IllegalArgumentException,
TagAlreadyExistsException {
Preconditions.checkArgument(StringUtils.isNotBlank(name), "tag name must
not be null or empty");
List<TagUpdateRequest> updates =
Arrays.stream(changes).map(DTOConverters::toTagUpdateRequest).collect(Collectors.toList());
diff --git a/clients/client-python/gravitino/api/tag/tag_operations.py
b/clients/client-python/gravitino/api/tag/tag_operations.py
index 58bbce2657..7089564705 100644
--- a/clients/client-python/gravitino/api/tag/tag_operations.py
+++ b/clients/client-python/gravitino/api/tag/tag_operations.py
@@ -114,6 +114,8 @@ class TagOperations(ABC):
Raises:
NoSuchTagException: If the tag does not exist.
NoSuchMetalakeException: If the metalake does not exist.
+ IllegalArgumentException: If the changes cannot be applied to the
tag.
+ TagAlreadyExistsException: If a tag with the new name already
exists.
"""
pass
diff --git a/clients/client-python/gravitino/client/gravitino_client.py
b/clients/client-python/gravitino/client/gravitino_client.py
index 862b4bce33..29c36a8937 100644
--- a/clients/client-python/gravitino/client/gravitino_client.py
+++ b/clients/client-python/gravitino/client/gravitino_client.py
@@ -312,6 +312,8 @@ class GravitinoClient(GravitinoClientBase, SupportsJobs,
TagOperations):
Raises:
NoSuchTagException: If the tag does not exist.
NoSuchMetalakeException: If the metalake does not exist.
+ IllegalArgumentException: If the changes cannot be applied to the
tag.
+ TagAlreadyExistsException: If a tag with the new name already
exists.
"""
return self.get_metalake().alter_tag(tag_name, *changes)
diff --git a/clients/client-python/gravitino/client/gravitino_metalake.py
b/clients/client-python/gravitino/client/gravitino_metalake.py
index 431bfdd23b..3bd3d2fff2 100644
--- a/clients/client-python/gravitino/client/gravitino_metalake.py
+++ b/clients/client-python/gravitino/client/gravitino_metalake.py
@@ -644,6 +644,8 @@ class GravitinoMetalake(
Raises:
NoSuchTagException: If the tag does not exist.
NoSuchMetalakeException: If the metalake does not exist.
+ IllegalArgumentException: If the changes cannot be applied to the
tag.
+ TagAlreadyExistsException: If a tag with the new name already
exists.
"""
Precondition.check_argument(
StringUtils.is_not_blank(tag_name),
diff --git
a/core/src/main/java/org/apache/gravitino/hook/TagHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/TagHookDispatcher.java
index 1db84fdff8..444f597fe4 100644
--- a/core/src/main/java/org/apache/gravitino/hook/TagHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/TagHookDispatcher.java
@@ -24,6 +24,7 @@ import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.authorization.Owner;
import org.apache.gravitino.authorization.OwnerDispatcher;
import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.exceptions.TagAlreadyExistsException;
import org.apache.gravitino.tag.Tag;
import org.apache.gravitino.tag.TagChange;
import org.apache.gravitino.tag.TagDispatcher;
@@ -73,7 +74,8 @@ public class TagHookDispatcher implements TagDispatcher {
}
@Override
- public Tag alterTag(String metalake, String name, TagChange... changes) {
+ public Tag alterTag(String metalake, String name, TagChange... changes)
+ throws IllegalArgumentException, TagAlreadyExistsException {
return dispatcher.alterTag(metalake, name, changes);
}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/TagEventDispatcher.java
b/core/src/main/java/org/apache/gravitino/listener/TagEventDispatcher.java
index 2dd81cde3f..8b06f10ddb 100644
--- a/core/src/main/java/org/apache/gravitino/listener/TagEventDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/listener/TagEventDispatcher.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.listener;
import java.util.Map;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.exceptions.TagAlreadyExistsException;
import org.apache.gravitino.listener.api.event.AlterTagEvent;
import org.apache.gravitino.listener.api.event.AlterTagFailureEvent;
import org.apache.gravitino.listener.api.event.AlterTagPreEvent;
@@ -141,7 +142,8 @@ public class TagEventDispatcher implements TagDispatcher {
}
@Override
- public Tag alterTag(String metalake, String name, TagChange... changes) {
+ public Tag alterTag(String metalake, String name, TagChange... changes)
+ throws IllegalArgumentException, TagAlreadyExistsException {
AlterTagPreEvent preEvent =
new AlterTagPreEvent(PrincipalUtils.getCurrentUserName(), metalake,
name, changes);
diff --git a/core/src/main/java/org/apache/gravitino/tag/TagDispatcher.java
b/core/src/main/java/org/apache/gravitino/tag/TagDispatcher.java
index 6e0276703d..e48113dfdf 100644
--- a/core/src/main/java/org/apache/gravitino/tag/TagDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/tag/TagDispatcher.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.tag;
import java.util.Map;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.exceptions.TagAlreadyExistsException;
/**
* {@code TagDispatcher} interface provides functionalities for managing tags
within a metalake. It
@@ -72,6 +73,8 @@ public interface TagDispatcher {
* @param name The name of the tag.
* @param changes The changes to apply to the tag.
* @return The updated tag.
+ * @throws IllegalArgumentException If the changes cannot be applied to the
tag.
+ * @throws TagAlreadyExistsException If a tag with the new name already
exists.
*/
Tag alterTag(String metalake, String name, TagChange... changes);
diff --git a/core/src/main/java/org/apache/gravitino/tag/TagManager.java
b/core/src/main/java/org/apache/gravitino/tag/TagManager.java
index 38c3f5bab7..eb26cf37e9 100644
--- a/core/src/main/java/org/apache/gravitino/tag/TagManager.java
+++ b/core/src/main/java/org/apache/gravitino/tag/TagManager.java
@@ -173,10 +173,14 @@ public class TagManager implements TagDispatcher {
throw new NoSuchTagException(
"Tag with name %s under metalake %s does not exist", name,
metalake);
} catch (EntityAlreadyExistsException e) {
- throw new RuntimeException(
- String.format(
- "Trying to alter tag %s under metalake %s, but the new
name already exists",
- name, metalake));
+ String newName =
+ Arrays.stream(changes)
+ .filter(c -> c instanceof TagChange.RenameTag)
+ .map(c -> ((TagChange.RenameTag) c).getNewName())
+ .findFirst()
+ .orElse(name);
+ throw new TagAlreadyExistsException(
+ e, "Tag with name %s under metalake %s already exists",
newName, metalake);
} catch (IOException ioe) {
LOG.error("Failed to alter tag {} under metalake {}", name,
metalake, ioe);
throw new RuntimeException(ioe);
diff --git a/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
b/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
index c1ca78e8cf..d7e86abba3 100644
--- a/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
+++ b/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
@@ -356,6 +356,24 @@ public class TestTagManager {
Assertions.assertEquals(expectedProp2, removedPropTag.properties());
}
+ @Test
+ public void testAlterTagRenameToExistingTag() {
+ tagManager.createTag(METALAKE, "tag1", null, null);
+ tagManager.createTag(METALAKE, "tag2", null, null);
+
+ TagAlreadyExistsException exception =
+ Assertions.assertThrows(
+ TagAlreadyExistsException.class,
+ () -> tagManager.alterTag(METALAKE, "tag1",
TagChange.rename("tag2")));
+
+ Assertions.assertEquals(
+ "Tag with name tag2 under metalake metalake_for_tag_test already
exists",
+ exception.getMessage());
+
+ Assertions.assertEquals("tag1", tagManager.getTag(METALAKE,
"tag1").name());
+ Assertions.assertEquals("tag2", tagManager.getTag(METALAKE,
"tag2").name());
+ }
+
@Test
public void testDeleteTag() {
tagManager.createTag(METALAKE, "tag1", null, null);
diff --git a/design-docs/gravitino-function-privilege.md
b/design-docs/gravitino-function-privilege.md
index c6c703b577..8ad3c01932 100644
--- a/design-docs/gravitino-function-privilege.md
+++ b/design-docs/gravitino-function-privilege.md
@@ -16,6 +16,7 @@
specific language governing permissions and limitations
under the License.
-->
+
# Design of Function Privilege Control in Gravitino
## Background