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

jshao pushed a commit to branch branch-1.0
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.0 by this push:
     new abb9a98537 [#7804][#7797][#8155][#8181][#8182][#7787] fix(cache): Fix 
metadata cache data consistency error (#8454)
abb9a98537 is described below

commit abb9a9853776c4626956c05a6b95e65e0edbf626
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Sep 6 14:19:11 2025 +0800

    [#7804][#7797][#8155][#8181][#8182][#7787] fix(cache): Fix metadata cache 
data consistency error (#8454)
    
    ### What changes were proposed in this pull request?
    
    This PR fixes metadata cache data consistency errors by implementing
    reverse indexing for cache invalidation and changing the default cache
    setting to disabled. The changes ensure that when cached entities are
    invalidated, all dependent cached relationships are properly cleaned up.
    
    Key changes:
    - Added reverse indexing mechanism to track entity relationships in
    cache
    - Refactored cache key hierarchy with new `EntityCacheRelationKey` class
    - Changed default cache setting from enabled to disabled
    
    ### Why are the changes needed?
    
    Fix: #7804
    Fix: #7797
    Fix: #8155
    Fix: #8181
    Fix: #8182
    Fix: #7787
    
    ### Does this PR introduce _any_ user-facing change?
    
    NA
    
    ### How was this patch tested?
    
    CI
    
    Co-authored-by: Xun <[email protected]>
    Co-authored-by: Eric Chang <[email protected]>
    Co-authored-by: yuqi <[email protected]>
    Co-authored-by: Jerry Shao <[email protected]>
---
 .../client/integration/test/PolicyIT.java          |   15 +-
 .../MetadataObjectRoleAuthorizationIT.java         |    4 +-
 .../gravitino/cache/TestCacheIndexCoherence.java   |   64 +-
 .../gravitino/cache/CaffeineEntityCache.java       |   99 +-
 .../org/apache/gravitino/cache/EntityCacheKey.java |   42 +-
 ...tyCacheKey.java => EntityCacheRelationKey.java} |   65 +-
 .../apache/gravitino/cache/ReverseIndexCache.java  |  110 ++
 .../apache/gravitino/cache/ReverseIndexRules.java  |  145 +++
 .../storage/relational/RelationalEntityStore.java  |    3 +-
 .../org/apache/gravitino/cache/TestCacheIndex.java |  179 ++--
 .../gravitino/cache/TestCaffeineEntityCache.java   | 1069 --------------------
 .../apache/gravitino/cache/TestEntityCacheKey.java |   49 +-
 .../gravitino/storage/TestEntityStorage.java       |   69 ++
 .../java/org/apache/gravitino/utils/TestUtil.java  |   13 +-
 14 files changed, 595 insertions(+), 1331 deletions(-)

diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
index 784a578823..e7cd6ed726 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
@@ -466,17 +466,16 @@ public class PolicyIT extends BaseIT {
     Assertions.assertTrue(policies7[0].enabled());
 
     // Test disable the policy then list again
-    // todo: uncomment after the bug is fixed
-    // https://github.com/apache/gravitino/issues/7787#issue-3255540700
-    //    Assertions.assertDoesNotThrow(() -> 
metalake.disablePolicy(policy1.name()));
-    //    Policy[] policies8 = 
relationalCatalog.supportsPolicies().listPolicyInfos();
-    //    Assertions.assertEquals(1, policies8.length);
-    //    Assertions.assertEquals(policy1.name(), policies8[0].name());
-    //    Assertions.assertFalse(policies8[0].enabled());
+    Assertions.assertDoesNotThrow(() -> 
metalake.disablePolicy(policy1.name()));
+    Policy[] policies8 = 
relationalCatalog.supportsPolicies().listPolicyInfos();
+    Assertions.assertEquals(1, policies8.length);
+    Assertions.assertEquals(policy1.name(), policies8[0].name());
+    Assertions.assertFalse(policies8[0].enabled());
+    Assertions.assertDoesNotThrow(() -> metalake.enablePolicy(policy1.name()));
 
     // Test get associated policy for catalog
     Policy policy = 
relationalCatalog.supportsPolicies().getPolicy(policy1.name());
-    Assertions.assertEquals(policy1, policy);
+    Assertions.assertEquals(policy1.enabled(), policy.enabled());
     Assertions.assertFalse(policy.inherited().get());
 
     // Test get non-existed policy for catalog
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/MetadataObjectRoleAuthorizationIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/MetadataObjectRoleAuthorizationIT.java
index 7e725ba3ca..21000208e9 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/MetadataObjectRoleAuthorizationIT.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/MetadataObjectRoleAuthorizationIT.java
@@ -38,7 +38,7 @@ public class MetadataObjectRoleAuthorizationIT extends 
BaseRestApiAuthorizationI
 
   private String hmsUri;
 
-  private static final String CATALOG = "CATALOG";
+  private static final String CATALOG = "catalog1";
 
   @BeforeAll
   @Override
@@ -63,6 +63,7 @@ public class MetadataObjectRoleAuthorizationIT extends 
BaseRestApiAuthorizationI
     Catalog catalog = gravitinoMetalake.loadCatalog(CATALOG);
     String[] roleNames = catalog.supportsRoles().listBindingRoleNames();
     assertArrayEquals(new String[] {}, roleNames);
+
     // create role1
     String role1 = "role1";
     gravitinoMetalake.createRole(
@@ -74,6 +75,7 @@ public class MetadataObjectRoleAuthorizationIT extends 
BaseRestApiAuthorizationI
     Catalog catalogLoadByNormalUser = 
normalUserClient.loadMetalake(METALAKE).loadCatalog(CATALOG);
     roleNames = catalogLoadByNormalUser.supportsRoles().listBindingRoleNames();
     assertArrayEquals(new String[] {role1}, roleNames);
+
     // create role2
     String role2 = "role2";
     gravitinoMetalake.createRole(
diff --git 
a/core/src/jcstress/java/org/apache/gravitino/cache/TestCacheIndexCoherence.java
 
b/core/src/jcstress/java/org/apache/gravitino/cache/TestCacheIndexCoherence.java
index ffeb04bcf8..d74f32b22c 100644
--- 
a/core/src/jcstress/java/org/apache/gravitino/cache/TestCacheIndexCoherence.java
+++ 
b/core/src/jcstress/java/org/apache/gravitino/cache/TestCacheIndexCoherence.java
@@ -78,13 +78,13 @@ public class TestCacheIndexCoherence {
           + "atomicity or thread-safety guarantees.")
   @State
   public static class InsertSameKeyCoherenceTest {
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
 
     private final NameIdentifier ident = NameIdentifier.of("metalake1", 
"catalog1", "schema1");
     private final Entity entity =
         getTestSchemaEntity(123L, "schema1", Namespace.of("metalake1", 
"catalog1"), "ident1");
-    private final EntityCacheKey key = EntityCacheKey.of(ident, entity.type());
+    private final EntityCacheRelationKey key = 
EntityCacheRelationKey.of(ident, entity.type());
     private final String keyStr = key.toString();
 
     @Actor
@@ -99,7 +99,7 @@ public class TestCacheIndexCoherence {
 
     @Arbiter
     public void arbiter(I_Result r) {
-      EntityCacheKey valueForExactKey = indexTree.getValueForExactKey(keyStr);
+      EntityCacheRelationKey valueForExactKey = 
indexTree.getValueForExactKey(keyStr);
       r.r1 =
           (valueForExactKey != null
                   && Objects.equals(valueForExactKey, key)
@@ -121,12 +121,12 @@ public class TestCacheIndexCoherence {
           + "forbidden result indicates incorrect key handling or data loss 
under concurrency.")
   @State
   public static class InsertSameKeyWithRelationTypeCoherenceTest {
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
 
     private final NameIdentifier ident = NameIdentifierUtil.ofRole("metalake", 
"role");
-    private final EntityCacheKey key =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key =
+        EntityCacheRelationKey.of(
             ident, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL);
     private final String keyStr = key.toString();
 
@@ -142,7 +142,7 @@ public class TestCacheIndexCoherence {
 
     @Arbiter
     public void arbiter(I_Result r) {
-      EntityCacheKey valueForExactKey = indexTree.getValueForExactKey(keyStr);
+      EntityCacheRelationKey valueForExactKey = 
indexTree.getValueForExactKey(keyStr);
       r.r1 =
           (valueForExactKey != null
                   && Objects.equals(valueForExactKey, key)
@@ -164,7 +164,7 @@ public class TestCacheIndexCoherence {
           + "indicates data loss or broken concurrency guarantees.")
   @State
   public static class InsertMultipleKeyCoherenceTest {
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
 
     private final NameIdentifier ident1 = NameIdentifier.of("metalake1", 
"catalog1", "schema1");
@@ -173,8 +173,8 @@ public class TestCacheIndexCoherence {
     private final NameIdentifier ident2 = NameIdentifier.of("metalake1", 
"catalog1", "schema2");
     private final Entity entity2 =
         getTestSchemaEntity(456L, "schema2", Namespace.of("metalake1", 
"catalog1"), "ident2");
-    private final EntityCacheKey key1 = EntityCacheKey.of(ident1, 
entity1.type());
-    private final EntityCacheKey key2 = EntityCacheKey.of(ident2, 
entity2.type());
+    private final EntityCacheRelationKey key1 = 
EntityCacheRelationKey.of(ident1, entity1.type());
+    private final EntityCacheRelationKey key2 = 
EntityCacheRelationKey.of(ident2, entity2.type());
     private final String key1Str = key1.toString();
     private final String key2Str = key2.toString();
 
@@ -190,8 +190,8 @@ public class TestCacheIndexCoherence {
 
     @Arbiter
     public void arbiter(I_Result r) {
-      EntityCacheKey valueForExactKey1 = 
indexTree.getValueForExactKey(key1Str);
-      EntityCacheKey valueForExactKey2 = 
indexTree.getValueForExactKey(key2Str);
+      EntityCacheRelationKey valueForExactKey1 = 
indexTree.getValueForExactKey(key1Str);
+      EntityCacheRelationKey valueForExactKey2 = 
indexTree.getValueForExactKey(key2Str);
       r.r1 =
           (valueForExactKey1 != null
                   && valueForExactKey2 != null
@@ -215,17 +215,17 @@ public class TestCacheIndexCoherence {
           + "both entries. A forbidden result indicates relation type is not 
correctly distinguished.")
   @State
   public static class InsertMultipleKeyWithRelationTypeCoherenceTest {
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
 
     private final NameIdentifier ident1 = 
NameIdentifierUtil.ofRole("metalake", "role1");
     private final NameIdentifier ident2 = 
NameIdentifierUtil.ofRole("metalake", "role1");
 
-    private final EntityCacheKey key1 =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key1 =
+        EntityCacheRelationKey.of(
             ident1, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL);
-    private final EntityCacheKey key2 =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key2 =
+        EntityCacheRelationKey.of(
             ident2, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
     private final String key1Str = key1.toString();
     private final String key2Str = key2.toString();
@@ -242,8 +242,8 @@ public class TestCacheIndexCoherence {
 
     @Arbiter
     public void arbiter(I_Result r) {
-      EntityCacheKey valueForExactKey1 = 
indexTree.getValueForExactKey(key1Str);
-      EntityCacheKey valueForExactKey2 = 
indexTree.getValueForExactKey(key2Str);
+      EntityCacheRelationKey valueForExactKey1 = 
indexTree.getValueForExactKey(key1Str);
+      EntityCacheRelationKey valueForExactKey2 = 
indexTree.getValueForExactKey(key2Str);
       r.r1 =
           (valueForExactKey1 != null
                   && valueForExactKey2 != null
@@ -309,12 +309,12 @@ public class TestCacheIndexCoherence {
   @State
   public static class PutRemoveSameKeyWithRelationTypeCoherenceTest {
 
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
 
     private final NameIdentifier ident = NameIdentifierUtil.ofRole("metalake", 
"role");
-    private final EntityCacheKey key =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key =
+        EntityCacheRelationKey.of(
             ident, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL);
     private final String keyStr = key.toString();
 
@@ -330,7 +330,7 @@ public class TestCacheIndexCoherence {
 
     @Arbiter
     public void arbiter(I_Result r) {
-      EntityCacheKey value = indexTree.getValueForExactKey(keyStr);
+      EntityCacheRelationKey value = indexTree.getValueForExactKey(keyStr);
       r.r1 = (value == null) ? 1 : 0;
     }
   }
@@ -416,17 +416,17 @@ public class TestCacheIndexCoherence {
   @State
   public static class PutAndPrefixScanWithRelationTypeCoherenceTest {
 
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
 
     private final NameIdentifier ident1 = 
NameIdentifierUtil.ofRole("metalake", "role1");
     private final NameIdentifier ident2 = 
NameIdentifierUtil.ofRole("metalake", "role2");
 
-    private final EntityCacheKey key1 =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key1 =
+        EntityCacheRelationKey.of(
             ident1, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL);
-    private final EntityCacheKey key2 =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key2 =
+        EntityCacheRelationKey.of(
             ident2, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
     private final String key1Str = key1.toString();
     private final String key2Str = key2.toString();
@@ -443,7 +443,7 @@ public class TestCacheIndexCoherence {
 
     @Arbiter
     public void arbiter(II_Result r) {
-      ImmutableList<EntityCacheKey> values =
+      ImmutableList<EntityCacheRelationKey> values =
           
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake"));
       r.r1 = values.contains(key1) ? 1 : 0;
       r.r2 = values.contains(key2) ? 1 : 0;
@@ -495,11 +495,11 @@ public class TestCacheIndexCoherence {
           + "Missing value is interesting but not forbidden, depending on 
execution timing.")
   @State
   public static class PutAndGetWithRelationTypeCoherenceTest {
-    private final RadixTree<EntityCacheKey> indexTree =
+    private final RadixTree<EntityCacheRelationKey> indexTree =
         new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
     private final NameIdentifier ident = NameIdentifierUtil.ofRole("metalake", 
"role");
-    private final EntityCacheKey key =
-        EntityCacheKey.of(
+    private final EntityCacheRelationKey key =
+        EntityCacheRelationKey.of(
             ident, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL);
     private final String keyStr = key.toString();
 
diff --git 
a/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java 
b/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
index c572427fd1..3faf543d30 100644
--- a/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
+++ b/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
@@ -30,10 +30,12 @@ import 
com.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
 import com.googlecode.concurrenttrees.radix.RadixTree;
 import 
com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
 import java.io.IOException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ExecutorService;
@@ -78,11 +80,14 @@ public class CaffeineEntityCache extends BaseEntityCache {
   private static final Logger LOG = 
LoggerFactory.getLogger(CaffeineEntityCache.class.getName());
   private final ReentrantLock opLock = new ReentrantLock();
 
-  /** Cache part */
-  private final Cache<EntityCacheKey, List<Entity>> cacheData;
+  /** Cache data structure. */
+  private final Cache<EntityCacheRelationKey, List<Entity>> cacheData;
 
-  /** Index part */
-  private RadixTree<EntityCacheKey> cacheIndex;
+  /** Cache reverse index structure. */
+  private ReverseIndexCache reverseIndex;
+
+  /** Cache Index structure. */
+  private RadixTree<EntityCacheRelationKey> cacheIndex;
 
   private ScheduledExecutorService scheduler;
 
@@ -94,6 +99,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
   public CaffeineEntityCache(Config cacheConfig) {
     super(cacheConfig);
     this.cacheIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());
+    this.reverseIndex = new ReverseIndexCache();
 
     Caffeine<EntityCacheKey, List<Entity>> cacheDataBuilder = 
newBaseBuilder(cacheConfig);
 
@@ -133,7 +139,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
     checkArguments(nameIdentifier, identType, relType);
 
     List<Entity> entitiesFromCache =
-        cacheData.getIfPresent(EntityCacheKey.of(nameIdentifier, identType, 
relType));
+        cacheData.getIfPresent(EntityCacheRelationKey.of(nameIdentifier, 
identType, relType));
     return 
Optional.ofNullable(entitiesFromCache).map(BaseEntityCache::convertEntities);
   }
 
@@ -143,7 +149,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
       NameIdentifier ident, Entity.EntityType type) {
     checkArguments(ident, type);
 
-    List<Entity> entitiesFromCache = 
cacheData.getIfPresent(EntityCacheKey.of(ident, type));
+    List<Entity> entitiesFromCache = 
cacheData.getIfPresent(EntityCacheRelationKey.of(ident, type));
 
     return Optional.ofNullable(entitiesFromCache)
         .filter(l -> !l.isEmpty())
@@ -156,7 +162,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
       NameIdentifier ident, Entity.EntityType type, 
SupportsRelationOperations.Type relType) {
     checkArguments(ident, type, relType);
 
-    return withLock(() -> invalidateEntities(ident));
+    return withLock(() -> invalidateEntities(ident, type, 
Optional.of(relType)));
   }
 
   /** {@inheritDoc} */
@@ -164,7 +170,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
   public boolean invalidate(NameIdentifier ident, Entity.EntityType type) {
     checkArguments(ident, type);
 
-    return withLock(() -> invalidateEntities(ident));
+    return withLock(() -> invalidateEntities(ident, type, Optional.empty()));
   }
 
   /** {@inheritDoc} */
@@ -172,14 +178,14 @@ public class CaffeineEntityCache extends BaseEntityCache {
   public boolean contains(
       NameIdentifier ident, Entity.EntityType type, 
SupportsRelationOperations.Type relType) {
     checkArguments(ident, type, relType);
-    return cacheData.getIfPresent(EntityCacheKey.of(ident, type, relType)) != 
null;
+    return cacheData.getIfPresent(EntityCacheRelationKey.of(ident, type, 
relType)) != null;
   }
 
   /** {@inheritDoc} */
   @Override
   public boolean contains(NameIdentifier ident, Entity.EntityType type) {
     checkArguments(ident, type);
-    return cacheData.getIfPresent(EntityCacheKey.of(ident, type)) != null;
+    return cacheData.getIfPresent(EntityCacheRelationKey.of(ident, type)) != 
null;
   }
 
   /** {@inheritDoc} */
@@ -194,7 +200,6 @@ public class CaffeineEntityCache extends BaseEntityCache {
     withLock(
         () -> {
           cacheData.invalidateAll();
-          cacheIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());
         });
   }
 
@@ -214,7 +219,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
           }
 
           syncEntitiesToCache(
-              EntityCacheKey.of(ident, type, relType),
+              EntityCacheRelationKey.of(ident, type, relType),
               entities.stream().map(e -> (Entity) 
e).collect(Collectors.toList()));
         });
   }
@@ -228,7 +233,8 @@ public class CaffeineEntityCache extends BaseEntityCache {
         () -> {
           invalidateOnKeyChange(entity);
           NameIdentifier identifier = getIdentFromEntity(entity);
-          EntityCacheKey entityCacheKey = EntityCacheKey.of(identifier, 
entity.type());
+          EntityCacheRelationKey entityCacheKey =
+              EntityCacheRelationKey.of(identifier, entity.type());
 
           syncEntitiesToCache(entityCacheKey, Lists.newArrayList(entity));
         });
@@ -272,6 +278,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
   protected void invalidateExpiredItem(EntityCacheKey key) {
     withLock(
         () -> {
+          reverseIndex.remove(key);
           cacheIndex.remove(key.toString());
         });
   }
@@ -283,7 +290,9 @@ public class CaffeineEntityCache extends BaseEntityCache {
    * @param key The key of the entities.
    * @param newEntities The new entities to sync to the cache.
    */
-  private void syncEntitiesToCache(EntityCacheKey key, List<Entity> 
newEntities) {
+  private void syncEntitiesToCache(EntityCacheRelationKey key, List<Entity> 
newEntities) {
+    if (key.relationType() != null) return;
+
     List<Entity> existingEntities = cacheData.getIfPresent(key);
 
     if (existingEntities != null && key.relationType() != null) {
@@ -294,6 +303,10 @@ public class CaffeineEntityCache extends BaseEntityCache {
 
     cacheData.put(key, newEntities);
 
+    for (Entity entity : newEntities) {
+      reverseIndex.indexEntity(entity, key);
+    }
+
     if (cacheData.policy().getIfPresentQuietly(key) != null) {
       cacheIndex.put(key.toString(), key);
     }
@@ -331,18 +344,62 @@ public class CaffeineEntityCache extends BaseEntityCache {
   }
 
   /**
-   * Invalidates the entities by the given cache key.
+   * Invalidate entities with iterative BFS algorithm.
    *
    * @param identifier The identifier of the entity to invalidate
    */
-  private boolean invalidateEntities(NameIdentifier identifier) {
-    List<EntityCacheKey> entityKeysToRemove =
-        
Lists.newArrayList(cacheIndex.getValuesForKeysStartingWith(identifier.toString()));
+  private boolean invalidateEntities(
+      NameIdentifier identifier,
+      Entity.EntityType type,
+      Optional<SupportsRelationOperations.Type> relTypeOpt) {
+    Queue<EntityCacheKey> queue = new ArrayDeque<>();
+
+    EntityCacheKey valueForExactKey =
+        cacheIndex.getValueForExactKey(
+            relTypeOpt.isEmpty()
+                ? EntityCacheKey.of(identifier, type).toString()
+                : EntityCacheRelationKey.of(identifier, type, 
relTypeOpt.get()).toString());
+
+    if (valueForExactKey == null) {
+      // No key to remove
+      return false;
+    }
 
-    cacheData.invalidateAll(entityKeysToRemove);
-    entityKeysToRemove.forEach(key -> cacheIndex.remove(key.toString()));
+    queue.offer(valueForExactKey);
+
+    while (!queue.isEmpty()) {
+      EntityCacheKey currentKeyToRemove = queue.poll();
+
+      cacheData.invalidate(currentKeyToRemove);
+      cacheIndex.remove(currentKeyToRemove.toString());
+
+      // Remove related entity keys
+      List<EntityCacheKey> relatedEntityKeysToRemove =
+          Lists.newArrayList(
+              
cacheIndex.getValuesForKeysStartingWith(currentKeyToRemove.identifier().toString()));
+      queue.addAll(relatedEntityKeysToRemove);
+
+      // Look up from reverse index to go to next depth
+      List<EntityCacheKey> reverseKeysToRemove =
+          Lists.newArrayList(
+              reverseIndex.getValuesForKeysStartingWith(
+                  currentKeyToRemove.identifier().toString()));
+      reverseKeysToRemove.forEach(
+          key -> {
+            // Remove from reverse index
+            // Convert EntityCacheRelationKey to EntityCacheKey
+            reverseIndex
+                .getKeysStartingWith(key.toString())
+                .forEach(
+                    reverseIndexKey -> {
+                      reverseIndex.remove(reverseIndexKey.toString());
+                    });
+          });
+
+      queue.addAll(reverseKeysToRemove);
+    }
 
-    return !entityKeysToRemove.isEmpty();
+    return true;
   }
 
   /**
diff --git a/core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java 
b/core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java
index c59dc68af0..229715e184 100644
--- a/core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java
+++ b/core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java
@@ -23,26 +23,11 @@ import com.google.common.base.Preconditions;
 import java.util.Objects;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.NameIdentifier;
-import org.apache.gravitino.SupportsRelationOperations;
 
 /** Key for Entity cache. */
 public class EntityCacheKey {
   private final NameIdentifier identifier;
   private final Entity.EntityType type;
-  private final SupportsRelationOperations.Type relationType;
-
-  /**
-   * Creates a new instance of {@link EntityCacheKey} with the given arguments.
-   *
-   * @param ident The identifier of the entity.
-   * @param type The type of the entity.
-   * @param relationType The type of the relation, it can be null.
-   * @return A new instance of {@link EntityCacheKey}.
-   */
-  public static EntityCacheKey of(
-      NameIdentifier ident, Entity.EntityType type, 
SupportsRelationOperations.Type relationType) {
-    return new EntityCacheKey(ident, type, relationType);
-  }
 
   /**
    * Creates a new instance of {@link EntityCacheKey} with the given arguments.
@@ -52,7 +37,7 @@ public class EntityCacheKey {
    * @return A new instance of {@link EntityCacheKey}.
    */
   public static EntityCacheKey of(NameIdentifier ident, Entity.EntityType 
type) {
-    return new EntityCacheKey(ident, type, null);
+    return new EntityCacheKey(ident, type);
   }
 
   /**
@@ -60,18 +45,13 @@ public class EntityCacheKey {
    *
    * @param identifier The identifier of the entity.
    * @param type The type of the entity.
-   * @param relationType The type of the relation.
    */
-  private EntityCacheKey(
-      NameIdentifier identifier,
-      Entity.EntityType type,
-      SupportsRelationOperations.Type relationType) {
+  EntityCacheKey(NameIdentifier identifier, Entity.EntityType type) {
     Preconditions.checkArgument(identifier != null, "identifier cannot be 
null");
     Preconditions.checkArgument(type != null, "type cannot be null");
 
     this.identifier = identifier;
     this.type = type;
-    this.relationType = relationType;
   }
 
   /**
@@ -92,15 +72,6 @@ public class EntityCacheKey {
     return type;
   }
 
-  /**
-   * Returns the type of the relation.
-   *
-   * @return The type of the relation.
-   */
-  public SupportsRelationOperations.Type relationType() {
-    return relationType;
-  }
-
   /**
    * Compares two instances of {@link EntityCacheKey} for equality. The 
comparison is done by
    * comparing the identifier, type, and relationType of the instances.
@@ -114,9 +85,7 @@ public class EntityCacheKey {
     if (!(obj instanceof EntityCacheKey)) return false;
     EntityCacheKey other = (EntityCacheKey) obj;
 
-    return Objects.equals(identifier, other.identifier)
-        && Objects.equals(type, other.type)
-        && Objects.equals(relationType, other.relationType);
+    return Objects.equals(identifier, other.identifier) && 
Objects.equals(type, other.type);
   }
 
   /**
@@ -127,7 +96,7 @@ public class EntityCacheKey {
    */
   @Override
   public int hashCode() {
-    return Objects.hash(identifier, type, relationType);
+    return Objects.hash(identifier, type);
   }
 
   /**
@@ -139,9 +108,6 @@ public class EntityCacheKey {
   @Override
   public String toString() {
     String stringExpr = identifier.toString() + ":" + type.toString();
-    if (relationType != null) {
-      stringExpr += ":" + relationType.name();
-    }
 
     return stringExpr;
   }
diff --git a/core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java 
b/core/src/main/java/org/apache/gravitino/cache/EntityCacheRelationKey.java
similarity index 60%
copy from core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java
copy to 
core/src/main/java/org/apache/gravitino/cache/EntityCacheRelationKey.java
index c59dc68af0..eac5950d4f 100644
--- a/core/src/main/java/org/apache/gravitino/cache/EntityCacheKey.java
+++ b/core/src/main/java/org/apache/gravitino/cache/EntityCacheRelationKey.java
@@ -19,79 +19,54 @@
 
 package org.apache.gravitino.cache;
 
-import com.google.common.base.Preconditions;
 import java.util.Objects;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.SupportsRelationOperations;
 
 /** Key for Entity cache. */
-public class EntityCacheKey {
-  private final NameIdentifier identifier;
-  private final Entity.EntityType type;
+public class EntityCacheRelationKey extends EntityCacheKey {
   private final SupportsRelationOperations.Type relationType;
 
   /**
-   * Creates a new instance of {@link EntityCacheKey} with the given arguments.
+   * Creates a new instance of {@link EntityCacheRelationKey} with the given 
arguments.
    *
    * @param ident The identifier of the entity.
    * @param type The type of the entity.
    * @param relationType The type of the relation, it can be null.
-   * @return A new instance of {@link EntityCacheKey}.
+   * @return A new instance of {@link EntityCacheRelationKey}.
    */
-  public static EntityCacheKey of(
+  public static EntityCacheRelationKey of(
       NameIdentifier ident, Entity.EntityType type, 
SupportsRelationOperations.Type relationType) {
-    return new EntityCacheKey(ident, type, relationType);
+    return new EntityCacheRelationKey(ident, type, relationType);
   }
 
   /**
-   * Creates a new instance of {@link EntityCacheKey} with the given arguments.
+   * Creates a new instance of {@link EntityCacheRelationKey} with the given 
arguments.
    *
    * @param ident The identifier of the entity.
    * @param type The type of the entity.
-   * @return A new instance of {@link EntityCacheKey}.
+   * @return A new instance of {@link EntityCacheRelationKey}.
    */
-  public static EntityCacheKey of(NameIdentifier ident, Entity.EntityType 
type) {
-    return new EntityCacheKey(ident, type, null);
+  public static EntityCacheRelationKey of(NameIdentifier ident, 
Entity.EntityType type) {
+    return new EntityCacheRelationKey(ident, type, null);
   }
 
   /**
-   * Creates a new instance of {@link EntityCacheKey} with the given 
parameters.
+   * Creates a new instance of {@link EntityCacheRelationKey} with the given 
parameters.
    *
    * @param identifier The identifier of the entity.
    * @param type The type of the entity.
    * @param relationType The type of the relation.
    */
-  private EntityCacheKey(
+  private EntityCacheRelationKey(
       NameIdentifier identifier,
       Entity.EntityType type,
       SupportsRelationOperations.Type relationType) {
-    Preconditions.checkArgument(identifier != null, "identifier cannot be 
null");
-    Preconditions.checkArgument(type != null, "type cannot be null");
-
-    this.identifier = identifier;
-    this.type = type;
+    super(identifier, type);
     this.relationType = relationType;
   }
 
-  /**
-   * Returns the identifier of the entity.
-   *
-   * @return The identifier of the entity.
-   */
-  public NameIdentifier identifier() {
-    return identifier;
-  }
-
-  /**
-   * Returns the type of the entity.
-   *
-   * @return The type of the entity.
-   */
-  public Entity.EntityType entityType() {
-    return type;
-  }
-
   /**
    * Returns the type of the relation.
    *
@@ -102,8 +77,8 @@ public class EntityCacheKey {
   }
 
   /**
-   * Compares two instances of {@link EntityCacheKey} for equality. The 
comparison is done by
-   * comparing the identifier, type, and relationType of the instances.
+   * Compares two instances of {@link EntityCacheRelationKey} for equality. 
The comparison is done
+   * by comparing the identifier, type, and relationType of the instances.
    *
    * @param obj The object to compare to.
    * @return {@code true} if the objects are equal, {@code false} otherwise.
@@ -111,12 +86,10 @@ public class EntityCacheKey {
   @Override
   public boolean equals(Object obj) {
     if (obj == this) return true;
-    if (!(obj instanceof EntityCacheKey)) return false;
-    EntityCacheKey other = (EntityCacheKey) obj;
+    if (!(obj instanceof EntityCacheRelationKey)) return false;
+    EntityCacheRelationKey other = (EntityCacheRelationKey) obj;
 
-    return Objects.equals(identifier, other.identifier)
-        && Objects.equals(type, other.type)
-        && Objects.equals(relationType, other.relationType);
+    return super.equals(obj) && Objects.equals(relationType, 
other.relationType);
   }
 
   /**
@@ -127,7 +100,7 @@ public class EntityCacheKey {
    */
   @Override
   public int hashCode() {
-    return Objects.hash(identifier, type, relationType);
+    return Objects.hash(super.identifier(), super.entityType(), relationType);
   }
 
   /**
@@ -138,7 +111,7 @@ public class EntityCacheKey {
    */
   @Override
   public String toString() {
-    String stringExpr = identifier.toString() + ":" + type.toString();
+    String stringExpr = super.identifier().toString() + ":" + 
super.entityType().toString();
     if (relationType != null) {
       stringExpr += ":" + relationType.name();
     }
diff --git 
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java 
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
new file mode 100644
index 0000000000..a18f05a5a1
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cache;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+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.List;
+import java.util.Map;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.UserEntity;
+
+/**
+ * Reverse index cache for managing entity relationships. This cache uses a 
radix tree to
+ * efficiently store and retrieve relationships between entities based on 
their keys.
+ */
+public class ReverseIndexCache {
+  private RadixTree<EntityCacheKey> reverseIndex;
+  /** Registers a reverse index processor for a specific entity class. */
+  private final Map<Class<? extends Entity>, ReverseIndexRule> 
reverseIndexRules = new HashMap<>();
+
+  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);
+  }
+
+  public boolean remove(EntityCacheKey key) {
+    return reverseIndex.remove(key.toString());
+  }
+
+  public Iterable<EntityCacheKey> getValuesForKeysStartingWith(String 
keyPrefix) {
+    return reverseIndex.getValuesForKeysStartingWith(keyPrefix);
+  }
+
+  public Iterable<CharSequence> getKeysStartingWith(String keyPrefix) {
+    return reverseIndex.getKeysStartingWith(keyPrefix);
+  }
+
+  public boolean remove(String key) {
+    return reverseIndex.remove(key);
+  }
+
+  public int size() {
+    return reverseIndex.size();
+  }
+
+  public void put(
+      NameIdentifier nameIdentifier, Entity.EntityType type, 
EntityCacheRelationKey key) {
+    EntityCacheKey entityCacheKey = EntityCacheKey.of(nameIdentifier, type);
+    String strEntityCacheKey = entityCacheKey.toString();
+    List<EntityCacheKey> entityKeys =
+        
Lists.newArrayList(reverseIndex.getValuesForKeysStartingWith(strEntityCacheKey));
+    String strEntityCacheKeySerialNumber =
+        String.format("%s-%d", strEntityCacheKey, entityKeys.size());
+    reverseIndex.put(strEntityCacheKeySerialNumber, key);
+  }
+
+  public void put(Entity entity, EntityCacheRelationKey key) {
+    Preconditions.checkArgument(entity != null, "EntityCacheRelationKey cannot 
be null");
+
+    if (entity instanceof HasIdentifier) {
+      NameIdentifier nameIdent = ((HasIdentifier) entity).nameIdentifier();
+      put(nameIdent, entity.type(), key);
+    }
+  }
+
+  public void registerReverseRule(Class<? extends Entity> entityClass, 
ReverseIndexRule rule) {
+    reverseIndexRules.put(entityClass, rule);
+  }
+
+  /** 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);
+    }
+  }
+
+  /** Functional interface for processing reverse index rules. */
+  @FunctionalInterface
+  interface ReverseIndexRule {
+    void indexEntity(Entity entity, EntityCacheRelationKey key, 
ReverseIndexCache cache);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java 
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
new file mode 100644
index 0000000000..d0839c5f6f
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cache;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.utils.NamespaceUtil;
+
+/**
+ * Reverse index rules for different entity types. This class defines how to 
process reverse
+ * indexing for UserEntity, GroupEntity, and RoleEntity. <br>
+ * For example: <br>
+ * - UserEntity role is 
{metalake-name}.system.user.{user-name}:USER-{serial-number} <br>
+ * - GroupEntity role is 
{metalake-name}.system.group.{group-name}:GROUP-{serial-number} <br>
+ * - RoleEntity role is 
{metalake-name}.system.role.{role-name}:ROLE-{serial-number} <br>
+ */
+public class ReverseIndexRules {
+
+  /** UserEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule USER_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        UserEntity userEntity = (UserEntity) entity;
+        if (userEntity.roleNames() != null) {
+          userEntity
+              .roleNames()
+              .forEach(
+                  role -> {
+                    Namespace ns = 
NamespaceUtil.ofRole(userEntity.namespace().level(0));
+                    NameIdentifier nameIdentifier = NameIdentifier.of(ns, 
role);
+                    reverseIndexCache.put(nameIdentifier, 
Entity.EntityType.ROLE, key);
+                  });
+        }
+      };
+
+  /** GroupEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule GROUP_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        GroupEntity groupEntity = (GroupEntity) entity;
+        if (groupEntity.roleNames() != null) {
+          groupEntity
+              .roleNames()
+              .forEach(
+                  role -> {
+                    Namespace ns = 
NamespaceUtil.ofRole(groupEntity.namespace().level(0));
+                    NameIdentifier nameIdentifier = NameIdentifier.of(ns, 
role);
+                    reverseIndexCache.put(nameIdentifier, 
Entity.EntityType.ROLE, key);
+                  });
+        }
+      };
+
+  /** * RoleEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule ROLE_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        RoleEntity roleEntity = (RoleEntity) entity;
+        if (roleEntity.securableObjects() != null) {
+          roleEntity
+              .securableObjects()
+              .forEach(
+                  securableObject -> {
+                    Namespace namespace = Namespace.empty();
+                    Entity.EntityType entityType = Entity.EntityType.METALAKE;
+                    switch (securableObject.type()) {
+                      case METALAKE:
+                        entityType = Entity.EntityType.METALAKE;
+                        namespace = NamespaceUtil.ofMetalake();
+                        break;
+                      case CATALOG:
+                        entityType = Entity.EntityType.CATALOG;
+                        namespace = 
NamespaceUtil.ofCatalog(roleEntity.namespace().level(0));
+                        break;
+                      case SCHEMA:
+                        entityType = Entity.EntityType.SCHEMA;
+                        Namespace nsSchema = 
Namespace.fromString(securableObject.parent());
+                        namespace =
+                            NamespaceUtil.ofSchema(
+                                roleEntity.namespace().level(0), 
nsSchema.level(0));
+                        break;
+                      case TABLE:
+                        entityType = Entity.EntityType.TABLE;
+                        Namespace nsTable = 
Namespace.fromString(securableObject.parent());
+                        namespace =
+                            NamespaceUtil.ofTable(
+                                roleEntity.namespace().level(0),
+                                nsTable.level(0),
+                                nsTable.level(1));
+                        break;
+                      case TOPIC:
+                        entityType = Entity.EntityType.TOPIC;
+                        Namespace nsTopic = 
Namespace.fromString(securableObject.parent());
+                        namespace =
+                            NamespaceUtil.ofTopic(
+                                roleEntity.namespace().level(0),
+                                nsTopic.level(0),
+                                nsTopic.level(1));
+                        break;
+                      case MODEL:
+                        entityType = Entity.EntityType.MODEL;
+                        Namespace nsModel = 
Namespace.fromString(securableObject.parent());
+                        namespace =
+                            NamespaceUtil.ofModel(
+                                roleEntity.namespace().level(0),
+                                nsModel.level(0),
+                                nsModel.level(1));
+                        break;
+                      case FILESET:
+                        entityType = Entity.EntityType.FILESET;
+                        Namespace nsFileset = 
Namespace.fromString(securableObject.parent());
+                        namespace =
+                            NamespaceUtil.ofFileset(
+                                roleEntity.namespace().level(0),
+                                nsFileset.level(0),
+                                nsFileset.level(1));
+                        break;
+                      default:
+                        throw new UnsupportedOperationException(
+                            "Don't support securable object type: " + 
securableObject.type());
+                    }
+                    Namespace securableObjectNamespace = 
Namespace.of(namespace.levels());
+                    NameIdentifier securableObjectIdent =
+                        NameIdentifier.of(securableObjectNamespace, 
securableObject.name());
+                    reverseIndexCache.put(securableObjectIdent, entityType, 
key);
+                  });
+        }
+      };
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
index 1a71fb0070..3968381135 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
@@ -216,8 +216,9 @@ public class RelationalEntityStore
             return entities.get();
           }
 
+          // Use allFields=true to cache complete entities
           List<E> backendEntities =
-              backend.listEntitiesByRelation(relType, nameIdentifier, 
identType, allFields);
+              backend.listEntitiesByRelation(relType, nameIdentifier, 
identType, true);
 
           cache.put(nameIdentifier, identType, relType, backendEntities);
 
diff --git a/core/src/test/java/org/apache/gravitino/cache/TestCacheIndex.java 
b/core/src/test/java/org/apache/gravitino/cache/TestCacheIndex.java
index d87995fdea..cea1f2867a 100644
--- a/core/src/test/java/org/apache/gravitino/cache/TestCacheIndex.java
+++ b/core/src/test/java/org/apache/gravitino/cache/TestCacheIndex.java
@@ -33,7 +33,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 public class TestCacheIndex {
-  private RadixTree<EntityCacheKey> indexTree;
+  private RadixTree<EntityCacheRelationKey> indexTree;
 
   private NameIdentifier ident1;
   private NameIdentifier ident2;
@@ -48,18 +48,18 @@ public class TestCacheIndex {
   private NameIdentifier ident11;
   private NameIdentifier ident12;
 
-  private EntityCacheKey key1;
-  private EntityCacheKey key2;
-  private EntityCacheKey key3;
-  private EntityCacheKey key4;
-  private EntityCacheKey key5;
-  private EntityCacheKey key6;
-  private EntityCacheKey key7;
-  private EntityCacheKey key8;
-  private EntityCacheKey key9;
-  private EntityCacheKey key10;
-  private EntityCacheKey key11;
-  private EntityCacheKey key12;
+  private EntityCacheRelationKey key1;
+  private EntityCacheRelationKey key2;
+  private EntityCacheRelationKey key3;
+  private EntityCacheRelationKey key4;
+  private EntityCacheRelationKey key5;
+  private EntityCacheRelationKey key6;
+  private EntityCacheRelationKey key7;
+  private EntityCacheRelationKey key8;
+  private EntityCacheRelationKey key9;
+  private EntityCacheRelationKey key10;
+  private EntityCacheRelationKey key11;
+  private EntityCacheRelationKey key12;
 
   @BeforeEach
   void setUp() {
@@ -80,24 +80,24 @@ public class TestCacheIndex {
     ident11 = NameIdentifierUtil.ofUser("metalake2", "user1");
     ident12 = NameIdentifierUtil.ofUser("metalake2", "user2");
 
-    key1 = EntityCacheKey.of(ident1, Entity.EntityType.SCHEMA);
-    key2 = EntityCacheKey.of(ident2, Entity.EntityType.SCHEMA);
-    key3 = EntityCacheKey.of(ident3, Entity.EntityType.TABLE);
-    key4 = EntityCacheKey.of(ident4, Entity.EntityType.TOPIC);
-    key5 = EntityCacheKey.of(ident5, Entity.EntityType.TABLE);
-    key6 = EntityCacheKey.of(ident6, Entity.EntityType.TABLE);
+    key1 = EntityCacheRelationKey.of(ident1, Entity.EntityType.SCHEMA);
+    key2 = EntityCacheRelationKey.of(ident2, Entity.EntityType.SCHEMA);
+    key3 = EntityCacheRelationKey.of(ident3, Entity.EntityType.TABLE);
+    key4 = EntityCacheRelationKey.of(ident4, Entity.EntityType.TOPIC);
+    key5 = EntityCacheRelationKey.of(ident5, Entity.EntityType.TABLE);
+    key6 = EntityCacheRelationKey.of(ident6, Entity.EntityType.TABLE);
 
     key7 =
-        EntityCacheKey.of(
+        EntityCacheRelationKey.of(
             ident7, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
     key8 =
-        EntityCacheKey.of(
+        EntityCacheRelationKey.of(
             ident8, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL);
 
-    key9 = EntityCacheKey.of(ident9, Entity.EntityType.GROUP);
-    key10 = EntityCacheKey.of(ident10, Entity.EntityType.GROUP);
-    key11 = EntityCacheKey.of(ident11, Entity.EntityType.USER);
-    key12 = EntityCacheKey.of(ident12, Entity.EntityType.USER);
+    key9 = EntityCacheRelationKey.of(ident9, Entity.EntityType.GROUP);
+    key10 = EntityCacheRelationKey.of(ident10, Entity.EntityType.GROUP);
+    key11 = EntityCacheRelationKey.of(ident11, Entity.EntityType.USER);
+    key12 = EntityCacheRelationKey.of(ident12, Entity.EntityType.USER);
 
     addIndex(indexTree, key12);
     addIndex(indexTree, key11);
@@ -120,107 +120,110 @@ public class TestCacheIndex {
 
   @Test
   void testGetFromByMetalakePrefix() {
-    List<EntityCacheKey> storeEntityCacheKeys =
+    List<EntityCacheRelationKey> storeEntityCacheRelationKeys =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake1"));
 
-    Assertions.assertEquals(8, storeEntityCacheKeys.size());
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key1));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key3));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key4));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key5));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key6));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key7));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key9));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key10));
-
-    List<EntityCacheKey> storeEntityCacheKeys2 =
+    Assertions.assertEquals(8, storeEntityCacheRelationKeys.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key1));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key3));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key4));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key5));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key6));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key7));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key9));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key10));
+
+    List<EntityCacheRelationKey> storeEntityCacheRelationKeys2 =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake2"));
 
-    Assertions.assertEquals(4, storeEntityCacheKeys2.size());
-    Assertions.assertTrue(storeEntityCacheKeys2.contains(key2));
-    Assertions.assertTrue(storeEntityCacheKeys2.contains(key8));
-    Assertions.assertTrue(storeEntityCacheKeys2.contains(key11));
-    Assertions.assertTrue(storeEntityCacheKeys2.contains(key12));
+    Assertions.assertEquals(4, storeEntityCacheRelationKeys2.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys2.contains(key2));
+    Assertions.assertTrue(storeEntityCacheRelationKeys2.contains(key8));
+    Assertions.assertTrue(storeEntityCacheRelationKeys2.contains(key11));
+    Assertions.assertTrue(storeEntityCacheRelationKeys2.contains(key12));
   }
 
   @Test
   void testGetByCatalogPrefix() {
-    List<EntityCacheKey> storeEntityCacheKeys =
+    List<EntityCacheRelationKey> storeEntityCacheRelationKeys =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake1.catalog1"));
 
-    Assertions.assertEquals(4, storeEntityCacheKeys.size());
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key1));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key3));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key4));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key6));
+    Assertions.assertEquals(4, storeEntityCacheRelationKeys.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key1));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key3));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key4));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key6));
 
-    storeEntityCacheKeys =
+    storeEntityCacheRelationKeys =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake1.catalog2"));
-    Assertions.assertEquals(1, storeEntityCacheKeys.size());
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key5));
+    Assertions.assertEquals(1, storeEntityCacheRelationKeys.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key5));
   }
 
   @Test
   void testGetBySchemaPrefix() {
-    List<EntityCacheKey> storeEntityCacheKeys =
+    List<EntityCacheRelationKey> storeEntityCacheRelationKeys =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake1.catalog1.schema1"));
 
-    Assertions.assertEquals(3, storeEntityCacheKeys.size());
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key1));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key3));
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key4));
+    Assertions.assertEquals(3, storeEntityCacheRelationKeys.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key1));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key3));
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key4));
 
-    storeEntityCacheKeys =
+    storeEntityCacheRelationKeys =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake1.catalog1.schema2"));
-    Assertions.assertEquals(1, storeEntityCacheKeys.size());
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key6));
+    Assertions.assertEquals(1, storeEntityCacheRelationKeys.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key6));
 
-    storeEntityCacheKeys =
+    storeEntityCacheRelationKeys =
         
ImmutableList.copyOf(indexTree.getValuesForKeysStartingWith("metalake1.catalog2.schema1"));
-    Assertions.assertEquals(1, storeEntityCacheKeys.size());
-    Assertions.assertTrue(storeEntityCacheKeys.contains(key5));
+    Assertions.assertEquals(1, storeEntityCacheRelationKeys.size());
+    Assertions.assertTrue(storeEntityCacheRelationKeys.contains(key5));
   }
 
   @Test
   void testGetByExactKey() {
-    EntityCacheKey storeEntityCacheKey = 
indexTree.getValueForExactKey(key1.toString());
-    Assertions.assertEquals(key1, storeEntityCacheKey);
+    EntityCacheRelationKey storeEntityCacheRelationKey =
+        indexTree.getValueForExactKey(key1.toString());
+    Assertions.assertEquals(key1, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key2.toString());
-    Assertions.assertEquals(key2, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key2.toString());
+    Assertions.assertEquals(key2, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key3.toString());
-    Assertions.assertEquals(key3, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key3.toString());
+    Assertions.assertEquals(key3, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key4.toString());
-    Assertions.assertEquals(key4, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key4.toString());
+    Assertions.assertEquals(key4, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key5.toString());
-    Assertions.assertEquals(key5, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key5.toString());
+    Assertions.assertEquals(key5, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key6.toString());
-    Assertions.assertEquals(key6, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key6.toString());
+    Assertions.assertEquals(key6, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key7.toString());
-    Assertions.assertEquals(key7, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key7.toString());
+    Assertions.assertEquals(key7, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key8.toString());
-    Assertions.assertEquals(key8, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key8.toString());
+    Assertions.assertEquals(key8, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key9.toString());
-    Assertions.assertEquals(key9, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key9.toString());
+    Assertions.assertEquals(key9, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key10.toString());
-    Assertions.assertEquals(key10, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key10.toString());
+    Assertions.assertEquals(key10, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key11.toString());
-    Assertions.assertEquals(key11, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key11.toString());
+    Assertions.assertEquals(key11, storeEntityCacheRelationKey);
 
-    storeEntityCacheKey = indexTree.getValueForExactKey(key12.toString());
-    Assertions.assertEquals(key12, storeEntityCacheKey);
+    storeEntityCacheRelationKey = 
indexTree.getValueForExactKey(key12.toString());
+    Assertions.assertEquals(key12, storeEntityCacheRelationKey);
   }
 
-  private void addIndex(RadixTree<EntityCacheKey> indexTree, EntityCacheKey 
storeEntityCacheKey) {
-    indexTree.put(storeEntityCacheKey.toString(), storeEntityCacheKey);
+  private void addIndex(
+      RadixTree<EntityCacheRelationKey> indexTree,
+      EntityCacheRelationKey storeEntityCacheRelationKey) {
+    indexTree.put(storeEntityCacheRelationKey.toString(), 
storeEntityCacheRelationKey);
   }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/cache/TestCaffeineEntityCache.java 
b/core/src/test/java/org/apache/gravitino/cache/TestCaffeineEntityCache.java
deleted file mode 100644
index 2b9e29146e..0000000000
--- a/core/src/test/java/org/apache/gravitino/cache/TestCaffeineEntityCache.java
+++ /dev/null
@@ -1,1069 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.gravitino.cache;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.awaitility.Awaitility.await;
-import static org.mockito.Mockito.mockStatic;
-
-import com.github.benmanes.caffeine.cache.Cache;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.reflect.FieldUtils;
-import org.apache.gravitino.Config;
-import org.apache.gravitino.Configs;
-import org.apache.gravitino.Entity;
-import org.apache.gravitino.Namespace;
-import org.apache.gravitino.SupportsRelationOperations;
-import org.apache.gravitino.meta.BaseMetalake;
-import org.apache.gravitino.meta.CatalogEntity;
-import org.apache.gravitino.meta.FilesetEntity;
-import org.apache.gravitino.meta.GroupEntity;
-import org.apache.gravitino.meta.ModelEntity;
-import org.apache.gravitino.meta.ModelVersionEntity;
-import org.apache.gravitino.meta.RoleEntity;
-import org.apache.gravitino.meta.SchemaEntity;
-import org.apache.gravitino.meta.TableEntity;
-import org.apache.gravitino.meta.TagEntity;
-import org.apache.gravitino.meta.TopicEntity;
-import org.apache.gravitino.meta.UserEntity;
-import org.apache.gravitino.model.ModelVersion;
-import org.apache.gravitino.utils.TestUtil;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.mockito.MockedStatic;
-
-@TestInstance(TestInstance.Lifecycle.PER_METHOD)
-public class TestCaffeineEntityCache {
-  // Test Entities.
-  private static SchemaEntity entity1;
-  private static SchemaEntity entity2;
-  private static TableEntity entity3;
-  private static TableEntity entity4;
-  private static TableEntity entity5;
-  private static CatalogEntity entity6;
-  private static BaseMetalake entity7;
-  private static UserEntity entity8;
-  private static UserEntity entity9;
-  private static GroupEntity entity10;
-  private static GroupEntity entity11;
-  private static RoleEntity entity12;
-  private static RoleEntity entity13;
-
-  private static Object getCacheDataFrom(EntityCache cache) {
-    try {
-      Object object = FieldUtils.readDeclaredField(cache, "cacheData", true);
-      if (object instanceof Cache) {
-        return object;
-      } else {
-        throw new RuntimeException("Unexpected cache data type: " + 
object.getClass());
-      }
-    } catch (IllegalAccessException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  @BeforeAll
-  static void initTestEntities() {
-    entity1 =
-        TestUtil.getTestSchemaEntity(
-            1L, "schema1", Namespace.of("metalake1", "catalog1"), 
"test_schema1");
-    entity2 =
-        TestUtil.getTestSchemaEntity(
-            2L, "schema2", Namespace.of("metalake2", "catalog2"), 
"test_schema2");
-    entity3 =
-        TestUtil.getTestTableEntity(3L, "table1", Namespace.of("metalake1", 
"catalog1", "schema1"));
-    entity4 =
-        TestUtil.getTestTableEntity(4L, "table2", Namespace.of("metalake1", 
"catalog2", "schema1"));
-    entity5 =
-        TestUtil.getTestTableEntity(5L, "table3", Namespace.of("metalake1", 
"catalog1", "schema2"));
-    entity6 =
-        TestUtil.getTestCatalogEntity(
-            6L, "catalog1", Namespace.of("metalake1"), "hive", "test_catalog");
-    entity7 = TestUtil.getTestMetalake(7L, "metalake1", "test_metalake1");
-
-    entity8 = TestUtil.getTestUserEntity(8L, "user1", "metalake1", 
ImmutableList.of(12L));
-    entity9 = TestUtil.getTestUserEntity(9L, "user2", "metalake1", 
ImmutableList.of(12L));
-
-    entity10 = TestUtil.getTestGroupEntity(10L, "group1", "metalake2", 
ImmutableList.of("role2"));
-    entity11 = TestUtil.getTestGroupEntity(11L, "group2", "metalake2", 
ImmutableList.of("role2"));
-
-    entity12 = TestUtil.getTestRoleEntity(12L, "role1", "metalake1");
-    entity13 = TestUtil.getTestRoleEntity(13L, "role2", "metalake2");
-  }
-
-  @Test
-  void testEnableStats() {
-    Config config = new Config() {};
-    config.set(Configs.CACHE_STATS_ENABLED, true);
-    EntityCache cache = new CaffeineEntityCache(config);
-
-    Assertions.assertDoesNotThrow(() -> cache.put(entity1));
-  }
-
-  @Test
-  void testPutAllTypeInCache() {
-    EntityCache cache = getNormalCache();
-
-    BaseMetalake testMetalake = TestUtil.getTestMetalake();
-    CatalogEntity testCatalogEntity = TestUtil.getTestCatalogEntity();
-    SchemaEntity testSchemaEntity = TestUtil.getTestSchemaEntity();
-    TableEntity testTableEntity = TestUtil.getTestTableEntity();
-    ModelEntity testModelEntity = TestUtil.getTestModelEntity();
-    FilesetEntity testFileSetEntity = TestUtil.getTestFileSetEntity();
-    TopicEntity testTopicEntity = TestUtil.getTestTopicEntity();
-    TagEntity testTagEntity = TestUtil.getTestTagEntity();
-    UserEntity testUserEntity = TestUtil.getTestUserEntity();
-    GroupEntity testGroupEntity = TestUtil.getTestGroupEntity();
-    RoleEntity testRoleEntity = TestUtil.getTestRoleEntity();
-    ModelVersionEntity testModelVersionEntity = 
TestUtil.getTestModelVersionEntity();
-
-    cache.put(testMetalake);
-    cache.put(testCatalogEntity);
-    cache.put(testSchemaEntity);
-    cache.put(testTableEntity);
-    cache.put(testModelEntity);
-    cache.put(testFileSetEntity);
-    cache.put(testTopicEntity);
-    cache.put(testTagEntity);
-    cache.put(testUserEntity);
-    cache.put(testGroupEntity);
-    cache.put(testRoleEntity);
-    cache.put(testModelVersionEntity);
-
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity));
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_USER_REL,
-        ImmutableList.of(testUserEntity));
-
-    Assertions.assertEquals(14, cache.size());
-    Assertions.assertTrue(
-        cache.getIfPresent(testMetalake.nameIdentifier(), 
Entity.EntityType.METALAKE).isPresent());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(testCatalogEntity.nameIdentifier(), 
Entity.EntityType.CATALOG)
-            .isPresent());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(testSchemaEntity.nameIdentifier(), 
Entity.EntityType.SCHEMA)
-            .isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testTableEntity.nameIdentifier(), 
Entity.EntityType.TABLE).isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testModelEntity.nameIdentifier(), 
Entity.EntityType.MODEL).isPresent());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(testFileSetEntity.nameIdentifier(), 
Entity.EntityType.FILESET)
-            .isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testTopicEntity.nameIdentifier(), 
Entity.EntityType.TOPIC).isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testTagEntity.nameIdentifier(), 
Entity.EntityType.TAG).isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testUserEntity.nameIdentifier(), 
Entity.EntityType.USER).isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testGroupEntity.nameIdentifier(), 
Entity.EntityType.GROUP).isPresent());
-    Assertions.assertTrue(
-        cache.getIfPresent(testRoleEntity.nameIdentifier(), 
Entity.EntityType.ROLE).isPresent());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(testModelVersionEntity.nameIdentifier(), 
Entity.EntityType.MODEL_VERSION)
-            .isPresent());
-
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                Entity.EntityType.ROLE)
-            .isPresent());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_USER_REL,
-                testRoleEntity.nameIdentifier(),
-                Entity.EntityType.ROLE)
-            .isPresent());
-  }
-
-  @Test
-  void testPutSameIdentifierEntities() {
-    EntityCache cache = getNormalCache();
-
-    UserEntity testUserEntity = TestUtil.getTestUserEntity();
-    TableEntity testTableEntity =
-        TestUtil.getTestTableEntity(
-            12L, "test_user", Namespace.of("test_metalake", "system", "user"));
-
-    cache.put(testUserEntity);
-    cache.put(testTableEntity);
-
-    Assertions.assertEquals(2, cache.size());
-    Assertions.assertTrue(
-        cache.contains(testTableEntity.nameIdentifier(), 
Entity.EntityType.TABLE));
-    Assertions.assertTrue(cache.contains(testUserEntity.nameIdentifier(), 
Entity.EntityType.USER));
-  }
-
-  @Test
-  void testPutAndGet() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(entity4);
-    cache.put(entity5);
-    cache.put(entity6);
-    cache.put(entity7);
-    cache.put(entity8);
-    cache.put(entity9);
-    cache.put(entity10);
-    cache.put(entity11);
-
-    cache.put(
-        entity12.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_USER_REL,
-        ImmutableList.of(entity8, entity9));
-    cache.put(
-        entity13.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(entity10, entity11));
-
-    Assertions.assertTrue(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-    Assertions.assertTrue(cache.contains(entity2.nameIdentifier(), 
entity2.type()));
-    Assertions.assertTrue(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-    Assertions.assertTrue(cache.contains(entity4.nameIdentifier(), 
entity4.type()));
-    Assertions.assertTrue(cache.contains(entity5.nameIdentifier(), 
entity5.type()));
-    Assertions.assertTrue(cache.contains(entity6.nameIdentifier(), 
entity6.type()));
-    Assertions.assertTrue(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-
-    Assertions.assertTrue(cache.contains(entity8.nameIdentifier(), 
entity8.type()));
-    Assertions.assertTrue(cache.contains(entity9.nameIdentifier(), 
entity9.type()));
-    Assertions.assertTrue(cache.contains(entity10.nameIdentifier(), 
entity10.type()));
-    Assertions.assertTrue(cache.contains(entity11.nameIdentifier(), 
entity11.type()));
-
-    Assertions.assertTrue(
-        cache.contains(
-            entity12.nameIdentifier(),
-            entity12.type(),
-            SupportsRelationOperations.Type.ROLE_USER_REL));
-    Assertions.assertTrue(
-        cache.contains(
-            entity13.nameIdentifier(),
-            entity13.type(),
-            SupportsRelationOperations.Type.ROLE_GROUP_REL));
-  }
-
-  @Test
-  void testGetIfPresent() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(
-        entity12.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_USER_REL,
-        ImmutableList.of(entity8, entity9));
-
-    Assertions.assertTrue(cache.getIfPresent(entity1.nameIdentifier(), 
entity1.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity2.nameIdentifier(), 
entity2.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity3.nameIdentifier(), 
entity3.type()).isPresent());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_USER_REL,
-                entity12.nameIdentifier(),
-                entity12.type())
-            .isPresent());
-    Assertions.assertEquals(cache.size(), 4);
-
-    Assertions.assertFalse(
-        cache.getIfPresent(entity4.nameIdentifier(), 
entity4.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity5.nameIdentifier(), 
entity5.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity6.nameIdentifier(), 
entity6.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity7.nameIdentifier(), 
entity7.type()).isPresent());
-  }
-
-  @Test
-  void testContains() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-
-    Assertions.assertTrue(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-    Assertions.assertTrue(cache.contains(entity2.nameIdentifier(), 
entity2.type()));
-    Assertions.assertTrue(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-    Assertions.assertFalse(cache.contains(entity4.nameIdentifier(), 
entity4.type()));
-    Assertions.assertFalse(cache.contains(entity5.nameIdentifier(), 
entity5.type()));
-    Assertions.assertFalse(cache.contains(entity6.nameIdentifier(), 
entity6.type()));
-    Assertions.assertFalse(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-  }
-
-  @Test
-  void testSize() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-
-    Assertions.assertEquals(3, cache.size());
-  }
-
-  @Test
-  void testClear() {
-    EntityCache cache = getNormalCache();
-    Assertions.assertDoesNotThrow(cache::clear);
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(entity4);
-    cache.put(entity5);
-    cache.put(entity6);
-    cache.put(entity7);
-    cache.put(
-        entity12.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_USER_REL,
-        ImmutableList.of(entity8, entity9));
-    cache.put(
-        entity13.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(entity10, entity11));
-
-    Assertions.assertEquals(9, cache.size());
-
-    cache.clear();
-
-    Assertions.assertEquals(0, cache.size());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity1.nameIdentifier(), 
entity1.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity2.nameIdentifier(), 
entity2.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity3.nameIdentifier(), 
entity3.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity4.nameIdentifier(), 
entity4.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity5.nameIdentifier(), 
entity5.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity6.nameIdentifier(), 
entity6.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity7.nameIdentifier(), 
entity7.type()).isPresent());
-    Assertions.assertFalse(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_USER_REL,
-                entity12.nameIdentifier(),
-                entity12.type())
-            .isPresent());
-    Assertions.assertFalse(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                entity13.nameIdentifier(),
-                entity13.type())
-            .isPresent());
-  }
-
-  @Test
-  void testInvalidateMetalake() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(entity4);
-    cache.put(entity5);
-    cache.put(entity6);
-    cache.put(entity7);
-    cache.put(entity8);
-    cache.put(entity9);
-    cache.put(entity10);
-    cache.put(entity11);
-
-    cache.put(
-        entity12.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_USER_REL,
-        ImmutableList.of(entity8, entity9));
-    cache.put(
-        entity13.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(entity10, entity11));
-
-    Assertions.assertEquals(13, cache.size());
-
-    cache.invalidate(entity7.nameIdentifier(), entity7.type());
-
-    Assertions.assertEquals(4, cache.size());
-    Assertions.assertTrue(cache.contains(entity10.nameIdentifier(), 
entity10.type()));
-    Assertions.assertTrue(cache.contains(entity11.nameIdentifier(), 
entity11.type()));
-    Assertions.assertTrue(
-        cache.contains(
-            entity13.nameIdentifier(),
-            entity13.type(),
-            SupportsRelationOperations.Type.ROLE_GROUP_REL));
-    Assertions.assertTrue(cache.getIfPresent(entity2.nameIdentifier(), 
entity2.type()).isPresent());
-
-    Assertions.assertFalse(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-    Assertions.assertFalse(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-    Assertions.assertFalse(cache.contains(entity4.nameIdentifier(), 
entity4.type()));
-    Assertions.assertFalse(cache.contains(entity5.nameIdentifier(), 
entity5.type()));
-    Assertions.assertFalse(cache.contains(entity6.nameIdentifier(), 
entity6.type()));
-    Assertions.assertFalse(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-  }
-
-  @Test
-  void testInvalidateCatalog() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(entity4);
-    cache.put(entity5);
-    cache.put(entity6);
-    cache.put(entity7);
-
-    Assertions.assertEquals(7, cache.size());
-    Assertions.assertTrue(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-    Assertions.assertTrue(cache.contains(entity2.nameIdentifier(), 
entity2.type()));
-    Assertions.assertTrue(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-    Assertions.assertTrue(cache.contains(entity4.nameIdentifier(), 
entity4.type()));
-    Assertions.assertTrue(cache.contains(entity5.nameIdentifier(), 
entity5.type()));
-    Assertions.assertTrue(cache.contains(entity6.nameIdentifier(), 
entity6.type()));
-    Assertions.assertTrue(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-
-    cache.invalidate(entity6.nameIdentifier(), entity6.type());
-    Assertions.assertEquals(3, cache.size());
-
-    Assertions.assertTrue(cache.getIfPresent(entity7.nameIdentifier(), 
entity7.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity4.nameIdentifier(), 
entity4.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity2.nameIdentifier(), 
entity2.type()).isPresent());
-
-    Assertions.assertFalse(
-        cache.getIfPresent(entity1.nameIdentifier(), 
entity1.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity3.nameIdentifier(), 
entity3.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity5.nameIdentifier(), 
entity5.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity6.nameIdentifier(), 
entity6.type()).isPresent());
-  }
-
-  @Test
-  void testInvalidateSchema() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(entity4);
-    cache.put(entity5);
-    cache.put(entity6);
-    cache.put(entity7);
-
-    Assertions.assertEquals(7, cache.size());
-    Assertions.assertTrue(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-    Assertions.assertTrue(cache.contains(entity2.nameIdentifier(), 
entity2.type()));
-    Assertions.assertTrue(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-    Assertions.assertTrue(cache.contains(entity4.nameIdentifier(), 
entity4.type()));
-    Assertions.assertTrue(cache.contains(entity5.nameIdentifier(), 
entity5.type()));
-    Assertions.assertTrue(cache.contains(entity6.nameIdentifier(), 
entity6.type()));
-    Assertions.assertTrue(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-
-    cache.invalidate(entity1.nameIdentifier(), entity1.type());
-
-    Assertions.assertEquals(5, cache.size());
-
-    Assertions.assertTrue(cache.getIfPresent(entity2.nameIdentifier(), 
entity2.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity4.nameIdentifier(), 
entity4.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity5.nameIdentifier(), 
entity5.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity6.nameIdentifier(), 
entity6.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity7.nameIdentifier(), 
entity7.type()).isPresent());
-
-    Assertions.assertFalse(
-        cache.getIfPresent(entity1.nameIdentifier(), 
entity1.type()).isPresent());
-    Assertions.assertFalse(
-        cache.getIfPresent(entity3.nameIdentifier(), 
entity3.type()).isPresent());
-  }
-
-  @Test
-  void testInvalidateTable() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-    cache.put(entity2);
-    cache.put(entity3);
-    cache.put(entity4);
-    cache.put(entity5);
-    cache.put(entity6);
-    cache.put(entity7);
-
-    Assertions.assertEquals(7, cache.size());
-    Assertions.assertTrue(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-    Assertions.assertTrue(cache.contains(entity2.nameIdentifier(), 
entity2.type()));
-    Assertions.assertTrue(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-    Assertions.assertTrue(cache.contains(entity4.nameIdentifier(), 
entity4.type()));
-    Assertions.assertTrue(cache.contains(entity5.nameIdentifier(), 
entity5.type()));
-    Assertions.assertTrue(cache.contains(entity6.nameIdentifier(), 
entity6.type()));
-    Assertions.assertTrue(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-
-    cache.invalidate(entity3.nameIdentifier(), entity3.type());
-
-    Assertions.assertEquals(6, cache.size());
-    Assertions.assertTrue(cache.getIfPresent(entity1.nameIdentifier(), 
entity1.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity2.nameIdentifier(), 
entity2.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity4.nameIdentifier(), 
entity4.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity5.nameIdentifier(), 
entity5.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity6.nameIdentifier(), 
entity6.type()).isPresent());
-    Assertions.assertTrue(cache.getIfPresent(entity7.nameIdentifier(), 
entity7.type()).isPresent());
-
-    Assertions.assertFalse(cache.contains(entity3.nameIdentifier(), 
entity3.type()));
-  }
-
-  @Test
-  void testPutRelationalEntitiesWithMerge() {
-    EntityCache cache = getNormalCache();
-    RoleEntity testRoleEntity = TestUtil.getTestRoleEntity();
-    GroupEntity testGroupEntity1 =
-        TestUtil.getTestGroupEntity(
-            20L, "group1", "test_metalake", 
ImmutableList.of(testRoleEntity.name()));
-    GroupEntity testGroupEntity2 =
-        TestUtil.getTestGroupEntity(
-            21L, "group1", "test_metalake", 
ImmutableList.of(testRoleEntity.name()));
-
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity1));
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity2));
-
-    Assertions.assertTrue(
-        cache.contains(
-            testRoleEntity.nameIdentifier(),
-            testRoleEntity.type(),
-            SupportsRelationOperations.Type.ROLE_GROUP_REL));
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                testRoleEntity.type())
-            .isPresent());
-    List<? extends Entity> entities =
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                testRoleEntity.type())
-            .get();
-    Assertions.assertEquals(2, entities.size());
-    Assertions.assertEquals(ImmutableList.of(testGroupEntity1, 
testGroupEntity2), entities);
-  }
-
-  @Test
-  void testInvalidateOnKeyChange() {
-    ModelEntity testModelEntity = TestUtil.getTestModelEntity();
-    ModelVersionEntity testModelVersionEntity =
-        TestUtil.getTestModelVersionEntity(
-            testModelEntity.nameIdentifier(),
-            1,
-            ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, "s3://test/path"),
-            ImmutableMap.of(),
-            "test model version",
-            ImmutableList.of("alias1", "alias2"));
-
-    EntityCache cache = getNormalCache();
-    cache.put(testModelEntity);
-    Assertions.assertEquals(1, cache.size());
-    Assertions.assertTrue(cache.contains(testModelEntity.nameIdentifier(), 
testModelEntity.type()));
-
-    cache.put(testModelVersionEntity);
-    Assertions.assertEquals(1, cache.size());
-    Assertions.assertFalse(
-        cache.contains(testModelEntity.nameIdentifier(), 
testModelEntity.type()));
-    Assertions.assertTrue(
-        cache.contains(testModelVersionEntity.nameIdentifier(), 
testModelVersionEntity.type()));
-  }
-
-  @Test
-  void testPutSameRelationalEntities() {
-    EntityCache cache = getNormalCache();
-    RoleEntity testRoleEntity = TestUtil.getTestRoleEntity();
-    GroupEntity testGroupEntity =
-        TestUtil.getTestGroupEntity(
-            20L, "group1", "test_metalake", 
ImmutableList.of(testRoleEntity.name()));
-
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity));
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity));
-
-    Assertions.assertEquals(1, cache.size());
-    Assertions.assertTrue(
-        cache.contains(
-            testRoleEntity.nameIdentifier(),
-            testRoleEntity.type(),
-            SupportsRelationOperations.Type.ROLE_GROUP_REL));
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                testRoleEntity.type())
-            .isPresent());
-
-    List<? extends Entity> entities =
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                testRoleEntity.type())
-            .get();
-    Assertions.assertEquals(1, entities.size());
-    Assertions.assertEquals(testGroupEntity, entities.get(0));
-  }
-
-  @Test
-  void testPutRelationalEntitiesWithEmptyList() {
-    EntityCache cache = getNormalCache();
-    RoleEntity testRoleEntity = TestUtil.getTestRoleEntity();
-
-    Assertions.assertDoesNotThrow(
-        () ->
-            cache.put(
-                testRoleEntity.nameIdentifier(),
-                Entity.EntityType.ROLE,
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                ImmutableList.of()));
-    Assertions.assertEquals(0, cache.size());
-  }
-
-  @Test
-  void testPutRelationalEntitiesWithDifferentOrderButDeduplicated() {
-    EntityCache cache = getNormalCache();
-    RoleEntity testRoleEntity = TestUtil.getTestRoleEntity();
-    GroupEntity testGroupEntity1 =
-        TestUtil.getTestGroupEntity(
-            20L, "group1", "test_metalake", 
ImmutableList.of(testRoleEntity.name()));
-    GroupEntity testGroupEntity2 =
-        TestUtil.getTestGroupEntity(
-            21L, "group1", "test_metalake", 
ImmutableList.of(testRoleEntity.name()));
-
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity1, testGroupEntity2));
-    cache.put(
-        testRoleEntity.nameIdentifier(),
-        Entity.EntityType.ROLE,
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(testGroupEntity2, testGroupEntity1));
-
-    Assertions.assertEquals(1, cache.size());
-    Assertions.assertTrue(
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                testRoleEntity.type())
-            .isPresent());
-
-    List<? extends Entity> entities =
-        cache
-            .getIfPresent(
-                SupportsRelationOperations.Type.ROLE_GROUP_REL,
-                testRoleEntity.nameIdentifier(),
-                testRoleEntity.type())
-            .get();
-
-    Assertions.assertEquals(
-        Sets.newHashSet(testGroupEntity1, testGroupEntity2), 
Sets.newHashSet(entities));
-  }
-
-  @Test
-  void testInvalidateRelationKeyAndRelatedEntities() {
-    EntityCache cache = getNormalCache();
-    RoleEntity role = TestUtil.getTestRoleEntity();
-    GroupEntity group = TestUtil.getTestGroupEntity();
-    UserEntity user = TestUtil.getTestUserEntity();
-
-    cache.put(
-        role.nameIdentifier(),
-        role.type(),
-        SupportsRelationOperations.Type.ROLE_GROUP_REL,
-        ImmutableList.of(group));
-    cache.put(
-        role.nameIdentifier(),
-        role.type(),
-        SupportsRelationOperations.Type.ROLE_USER_REL,
-        ImmutableList.of(user));
-    cache.put(role);
-
-    cache.invalidate(role.nameIdentifier(), role.type());
-
-    Assertions.assertFalse(
-        cache.contains(
-            role.nameIdentifier(), role.type(), 
SupportsRelationOperations.Type.ROLE_GROUP_REL));
-    Assertions.assertFalse(
-        cache.contains(
-            role.nameIdentifier(), role.type(), 
SupportsRelationOperations.Type.ROLE_USER_REL));
-    Assertions.assertFalse(cache.contains(role.nameIdentifier(), role.type()));
-  }
-
-  @Test
-  void testRemoveNonExistentEntity() {
-    EntityCache cache = getNormalCache();
-
-    cache.put(entity1);
-
-    Assertions.assertEquals(1, cache.size());
-    Assertions.assertTrue(cache.contains(entity1.nameIdentifier(), 
entity1.type()));
-
-    Assertions.assertDoesNotThrow(() -> 
cache.invalidate(entity2.nameIdentifier(), entity2.type()));
-    Assertions.assertFalse(cache.invalidate(entity2.nameIdentifier(), 
entity2.type()));
-
-    Assertions.assertDoesNotThrow(() -> 
cache.invalidate(entity7.nameIdentifier(), entity7.type()));
-    Assertions.assertFalse(cache.invalidate(entity7.nameIdentifier(), 
entity7.type()));
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  void testExpireByTime() {
-    long expireTime = 1_000L;
-
-    Config config = new Config() {};
-    config.set(Configs.CACHE_EXPIRATION_TIME, expireTime);
-    config.set(Configs.CACHE_WEIGHER_ENABLED, false);
-
-    EntityCache cache = new CaffeineEntityCache(config);
-    cache.put(entity1);
-
-    await()
-        .atMost(3_000, MILLISECONDS)
-        .pollInterval(100, MILLISECONDS)
-        .until(
-            () -> {
-              cache.put(entity2);
-              Cache<EntityCacheKey, List<Entity>> internalCache =
-                  (Cache<EntityCacheKey, List<Entity>>) 
getCacheDataFrom(cache);
-              internalCache.cleanUp();
-              return !cache.contains(entity1.nameIdentifier(), 
Entity.EntityType.SCHEMA);
-            });
-
-    Assertions.assertFalse(cache.contains(entity1.nameIdentifier(), 
Entity.EntityType.SCHEMA));
-    Assertions.assertTrue(cache.contains(entity2.nameIdentifier(), 
Entity.EntityType.SCHEMA));
-  }
-
-  @Test
-  void testExpireByWeightExceedMaxWeight() {
-    Config config = new Config() {};
-    config.set(Configs.CACHE_WEIGHER_ENABLED, true);
-
-    try (MockedStatic<EntityCacheWeigher> mockedStatic = 
mockStatic(EntityCacheWeigher.class)) {
-      mockedStatic.when(EntityCacheWeigher::getMaxWeight).thenReturn(75L);
-      mockedStatic.when(EntityCacheWeigher::getInstance).thenReturn(new 
EntityCacheWeigher());
-
-      EntityCache cache = new CaffeineEntityCache(config);
-      cache.put(entity7);
-
-      Cache<EntityCacheKey, List<Entity>> caffeineObject =
-          (Cache<EntityCacheKey, List<Entity>>) getCacheDataFrom(cache);
-
-      await()
-          .atMost(1, TimeUnit.SECONDS)
-          .pollInterval(50, TimeUnit.MILLISECONDS)
-          .until(
-              () -> {
-                caffeineObject.cleanUp();
-                return !cache.contains(entity7.nameIdentifier(), 
Entity.EntityType.METALAKE);
-              });
-
-      Assertions.assertEquals(0, cache.size());
-      Assertions.assertFalse(cache.contains(entity7.nameIdentifier(), 
Entity.EntityType.METALAKE));
-    }
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  void testExpireByWeight() throws InterruptedException {
-    try (MockedStatic<EntityCacheWeigher> entityCacheWeigherMocked =
-        mockStatic(EntityCacheWeigher.class)) {
-      
entityCacheWeigherMocked.when(EntityCacheWeigher::getMaxWeight).thenReturn(105L);
-      entityCacheWeigherMocked
-          .when(EntityCacheWeigher::getInstance)
-          .thenReturn(new EntityCacheWeigher());
-      Config config = new Config() {};
-      config.set(Configs.CACHE_WEIGHER_ENABLED, true);
-
-      EntityCache cache = new CaffeineEntityCache(config);
-      cache.put(entity7);
-      Assertions.assertEquals(1, cache.size());
-      Assertions.assertTrue(cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-
-      cache.put(entity1);
-      cache.put(entity2);
-      cache.getIfPresent(entity1.nameIdentifier(), entity1.type());
-      cache.getIfPresent(entity2.nameIdentifier(), entity2.type());
-
-      Cache<EntityCacheKey, List<Entity>> caffeineObject =
-          (Cache<EntityCacheKey, List<Entity>>) getCacheDataFrom(cache);
-      caffeineObject.cleanUp();
-      await()
-          .atMost(1500, MILLISECONDS)
-          .pollInterval(50, MILLISECONDS)
-          .until(() -> !cache.contains(entity7.nameIdentifier(), 
entity7.type()));
-    }
-  }
-
-  @Test
-  void testExpireBySize() {
-    Config config = new Config() {};
-    config.set(Configs.CACHE_WEIGHER_ENABLED, false);
-    config.set(Configs.CACHE_EXPIRATION_TIME, 0L);
-    config.set(Configs.CACHE_MAX_ENTRIES, 1);
-    EntityCache cache = new CaffeineEntityCache(config);
-
-    Cache<EntityCacheKey, List<Entity>> caffeineObject =
-        (Cache<EntityCacheKey, List<Entity>>) getCacheDataFrom(cache);
-
-    cache.put(entity1);
-    caffeineObject.cleanUp();
-    await()
-        .atMost(500, MILLISECONDS)
-        .until(() -> cache.contains(entity1.nameIdentifier(), 
Entity.EntityType.SCHEMA));
-    Assertions.assertEquals(1, cache.size());
-
-    cache.put(entity2);
-    caffeineObject.cleanUp();
-    await()
-        .atMost(500, MILLISECONDS)
-        .until(() -> cache.contains(entity2.nameIdentifier(), entity2.type()));
-    Assertions.assertEquals(1, cache.size());
-
-    cache.put(entity3);
-    caffeineObject.cleanUp();
-    await()
-        .atMost(500, MILLISECONDS)
-        .until(() -> cache.contains(entity3.nameIdentifier(), entity3.type()));
-    Assertions.assertEquals(1, cache.size());
-  }
-
-  @Test
-  void testWeightCalculation() {
-    int metalakeWeight =
-        EntityCacheWeigher.getInstance()
-            .weigh(
-                EntityCacheKey.of(entity7.nameIdentifier(), entity7.type()),
-                ImmutableList.of(entity7));
-    Assertions.assertEquals(100, metalakeWeight);
-
-    int catalogWeight =
-        EntityCacheWeigher.getInstance()
-            .weigh(
-                EntityCacheKey.of(entity6.nameIdentifier(), entity6.type()),
-                ImmutableList.of(entity6));
-    Assertions.assertEquals(75, catalogWeight);
-
-    int schemaWeight =
-        EntityCacheWeigher.getInstance()
-            .weigh(
-                EntityCacheKey.of(entity1.nameIdentifier(), entity1.type()),
-                ImmutableList.of(entity1));
-    Assertions.assertEquals(50, schemaWeight);
-
-    int tableWeight =
-        EntityCacheWeigher.getInstance()
-            .weigh(
-                EntityCacheKey.of(entity3.nameIdentifier(), entity3.type()),
-                ImmutableList.of(entity3));
-    Assertions.assertEquals(15, tableWeight);
-
-    int multiUserWeight =
-        EntityCacheWeigher.getInstance()
-            .weigh(
-                EntityCacheKey.of(
-                    entity12.nameIdentifier(),
-                    entity12.type(),
-                    SupportsRelationOperations.Type.ROLE_USER_REL),
-                ImmutableList.of(entity8, entity9));
-
-    Assertions.assertEquals(30, multiUserWeight);
-  }
-
-  @Test
-  void testGetIfPresentWithNull() {
-    EntityCache cache = getNormalCache();
-    cache.put(entity1);
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class, () -> cache.getIfPresent(null, 
Entity.EntityType.SCHEMA));
-    Assertions.assertThrows(
-        IllegalArgumentException.class, () -> 
cache.getIfPresent(entity1.nameIdentifier(), null));
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () -> cache.getIfPresent(null, entity12.nameIdentifier(), 
entity12.type()));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.getIfPresent(
-                SupportsRelationOperations.Type.ROLE_USER_REL, null, 
Entity.EntityType.ROLE));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.getIfPresent(
-                SupportsRelationOperations.Type.ROLE_USER_REL, 
entity12.nameIdentifier(), null));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.getIfPresent(
-                SupportsRelationOperations.Type.ROLE_USER_REL, 
entity12.nameIdentifier(), null));
-  }
-
-  @Test
-  void testContainsWithNull() {
-    EntityCache cache = getNormalCache();
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class, () -> cache.contains(null, 
Entity.EntityType.SCHEMA));
-    Assertions.assertThrows(
-        IllegalArgumentException.class, () -> 
cache.contains(entity7.nameIdentifier(), null));
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.contains(
-                null, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.contains(
-                entity12.nameIdentifier(), null, 
SupportsRelationOperations.Type.ROLE_USER_REL));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () -> cache.contains(entity12.nameIdentifier(), entity12.type(), 
null));
-  }
-
-  @Test
-  void testInvalidateWithNull() {
-    EntityCache cache = getNormalCache();
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class, () -> cache.invalidate(null, 
Entity.EntityType.CATALOG));
-    Assertions.assertThrows(
-        IllegalArgumentException.class, () -> 
cache.invalidate(entity7.nameIdentifier(), null));
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.invalidate(
-                null, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_USER_REL));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.invalidate(
-                entity12.nameIdentifier(), null, 
SupportsRelationOperations.Type.ROLE_USER_REL));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () -> cache.invalidate(entity12.nameIdentifier(), entity12.type(), 
null));
-  }
-
-  @Test
-  void testPutWithNull() {
-    EntityCache cache = getNormalCache();
-
-    Assertions.assertThrows(IllegalArgumentException.class, () -> 
cache.put(null));
-
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.put(
-                null,
-                Entity.EntityType.ROLE,
-                SupportsRelationOperations.Type.ROLE_USER_REL,
-                ImmutableList.of()));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.put(
-                entity12.nameIdentifier(),
-                null,
-                SupportsRelationOperations.Type.ROLE_USER_REL,
-                ImmutableList.of()));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () -> cache.put(entity12.nameIdentifier(), entity12.type(), null, 
ImmutableList.of()));
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            cache.put(
-                entity12.nameIdentifier(),
-                Entity.EntityType.ROLE,
-                SupportsRelationOperations.Type.ROLE_USER_REL,
-                null));
-  }
-
-  private EntityCache getNormalCache() {
-    Config config = new Config() {};
-    config.set(Configs.CACHE_EXPIRATION_TIME, 0L);
-    config.set(Configs.CACHE_WEIGHER_ENABLED, false);
-    config.set(Configs.CACHE_MAX_ENTRIES, 1000000);
-
-    return new CaffeineEntityCache(config);
-  }
-}
diff --git 
a/core/src/test/java/org/apache/gravitino/cache/TestEntityCacheKey.java 
b/core/src/test/java/org/apache/gravitino/cache/TestEntityCacheKey.java
index 3624db1f6b..6fbfd08f24 100644
--- a/core/src/test/java/org/apache/gravitino/cache/TestEntityCacheKey.java
+++ b/core/src/test/java/org/apache/gravitino/cache/TestEntityCacheKey.java
@@ -29,11 +29,11 @@ import org.junit.jupiter.api.Test;
 public class TestEntityCacheKey {
 
   @Test
-  void testCreateRelationEntityCacheKeyUsingStaticMethod() {
+  void testCreateRelationEntityCacheRelationKeyUsingStaticMethod() {
     NameIdentifier ident = NameIdentifierUtil.ofRole("metalake", "role1");
     // test Relation Entity
-    EntityCacheKey key =
-        EntityCacheKey.of(
+    EntityCacheRelationKey key =
+        EntityCacheRelationKey.of(
             ident, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
     Assertions.assertEquals("metalake.system.role.role1:ROLE:ROLE_GROUP_REL", 
key.toString());
     Assertions.assertEquals(
@@ -42,7 +42,7 @@ public class TestEntityCacheKey {
     Assertions.assertEquals(SupportsRelationOperations.Type.ROLE_GROUP_REL, 
key.relationType());
 
     // test Store Entity
-    EntityCacheKey key2 = EntityCacheKey.of(ident, Entity.EntityType.ROLE, 
null);
+    EntityCacheRelationKey key2 = EntityCacheRelationKey.of(ident, 
Entity.EntityType.ROLE, null);
     Assertions.assertEquals("metalake.system.role.role1:ROLE", 
key2.toString());
     Assertions.assertEquals(
         NameIdentifier.of("metalake", "system", "role", "role1"), 
key2.identifier());
@@ -51,22 +51,22 @@ public class TestEntityCacheKey {
   }
 
   @Test
-  void testCreateRelationEntityCacheKeyWithNullArguments() {
+  void testCreateRelationEntityCacheRelationKeyWithNullArguments() {
     NameIdentifier ident = NameIdentifierUtil.ofRole("metalake", "role1");
     Assertions.assertThrows(
         IllegalArgumentException.class,
         () -> {
-          EntityCacheKey.of(
+          EntityCacheRelationKey.of(
               null, Entity.EntityType.ROLE, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
         });
     Assertions.assertThrows(
         IllegalArgumentException.class,
         () -> {
-          EntityCacheKey.of(ident, null, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
+          EntityCacheRelationKey.of(ident, null, 
SupportsRelationOperations.Type.ROLE_GROUP_REL);
         });
     Assertions.assertDoesNotThrow(
         () -> {
-          EntityCacheKey.of(ident, Entity.EntityType.ROLE, null);
+          EntityCacheRelationKey.of(ident, Entity.EntityType.ROLE, null);
         });
   }
 
@@ -75,8 +75,9 @@ public class TestEntityCacheKey {
     NameIdentifier ident1 = NameIdentifier.of("ns", "db", "tbl");
     Entity.EntityType type = Entity.EntityType.TABLE;
 
-    EntityCacheKey key1 = EntityCacheKey.of(ident1, type);
-    EntityCacheKey key2 = EntityCacheKey.of(NameIdentifier.of("ns", "db", 
"tbl"), type);
+    EntityCacheRelationKey key1 = EntityCacheRelationKey.of(ident1, type);
+    EntityCacheRelationKey key2 =
+        EntityCacheRelationKey.of(NameIdentifier.of("ns", "db", "tbl"), type);
 
     Assertions.assertEquals(key1, key2, "Keys with same ident and type should 
be equal");
     Assertions.assertEquals(
@@ -89,8 +90,9 @@ public class TestEntityCacheKey {
     Entity.EntityType type = Entity.EntityType.TABLE;
     SupportsRelationOperations.Type relType = 
SupportsRelationOperations.Type.OWNER_REL;
 
-    EntityCacheKey key1 = EntityCacheKey.of(ident, type, relType);
-    EntityCacheKey key2 = EntityCacheKey.of(NameIdentifier.of("ns", "db", 
"tbl"), type, relType);
+    EntityCacheRelationKey key1 = EntityCacheRelationKey.of(ident, type, 
relType);
+    EntityCacheRelationKey key2 =
+        EntityCacheRelationKey.of(NameIdentifier.of("ns", "db", "tbl"), type, 
relType);
 
     Assertions.assertEquals(
         key1, key2, "Keys with same ident, type, and relationType should be 
equal");
@@ -100,10 +102,10 @@ public class TestEntityCacheKey {
 
   @Test
   public void testInequalityWithDifferentIdentifier() {
-    EntityCacheKey key1 =
-        EntityCacheKey.of(NameIdentifier.of("ns", "db", "tbl1"), 
Entity.EntityType.TABLE);
-    EntityCacheKey key2 =
-        EntityCacheKey.of(NameIdentifier.of("ns", "db", "tbl2"), 
Entity.EntityType.TABLE);
+    EntityCacheRelationKey key1 =
+        EntityCacheRelationKey.of(NameIdentifier.of("ns", "db", "tbl1"), 
Entity.EntityType.TABLE);
+    EntityCacheRelationKey key2 =
+        EntityCacheRelationKey.of(NameIdentifier.of("ns", "db", "tbl2"), 
Entity.EntityType.TABLE);
 
     Assertions.assertNotEquals(key1, key2, "Keys with different identifiers 
should not be equal");
   }
@@ -111,8 +113,8 @@ public class TestEntityCacheKey {
   @Test
   public void testInequalityWithDifferentEntityType() {
     NameIdentifier ident = NameIdentifier.of("ns", "db", "obj");
-    EntityCacheKey key1 = EntityCacheKey.of(ident, Entity.EntityType.TABLE);
-    EntityCacheKey key2 = EntityCacheKey.of(ident, Entity.EntityType.FILESET);
+    EntityCacheRelationKey key1 = EntityCacheRelationKey.of(ident, 
Entity.EntityType.TABLE);
+    EntityCacheRelationKey key2 = EntityCacheRelationKey.of(ident, 
Entity.EntityType.FILESET);
 
     Assertions.assertNotEquals(key1, key2, "Keys with different entity types 
should not be equal");
   }
@@ -122,9 +124,10 @@ public class TestEntityCacheKey {
     NameIdentifier ident = NameIdentifier.of("ns", "db", "obj");
     Entity.EntityType type = Entity.EntityType.TABLE;
 
-    EntityCacheKey key1 = EntityCacheKey.of(ident, type, 
SupportsRelationOperations.Type.OWNER_REL);
-    EntityCacheKey key2 =
-        EntityCacheKey.of(ident, type, 
SupportsRelationOperations.Type.ROLE_USER_REL);
+    EntityCacheRelationKey key1 =
+        EntityCacheRelationKey.of(ident, type, 
SupportsRelationOperations.Type.OWNER_REL);
+    EntityCacheRelationKey key2 =
+        EntityCacheRelationKey.of(ident, type, 
SupportsRelationOperations.Type.ROLE_USER_REL);
 
     Assertions.assertNotEquals(
         key1, key2, "Keys with different relation types should not be equal");
@@ -135,7 +138,7 @@ public class TestEntityCacheKey {
     NameIdentifier ident = NameIdentifierUtil.ofUser("metalake", "user1");
     Entity.EntityType type = Entity.EntityType.USER;
 
-    EntityCacheKey key = EntityCacheKey.of(ident, type);
+    EntityCacheRelationKey key = EntityCacheRelationKey.of(ident, type);
 
     Assertions.assertEquals("metalake.system.user.user1:USER", key.toString());
   }
@@ -146,7 +149,7 @@ public class TestEntityCacheKey {
     Entity.EntityType type = Entity.EntityType.USER;
     SupportsRelationOperations.Type relationType = 
SupportsRelationOperations.Type.ROLE_USER_REL;
 
-    EntityCacheKey key = EntityCacheKey.of(ident, type, relationType);
+    EntityCacheRelationKey key = EntityCacheRelationKey.of(ident, type, 
relationType);
 
     Assertions.assertEquals("metalake.system.user.user1:USER:ROLE_USER_REL", 
key.toString());
   }
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java 
b/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
index 389bb6cfc2..44d9134531 100644
--- a/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
+++ b/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
@@ -33,6 +33,7 @@ import static 
org.apache.gravitino.Configs.RELATIONAL_ENTITY_STORE;
 import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME;
 import static org.apache.gravitino.Configs.VERSION_RETENTION_COUNT;
 import static org.apache.gravitino.file.Fileset.LOCATION_NAME_UNKNOWN;
+import static 
org.apache.gravitino.storage.relational.TestJDBCBackend.createRoleEntity;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -65,6 +66,7 @@ import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.authorization.AuthorizationUtils;
 import org.apache.gravitino.authorization.Privileges;
+import org.apache.gravitino.authorization.Role;
 import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.authorization.SecurableObjects;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
@@ -98,6 +100,7 @@ import 
org.apache.gravitino.storage.relational.converters.MySQLExceptionConverte
 import 
org.apache.gravitino.storage.relational.converters.PostgreSQLExceptionConverter;
 import 
org.apache.gravitino.storage.relational.converters.SQLExceptionConverterFactory;
 import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
+import org.apache.gravitino.utils.NamespaceUtil;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -2572,4 +2575,70 @@ public class TestEntityStorage {
         listAllColumnWithEntityId(entityId, entityType);
     deleteResult.forEach(p -> Assertions.assertTrue(p.getRight().getRight() > 
0));
   }
+
+  @ParameterizedTest
+  @MethodSource("storageProvider")
+  void testInvalidRelationCache(String type) throws Exception {
+    Config config = Mockito.mock(Config.class);
+    init(type, config);
+
+    AuditInfo auditInfo =
+        
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+    try (EntityStore store = EntityStoreFactory.createEntityStore(config)) {
+      store.initialize(config);
+
+      BaseMetalake metalake =
+          createBaseMakeLake(RandomIdGenerator.INSTANCE.nextId(), "metalake", 
auditInfo);
+      store.put(metalake, false);
+
+      CatalogEntity catalog =
+          createCatalog(
+              RandomIdGenerator.INSTANCE.nextId(),
+              NamespaceUtil.ofCatalog("metalake"),
+              "catalog",
+              auditInfo);
+      store.put(catalog, false);
+
+      // Insert a role
+      RoleEntity role =
+          createRoleEntity(
+              RandomIdGenerator.INSTANCE.nextId(),
+              AuthorizationUtils.ofRoleNamespace("metalake"),
+              "role",
+              auditInfo,
+              "catalog");
+      store.put(role, false);
+
+      // Get a role
+      Role oldRole = store.get(role.nameIdentifier(), Entity.EntityType.ROLE, 
RoleEntity.class);
+
+      // Rename the catalog that the role is associated with
+      CatalogEntity updatedCatalog =
+          CatalogEntity.builder()
+              .withId(catalog.id())
+              .withNamespace(catalog.namespace())
+              .withName("newCatalogName")
+              .withAuditInfo(auditInfo)
+              .withComment(catalog.getComment())
+              .withProperties(catalog.getProperties())
+              .withType(catalog.getType())
+              .withProvider(catalog.getProvider())
+              .build();
+      store.update(
+          catalog.nameIdentifier(),
+          CatalogEntity.class,
+          Entity.EntityType.CATALOG,
+          e -> updatedCatalog);
+
+      // Now try to get the role again, it should reflect the updated catalog 
name
+      Role newRow = store.get(role.nameIdentifier(), Entity.EntityType.ROLE, 
RoleEntity.class);
+      Assertions.assertNotEquals(oldRole, newRow);
+      Assertions.assertNotEquals(oldRole.securableObjects(), 
newRow.securableObjects());
+      List<SecurableObject> securableObjects = newRow.securableObjects();
+      Assertions.assertEquals(1, securableObjects.size());
+      Assertions.assertEquals("newCatalogName", 
securableObjects.get(0).name());
+      destroy(type);
+    }
+  }
 }
diff --git a/core/src/test/java/org/apache/gravitino/utils/TestUtil.java 
b/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
index fc909dc6a1..59d2ff2d08 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
@@ -28,8 +28,10 @@ import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 import org.apache.gravitino.Catalog;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.AuthorizationUtils;
 import org.apache.gravitino.authorization.Privileges;
 import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.file.Fileset;
@@ -78,7 +80,7 @@ public class TestUtil {
    * @return The test {@link BaseMetalake} entity.
    */
   public static BaseMetalake getTestMetalake() {
-    return getTestMetalake(generator.nextId(), "test_metalake", "metalake 
entity test");
+    return getTestMetalake(generator.nextId(), "m1", "metalake entity test");
   }
 
   /**
@@ -107,7 +109,7 @@ public class TestUtil {
    */
   public static CatalogEntity getTestCatalogEntity() {
     return getTestCatalogEntity(
-        generator.nextId(), "test_catalog", Namespace.of("m1"), "hive", 
"catalog entity test");
+        generator.nextId(), "c2", Namespace.of("m1"), "hive", "catalog entity 
test");
   }
 
   /**
@@ -389,6 +391,7 @@ public class TestUtil {
   public static UserEntity getTestUserEntity(
       long id, String name, String metalake, List<Long> roles) {
     return UserEntity.builder()
+        .withNamespace(AuthorizationUtils.ofUserNamespace(metalake))
         .withId(id)
         .withName(name)
         .withNamespace(NamespaceUtil.ofUser(metalake))
@@ -421,7 +424,7 @@ public class TestUtil {
     return GroupEntity.builder()
         .withId(id)
         .withName(name)
-        .withNamespace(NamespaceUtil.ofGroup(metalake))
+        .withNamespace(AuthorizationUtils.ofGroupNamespace(metalake))
         .withAuditInfo(getTestAuditInfo())
         .withRoleNames(roles)
         .build();
@@ -449,7 +452,7 @@ public class TestUtil {
     return RoleEntity.builder()
         .withId(id)
         .withName(name)
-        .withNamespace(NamespaceUtil.ofRole(metalake))
+        .withNamespace(AuthorizationUtils.ofRoleNamespace(metalake))
         .withAuditInfo(getTestAuditInfo())
         .withSecurableObjects(ImmutableList.of(getMockSecurableObject()))
         .build();
@@ -462,6 +465,8 @@ public class TestUtil {
    */
   public static SecurableObject getMockSecurableObject() {
     SecurableObject mockObject = mock(SecurableObject.class);
+    when(mockObject.type()).thenReturn(MetadataObject.Type.CATALOG);
+    when(mockObject.name()).thenReturn("c1");
     when(mockObject.privileges())
         .thenReturn(
             ImmutableList.of(Privileges.CreateSchema.allow(), 
Privileges.CreateTable.allow()));

Reply via email to