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

jshao 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 9f46fd73ac [#9204] fix(core): Fix can't clear cache when the user of a 
metadata object is deleted. (#9205)
9f46fd73ac is described below

commit 9f46fd73acc3e88d98a385f6f14ad2b2b6ed5d6e
Author: Mini Yu <[email protected]>
AuthorDate: Wed Nov 26 14:14:47 2025 +0800

    [#9204] fix(core): Fix can't clear cache when the user of a metadata object 
is deleted. (#9205)
    
    ### What changes were proposed in this pull request?
    
    Add the reverse index for owner and metadata objects.
    
    ### Why are the changes needed?
    
    Without this, we will get an out-of-date user information.
    
    Fix: #9204
    
    ### Does this PR introduce _any_ user-facing change?
    
    N/A
    
    ### How was this patch tested?
    
    ITs
---
 .../integration/test/authorization/OwnerIT.java    | 47 ++++++++++++++++++++++
 .../apache/gravitino/cache/ReverseIndexCache.java  | 30 +++++++++-----
 .../apache/gravitino/cache/ReverseIndexRules.java  | 33 ++++++++++++---
 3 files changed, 93 insertions(+), 17 deletions(-)

diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
index 280489f6db..0f59d46c8b 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
@@ -32,6 +32,7 @@ import org.apache.gravitino.authorization.Owner;
 import org.apache.gravitino.authorization.Privileges;
 import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.authorization.SecurableObjects;
+import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.client.GravitinoMetalake;
 import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
 import org.apache.gravitino.exceptions.NotFoundException;
@@ -413,4 +414,50 @@ public class OwnerIT extends BaseIT {
     // Cleanup
     client.dropMetalake(metalakeNameE, true);
   }
+
+  @Test
+  void testOwnerDrop() {
+    String metalakeNameE = RandomNameUtils.genRandomName("metalakeE");
+    String catalogNameE = RandomNameUtils.genRandomName("catalogE");
+    GravitinoMetalake metalake =
+        client.createMetalake(metalakeNameE, "metalake E comment", 
Collections.emptyMap());
+    metalake.createCatalog(
+        catalogNameE, Catalog.Type.FILESET, "hadoop", "comment", 
Collections.emptyMap());
+    // Create a user
+    String userName = "ownerUser";
+    metalake.addUser(userName);
+
+    // Now set the owner of catalog to the user
+    MetadataObject catalogObject =
+        MetadataObjects.of(Lists.newArrayList(catalogNameE), 
MetadataObject.Type.CATALOG);
+    metalake.setOwner(catalogObject, userName, Owner.Type.USER);
+    Owner owner = metalake.getOwner(catalogObject).get();
+    Assertions.assertEquals(userName, owner.name());
+
+    try (GravitinoClient ordinaryClient =
+        
GravitinoClient.builder(serverUri).withMetalake(metalakeNameE).build()) {
+      // Drop the user
+      ordinaryClient.removeUser(userName);
+      // The owner should be removed as well
+      Owner ownerAfterDrop = metalake.getOwner(catalogObject).orElse(null);
+      Assertions.assertNull(ownerAfterDrop);
+
+      // create a Group
+      String groupName = "ownerGroup";
+      metalake.addGroup(groupName);
+      // Now set the owner of catalog to the group
+      metalake.setOwner(catalogObject, groupName, Owner.Type.GROUP);
+      owner = metalake.getOwner(catalogObject).get();
+      Assertions.assertEquals(groupName, owner.name());
+
+      // Drop the group
+      ordinaryClient.removeGroup(groupName);
+      // The owner should be removed as well
+      ownerAfterDrop = metalake.getOwner(catalogObject).orElse(null);
+      Assertions.assertNull(ownerAfterDrop);
+    }
+    // Cleanup
+    client.disableMetalake(metalakeNameE);
+    client.dropMetalake(metalakeNameE, true);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java 
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
index 16870d0411..49cc91e8b8 100644
--- a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
@@ -19,12 +19,14 @@
 package org.apache.gravitino.cache;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
 import com.googlecode.concurrenttrees.radix.RadixTree;
 import 
com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import org.apache.commons.collections4.CollectionUtils;
@@ -43,9 +45,11 @@ import org.apache.gravitino.meta.UserEntity;
  * efficiently store and retrieve relationships between entities based on 
their keys.
  */
 public class ReverseIndexCache {
-  private RadixTree<List<EntityCacheKey>> reverseIndex;
+  private final RadixTree<List<EntityCacheKey>> reverseIndex;
+
   /** Registers a reverse index processor for a specific entity class. */
-  private final Map<Class<? extends Entity>, ReverseIndexRule> 
reverseIndexRules = new HashMap<>();
+  private final Multimap<Class<? extends Entity>, ReverseIndexRule> 
reverseIndexRules =
+      HashMultimap.create();
 
   /**
    * Map from data entity key to a list of entity cache relation keys. This is 
used for reverse
@@ -73,11 +77,13 @@ public class ReverseIndexCache {
   public ReverseIndexCache() {
     this.reverseIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());
 
-    registerReverseRule(UserEntity.class, ReverseIndexRules.USER_REVERSE_RULE);
-    registerReverseRule(GroupEntity.class, 
ReverseIndexRules.GROUP_REVERSE_RULE);
-    registerReverseRule(RoleEntity.class, ReverseIndexRules.ROLE_REVERSE_RULE);
-    registerReverseRule(PolicyEntity.class, 
ReverseIndexRules.POLICY_REVERSE_RULE);
-    registerReverseRule(TagEntity.class, ReverseIndexRules.TAG_REVERSE_RULE);
+    registerReverseRule(UserEntity.class, 
ReverseIndexRules.USER_ROLE_REVERSE_RULE);
+    registerReverseRule(UserEntity.class, 
ReverseIndexRules.USER_OWNERSHIP_REVERSE_RULE);
+    registerReverseRule(GroupEntity.class, 
ReverseIndexRules.GROUP_ROLE_REVERSE_RULE);
+    registerReverseRule(GroupEntity.class, 
ReverseIndexRules.GROUP_OWNERSHIP_REVERSE_RULE);
+    registerReverseRule(RoleEntity.class, 
ReverseIndexRules.ROLE_SECURABLE_OBJECT_REVERSE_RULE);
+    registerReverseRule(PolicyEntity.class, 
ReverseIndexRules.POLICY_SECURABLE_OBJECT_REVERSE_RULE);
+    registerReverseRule(TagEntity.class, 
ReverseIndexRules.TAG_SECURABLE_OBJECT_REVERSE_RULE);
     registerReverseRule(
         GenericEntity.class, 
ReverseIndexRules.GENERIC_METADATA_OBJECT_REVERSE_RULE);
   }
@@ -149,9 +155,11 @@ public class ReverseIndexCache {
 
   /** Processes an entity and updates the reverse index accordingly. */
   public void indexEntity(Entity entity, EntityCacheRelationKey key) {
-    ReverseIndexRule rule = reverseIndexRules.get(entity.getClass());
-    if (rule != null) {
-      rule.indexEntity(entity, key, this);
+    Collection<ReverseIndexRule> rules = 
reverseIndexRules.get(entity.getClass());
+    if (!rules.isEmpty()) {
+      for (ReverseIndexRule rule : rules) {
+        rule.indexEntity(entity, key, this);
+      }
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java 
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
index f568320860..ca7ad074a4 100644
--- a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
@@ -23,6 +23,7 @@ import org.apache.gravitino.Entity;
 import org.apache.gravitino.Entity.EntityType;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
 import org.apache.gravitino.meta.GenericEntity;
 import org.apache.gravitino.meta.GroupEntity;
 import org.apache.gravitino.meta.PolicyEntity;
@@ -42,7 +43,7 @@ import org.apache.gravitino.utils.NamespaceUtil;
 public class ReverseIndexRules {
 
   /** UserEntity reverse index processor */
-  public static final ReverseIndexCache.ReverseIndexRule USER_REVERSE_RULE =
+  public static final ReverseIndexCache.ReverseIndexRule 
USER_ROLE_REVERSE_RULE =
       (entity, key, reverseIndexCache) -> {
         UserEntity userEntity = (UserEntity) entity;
         if (userEntity.roleNames() != null) {
@@ -57,8 +58,18 @@ public class ReverseIndexRules {
         }
       };
 
+  public static final ReverseIndexCache.ReverseIndexRule 
USER_OWNERSHIP_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        UserEntity userEntity = (UserEntity) entity;
+        // Handle Securable Objects -> User reverse index, so the key type is 
User and the value
+        // type is securable Object.
+        if (key.relationType() == SupportsRelationOperations.Type.OWNER_REL) {
+          reverseIndexCache.put(userEntity.nameIdentifier(), EntityType.USER, 
key);
+        }
+      };
+
   /** GroupEntity reverse index processor */
-  public static final ReverseIndexCache.ReverseIndexRule GROUP_REVERSE_RULE =
+  public static final ReverseIndexCache.ReverseIndexRule 
GROUP_ROLE_REVERSE_RULE =
       (entity, key, reverseIndexCache) -> {
         GroupEntity groupEntity = (GroupEntity) entity;
         if (groupEntity.roleNames() != null) {
@@ -73,8 +84,18 @@ public class ReverseIndexRules {
         }
       };
 
-  /** * RoleEntity reverse index processor */
-  public static final ReverseIndexCache.ReverseIndexRule ROLE_REVERSE_RULE =
+  public static final ReverseIndexCache.ReverseIndexRule 
GROUP_OWNERSHIP_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        GroupEntity groupEntity = (GroupEntity) entity;
+        // Handle Securable Objects -> Group reverse index, so the key type is 
group and the value
+        // type is securable Object.
+        if (key.relationType() == SupportsRelationOperations.Type.OWNER_REL) {
+          reverseIndexCache.put(groupEntity.nameIdentifier(), 
EntityType.GROUP, key);
+        }
+      };
+
+  /** RoleEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule 
ROLE_SECURABLE_OBJECT_REVERSE_RULE =
       (entity, key, reverseIndexCache) -> {
         RoleEntity roleEntity = (RoleEntity) entity;
         if (roleEntity.securableObjects() != null) {
@@ -166,7 +187,7 @@ public class ReverseIndexRules {
 
   // Keep objects to policies reverse index for policy objects, so the key are 
policies and the
   // values are objects.
-  public static final ReverseIndexCache.ReverseIndexRule POLICY_REVERSE_RULE =
+  public static final ReverseIndexCache.ReverseIndexRule 
POLICY_SECURABLE_OBJECT_REVERSE_RULE =
       (entity, key, reverseIndexCache) -> {
         PolicyEntity policyEntity = (PolicyEntity) entity;
         NameIdentifier nameIdentifier =
@@ -176,7 +197,7 @@ public class ReverseIndexRules {
 
   // Keep objects to tags reverse index for tag objects, so the key are tags 
and the
   // values are objects.
-  public static final ReverseIndexCache.ReverseIndexRule TAG_REVERSE_RULE =
+  public static final ReverseIndexCache.ReverseIndexRule 
TAG_SECURABLE_OBJECT_REVERSE_RULE =
       (entity, key, reverseIndexCache) -> {
         TagEntity tagEntity = (TagEntity) entity;
         NameIdentifier nameIdentifier = 
NameIdentifier.of(tagEntity.namespace(), tagEntity.name());

Reply via email to