This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 8ddb17436c [#8710] feat(core): Support cache entities in relation
operations. (#8712)
8ddb17436c is described below
commit 8ddb17436cfbaed7b46317da73471f489130a1c3
Author: Mini Yu <[email protected]>
AuthorDate: Thu Nov 20 23:56:45 2025 +0800
[#8710] feat(core): Support cache entities in relation operations. (#8712)
### What changes were proposed in this pull request?
Cache entities in relation operations.
### Why are the changes needed?
It's a big improvement.
Fix: #8710
### Does this PR introduce _any_ user-facing change?
N/A
### How was this patch tested?
UTs and ITs.
---
.../gravitino/cache/CaffeineEntityCache.java | 86 ++++++-
.../apache/gravitino/cache/ReverseIndexCache.java | 8 +
.../apache/gravitino/cache/ReverseIndexRules.java | 40 ++++
.../org/apache/gravitino/policy/PolicyManager.java | 2 +-
.../storage/relational/RelationalEntityStore.java | 39 +++-
.../java/org/apache/gravitino/tag/TagManager.java | 2 +-
.../gravitino/storage/TestEntityStorage.java | 247 +++++++++++++++++++++
7 files changed, 408 insertions(+), 16 deletions(-)
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 ceb1a3ef29..1ecc27dc4f 100644
--- a/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
+++ b/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
@@ -23,6 +23,7 @@ import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -43,12 +44,15 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Config;
import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.GenericEntity;
import org.apache.gravitino.meta.ModelVersionEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -90,6 +94,14 @@ public class CaffeineEntityCache extends BaseEntityCache {
private ScheduledExecutorService scheduler;
+ private static final Set<SupportsRelationOperations.Type> RELATION_TYPES =
+ Sets.newHashSet(
+ SupportsRelationOperations.Type.METADATA_OBJECT_ROLE_REL,
+ SupportsRelationOperations.Type.ROLE_USER_REL,
+ SupportsRelationOperations.Type.ROLE_GROUP_REL,
+ SupportsRelationOperations.Type.POLICY_METADATA_OBJECT_REL,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL);
+
/**
* Constructs a new {@link CaffeineEntityCache}.
*
@@ -133,6 +145,11 @@ public class CaffeineEntityCache extends BaseEntityCache {
}
}
+ @VisibleForTesting
+ public Cache<EntityCacheRelationKey, List<Entity>> getCacheData() {
+ return this.cacheData;
+ }
+
/** {@inheritDoc} */
@Override
public <E extends Entity & HasIdentifier> Optional<List<E>> getIfPresent(
@@ -167,7 +184,10 @@ public class CaffeineEntityCache extends BaseEntityCache {
return segmentedLock.withLock(
EntityCacheRelationKey.of(ident, type, relType),
- () -> invalidateEntities(ident, type, Optional.of(relType)));
+ () -> {
+ invalidateEntities(ident, type, Optional.of(relType));
+ return true;
+ });
}
/** {@inheritDoc} */
@@ -176,7 +196,47 @@ public class CaffeineEntityCache extends BaseEntityCache {
checkArguments(ident, type);
return segmentedLock.withLock(
EntityCacheRelationKey.of(ident, type),
- () -> invalidateEntities(ident, type, Optional.empty()));
+ () -> {
+ // Clear possible relation first, then clear the main entity cache.
+ // For example, if a tag has been updated, apart from invalidating
the relation:
+ // metadata_object_to_tag_rel, we also need to invalidate the
tag_to_metadata_object_rel.
+ // Assuming a tag "tag1" is related to a metadata object "catalog",
when "catalog" is
+ // renamed to `catalog_new`, we need to invalidate both relations to
avoid stale data.
+ // that is: before: tag1:TAG_METADATA_OBJECT_REL -> catalog,
+ // catalog:TAG_METADATA_OBJECT_REL -> tag1, after:
tag1:TAG_METADATA_OBJECT_REL -> null,
+ // catalog:TAG_METADATA_OBJECT_REL -> null.
+ RELATION_TYPES.forEach(
+ relType -> {
+ List<Entity> relatedEntities =
+ cacheData.getIfPresent(EntityCacheRelationKey.of(ident,
type, relType));
+ if (relatedEntities != null) {
+ relatedEntities.stream()
+ .filter(e -> StringUtils.isNotBlank(((HasIdentifier)
e).name()))
+ .forEach(
+ entity -> {
+ NameIdentifier identifier = ((HasIdentifier)
entity).nameIdentifier();
+ if (entity instanceof GenericEntity) {
+ String metalakeName = ident.namespace().level(0);
+ String[] names =
+ ArrayUtils.addFirst(
+ identifier.namespace().levels(),
metalakeName);
+ names = ArrayUtils.add(names, identifier.name());
+ identifier = NameIdentifier.of(names);
+ }
+
+ invalidateEntities(identifier, entity.type(),
Optional.of(relType));
+ });
+ }
+ });
+
+ RELATION_TYPES.forEach(
+ relType -> {
+ invalidateEntities(ident, type, Optional.of(relType));
+ });
+
+ invalidateEntities(ident, type, Optional.empty());
+ return true;
+ });
}
/** {@inheritDoc} */
@@ -222,10 +282,12 @@ public class CaffeineEntityCache extends BaseEntityCache {
segmentedLock.withLock(
entityCacheKey,
() -> {
- // We still need to cache the entities even if the list is empty, to
avoid cache
- // misses. Consider the scenario where a user queries for an
entity's relations and the
- // result is empty. If we don't cache this empty result, the next
query will still hit the
- // backend, this is not desired.
+ // Return directly if entities are empty. No need to put an empty
list to cache, we will
+ // use another PR to resolve the performance problem.
+ if (entities.isEmpty()) {
+ return;
+ }
+
syncEntitiesToCache(
entityCacheKey, entities.stream().map(e -> (Entity)
e).collect(Collectors.toList()));
});
@@ -296,15 +358,13 @@ public class CaffeineEntityCache extends BaseEntityCache {
}
/**
- * Syncs the entities to the cache, if entities is too big and can not put
to the cache, then it
- * will be removed from the cache and cacheIndex will not be updated.
+ * Syncs the entities to the cache, if entities are too big and cannot put
to the cache, then it
+ * will be removed from the cache, and cacheIndex will not be updated.
*
* @param key The key of the entities.
* @param newEntities The new entities to sync to the cache.
*/
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) {
@@ -377,11 +437,12 @@ public class CaffeineEntityCache extends BaseEntityCache {
// For example, we have stored a role entity in the cache and entity to
role mapping in the
// reverse index. This is: cache data: role identifier -> role entity,
reverse index:
// the securable object -> role. When we update the securable object, we
need to invalidate
- // the
- // role entity from the cache though the securable object is not in the
cache data.
+ // the role entity from the cache though the securable object is not in
the cache data.
valueForExactKey = EntityCacheRelationKey.of(identifier, type,
relTypeOpt.orElse(null));
}
+ // The visited set to avoid processing the same key multiple times and
thus causing infinite
+ // loop.
Set<EntityCacheKey> visited = Sets.newHashSet();
queue.offer(valueForExactKey);
while (!queue.isEmpty()) {
@@ -405,6 +466,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
Lists.newArrayList(
reverseIndex.getValuesForKeysStartingWith(
currentKeyToRemove.identifier().toString()));
+
reverseKeysToRemove.forEach(
key -> {
// Remove from reverse index
diff --git
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
index 57ca31c974..16c0c87532 100644
--- a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java
@@ -29,8 +29,11 @@ import java.util.Map;
import org.apache.gravitino.Entity;
import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.meta.GenericEntity;
import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.PolicyEntity;
import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.TagEntity;
import org.apache.gravitino.meta.UserEntity;
/**
@@ -48,6 +51,10 @@ public class ReverseIndexCache {
registerReverseRule(UserEntity.class, ReverseIndexRules.USER_REVERSE_RULE);
registerReverseRule(GroupEntity.class,
ReverseIndexRules.GROUP_REVERSE_RULE);
registerReverseRule(RoleEntity.class, ReverseIndexRules.ROLE_REVERSE_RULE);
+ registerReverseRule(PolicyEntity.class,
ReverseIndexRules.POLICY_REVERSE_RULE);
+ registerReverseRule(TagEntity.class, ReverseIndexRules.TAG_REVERSE_RULE);
+ registerReverseRule(
+ GenericEntity.class,
ReverseIndexRules.GENERIC_METADATA_OBJECT_REVERSE_RULE);
}
public boolean remove(EntityCacheKey key) {
@@ -73,6 +80,7 @@ public class ReverseIndexCache {
public void put(
NameIdentifier nameIdentifier, Entity.EntityType type,
EntityCacheRelationKey key) {
EntityCacheKey entityCacheKey = EntityCacheKey.of(nameIdentifier, type);
+
List<EntityCacheKey> existingKeys =
reverseIndex.getValueForExactKey(entityCacheKey.toString());
if (existingKeys == null) {
reverseIndex.put(entityCacheKey.toString(), List.of(key));
diff --git
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
index d0839c5f6f..f568320860 100644
--- a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
@@ -18,11 +18,16 @@
*/
package org.apache.gravitino.cache;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.gravitino.Entity;
+import org.apache.gravitino.Entity.EntityType;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
+import org.apache.gravitino.meta.GenericEntity;
import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.PolicyEntity;
import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.TagEntity;
import org.apache.gravitino.meta.UserEntity;
import org.apache.gravitino.utils.NamespaceUtil;
@@ -142,4 +147,39 @@ public class ReverseIndexRules {
});
}
};
+
+ // Keep policies/tags to objects reverse index for metadata objects, so the
key are objects and
+ // the values are policies/tags.
+ public static final ReverseIndexCache.ReverseIndexRule
GENERIC_METADATA_OBJECT_REVERSE_RULE =
+ (entity, key, reverseIndexCache) -> {
+ // Name in GenericEntity contains no metalake.
+ GenericEntity genericEntity = (GenericEntity) entity;
+ EntityType type = entity.type();
+ if (genericEntity.name() != null) {
+ String[] levels = genericEntity.name().split("\\.");
+ String metalakeName = key.identifier().namespace().levels()[0];
+ NameIdentifier objectNameIdentifier =
+ NameIdentifier.of(ArrayUtils.addFirst(levels, metalakeName));
+ reverseIndexCache.put(objectNameIdentifier, type, key);
+ }
+ };
+
+ // Keep objects to policies reverse index for policy objects, so the key are
policies and the
+ // values are objects.
+ public static final ReverseIndexCache.ReverseIndexRule POLICY_REVERSE_RULE =
+ (entity, key, reverseIndexCache) -> {
+ PolicyEntity policyEntity = (PolicyEntity) entity;
+ NameIdentifier nameIdentifier =
+ NameIdentifier.of(policyEntity.namespace(), policyEntity.name());
+ reverseIndexCache.put(nameIdentifier, Entity.EntityType.POLICY, key);
+ };
+
+ // Keep objects to tags reverse index for tag objects, so the key are tags
and the
+ // values are objects.
+ public static final ReverseIndexCache.ReverseIndexRule TAG_REVERSE_RULE =
+ (entity, key, reverseIndexCache) -> {
+ TagEntity tagEntity = (TagEntity) entity;
+ NameIdentifier nameIdentifier =
NameIdentifier.of(tagEntity.namespace(), tagEntity.name());
+ reverseIndexCache.put(nameIdentifier, Entity.EntityType.TAG, key);
+ };
}
diff --git a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
index 14deabbb48..fe39649b9d 100644
--- a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
+++ b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
@@ -387,7 +387,7 @@ public class PolicyManager implements PolicyDispatcher {
entityType,
policyIdent);
} catch (NoSuchEntityException e) {
- if (e.getMessage().contains("No such policy entity")) {
+ if (e.getMessage().contains("No such entity")) {
throw new NoSuchPolicyException(
e, "Policy %s does not exist for metadata object %s",
policyName, metadataObject);
} else {
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 790624bc12..57de273755 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
@@ -20,6 +20,7 @@ package org.apache.gravitino.storage.relational;
import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_STORE;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.List;
@@ -58,6 +59,11 @@ public class RelationalEntityStore implements EntityStore,
SupportsRelationOpera
private RelationalGarbageCollector garbageCollector;
private EntityCache cache;
+ @VisibleForTesting
+ public EntityCache getCache() {
+ return cache;
+ }
+
@Override
public void initialize(Config config) throws RuntimeException {
this.backend = createRelationalEntityBackend(config);
@@ -196,8 +202,37 @@ public class RelationalEntityStore implements EntityStore,
SupportsRelationOpera
Entity.EntityType srcType,
NameIdentifier destEntityIdent)
throws IOException, NoSuchEntityException {
- // todo: support cache
- return backend.getEntityByRelation(relType, srcIdentifier, srcType,
destEntityIdent);
+ return cache.withCacheLock(
+ EntityCacheRelationKey.of(srcIdentifier, srcType, relType),
+ () -> {
+ Optional<List<E>> entities = cache.getIfPresent(relType,
srcIdentifier, srcType);
+ if (entities.isPresent()) {
+ return entities.get().stream()
+ .filter(e -> e.nameIdentifier().equals(destEntityIdent))
+ .findFirst()
+ .orElseThrow(
+ () ->
+ new NoSuchEntityException(
+ "No such entity with ident: %s", destEntityIdent));
+ }
+
+ // Use allFields=true to cache complete entities
+ List<E> backendEntities =
+ backend.listEntitiesByRelation(relType, srcIdentifier, srcType,
true);
+
+ E r =
+ backendEntities.stream()
+ .filter(e -> e.nameIdentifier().equals(destEntityIdent))
+ .findFirst()
+ .orElseThrow(
+ () ->
+ new NoSuchEntityException(
+ "No such entity with ident: %s",
destEntityIdent));
+
+ cache.put(srcIdentifier, srcType, relType, backendEntities);
+
+ return r;
+ });
}
@Override
diff --git a/core/src/main/java/org/apache/gravitino/tag/TagManager.java
b/core/src/main/java/org/apache/gravitino/tag/TagManager.java
index 18d258c1ee..867e49d1c1 100644
--- a/core/src/main/java/org/apache/gravitino/tag/TagManager.java
+++ b/core/src/main/java/org/apache/gravitino/tag/TagManager.java
@@ -280,7 +280,7 @@ public class TagManager implements TagDispatcher {
entityType,
tagIdent);
} catch (NoSuchEntityException e) {
- if (e.getMessage().contains("No such tag entity")) {
+ if (e.getMessage().contains("No such entity")) {
throw new NoSuchTagException(
e, "Tag %s does not exist for metadata object %s", name,
metadataObject);
} else {
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 5ff904eb31..d9044f322f 100644
--- a/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
+++ b/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
@@ -36,6 +36,7 @@ import static
org.apache.gravitino.file.Fileset.LOCATION_NAME_UNKNOWN;
import static
org.apache.gravitino.storage.relational.TestJDBCBackend.createRoleEntity;
import static
org.apache.gravitino.storage.relational.TestJDBCBackend.createUserEntity;
+import com.github.benmanes.caffeine.cache.Cache;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -65,12 +66,15 @@ import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.Privilege.Condition;
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.cache.CaffeineEntityCache;
+import org.apache.gravitino.cache.EntityCacheRelationKey;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.exceptions.NonEmptyEntityException;
import org.apache.gravitino.file.Fileset;
@@ -81,6 +85,7 @@ import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.meta.ColumnEntity;
import org.apache.gravitino.meta.FilesetEntity;
+import org.apache.gravitino.meta.GenericEntity;
import org.apache.gravitino.meta.GenericTableEntity;
import org.apache.gravitino.meta.GroupEntity;
import org.apache.gravitino.meta.ModelEntity;
@@ -89,6 +94,7 @@ import org.apache.gravitino.meta.RoleEntity;
import org.apache.gravitino.meta.SchemaEntity;
import org.apache.gravitino.meta.SchemaVersion;
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;
@@ -103,6 +109,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.NameIdentifierUtil;
import org.apache.gravitino.utils.NamespaceUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.AfterEach;
@@ -2794,6 +2801,246 @@ public class TestEntityStorage {
}
}
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testTagRelationCache(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);
+
+ Namespace namespace = NameIdentifierUtil.ofTag("metalake",
"tag1").namespace();
+ TagEntity tag1 =
+ TagEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withNamespace(namespace)
+ .withName("tag1")
+ .withAuditInfo(auditInfo)
+ .withProperties(Collections.emptyMap())
+ .build();
+ CatalogEntity catalog =
+ createCatalog(
+ RandomIdGenerator.INSTANCE.nextId(),
+ NamespaceUtil.ofCatalog("metalake"),
+ "catalog",
+ auditInfo);
+
+ store.put(catalog, false);
+ store.put(tag1, false);
+
+ SupportsRelationOperations relationOperations =
(SupportsRelationOperations) store;
+
+ relationOperations.updateEntityRelations(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ catalog.nameIdentifier(),
+ EntityType.CATALOG,
+ new NameIdentifier[] {tag1.nameIdentifier()},
+ new NameIdentifier[] {});
+
+ // Now try to load the relation
+ List<TagEntity> tags =
+ relationOperations.listEntitiesByRelation(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ catalog.nameIdentifier(),
+ EntityType.CATALOG,
+ true);
+ Assertions.assertEquals(1, tags.size());
+ Assertions.assertEquals(tag1, tags.get(0));
+
+ // Check whether tags exists in entity store cache
+ RelationalEntityStore relationalEntityStore = (RelationalEntityStore)
store;
+ CaffeineEntityCache caffeineEntityCache =
+ (CaffeineEntityCache) relationalEntityStore.getCache();
+ Cache<EntityCacheRelationKey, List<Entity>> cache =
caffeineEntityCache.getCacheData();
+
+ List<Entity> cachedTags =
+ cache.get(
+ EntityCacheRelationKey.of(
+ catalog.nameIdentifier(),
+ EntityType.CATALOG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+
+ // Check cached tags is correct
+ Assertions.assertNotNull(cachedTags);
+ Assertions.assertEquals(1, cachedTags.size());
+ Assertions.assertEquals(tag1, cachedTags.get(0));
+
+ List<GenericEntity> genericEntities =
+ relationOperations.listEntitiesByRelation(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ tag1.nameIdentifier(),
+ EntityType.TAG,
+ true);
+ Assertions.assertEquals(1, genericEntities.size());
+ Assertions.assertEquals(catalog.id(), genericEntities.get(0).id());
+ Assertions.assertEquals(catalog.name(), genericEntities.get(0).name());
+
+ // Now we are going to alter the catalog
+ 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 load the relation again from cache, it should be empty.
+ List<Entity> cachedTagsAfterCatalogUpdate =
+ cache.get(
+ EntityCacheRelationKey.of(
+ catalog.nameIdentifier(),
+ EntityType.CATALOG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNull(cachedTagsAfterCatalogUpdate);
+
+ List<Entity> cachedTagsByTagAfterCatalogUpdate =
+ cache.get(
+ EntityCacheRelationKey.of(
+ tag1.nameIdentifier(),
+ EntityType.TAG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNull(cachedTagsByTagAfterCatalogUpdate);
+
+ // Load tags again, it should repopulate the cache
+ List<TagEntity> tagsAfterCatalogUpdate =
+ relationOperations.listEntitiesByRelation(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ updatedCatalog.nameIdentifier(),
+ EntityType.CATALOG,
+ true);
+ Assertions.assertEquals(1, tagsAfterCatalogUpdate.size());
+ Assertions.assertEquals(tag1, tagsAfterCatalogUpdate.get(0));
+
+ List<Entity> cachedTagsAfterReload =
+ cache.get(
+ EntityCacheRelationKey.of(
+ updatedCatalog.nameIdentifier(),
+ EntityType.CATALOG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNotNull(cachedTagsAfterReload);
+ Assertions.assertEquals(1, cachedTagsAfterReload.size());
+ Assertions.assertEquals(tag1, cachedTagsAfterReload.get(0));
+
+ List<GenericEntity> genericEntitiesAfterCatalogUpdate =
+ relationOperations.listEntitiesByRelation(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ tag1.nameIdentifier(),
+ EntityType.TAG,
+ true);
+ Assertions.assertEquals(1, genericEntitiesAfterCatalogUpdate.size());
+ Assertions.assertEquals(catalog.id(),
genericEntitiesAfterCatalogUpdate.get(0).id());
+ Assertions.assertEquals(
+ updatedCatalog.name(),
genericEntitiesAfterCatalogUpdate.get(0).name());
+
+ List<Entity> cachedTagsByTagAfterReload =
+ cache.get(
+ EntityCacheRelationKey.of(
+ tag1.nameIdentifier(),
+ EntityType.TAG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNotNull(cachedTagsByTagAfterReload);
+ Assertions.assertEquals(1, cachedTagsByTagAfterReload.size());
+ Assertions.assertEquals(
+ updatedCatalog.id(), ((GenericEntity)
cachedTagsByTagAfterReload.get(0)).id());
+ Assertions.assertEquals(
+ updatedCatalog.name(), ((GenericEntity)
cachedTagsByTagAfterReload.get(0)).name());
+
+ // Now try to alter the tag: rename tag1 -> tagChanged.
+ TagEntity updatedTag1 =
+ TagEntity.builder()
+ .withId(tag1.id())
+ .withNamespace(tag1.namespace())
+ .withName("tagChanged")
+ .withAuditInfo(auditInfo)
+ .withProperties(tag1.properties())
+ .build();
+ store.update(tag1.nameIdentifier(), TagEntity.class,
Entity.EntityType.TAG, e -> updatedTag1);
+
+ // Now try to load the relation again from cache, it should be empty.
+ List<Entity> cachedTagsAfterTagUpdate =
+ cache.get(
+ EntityCacheRelationKey.of(
+ updatedCatalog.nameIdentifier(),
+ EntityType.CATALOG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNull(cachedTagsAfterTagUpdate);
+
+ List<Entity> cachedEntitiesAfterTagUpdate =
+ cache.get(
+ EntityCacheRelationKey.of(
+ tag1.nameIdentifier(),
+ EntityType.TAG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNull(cachedEntitiesAfterTagUpdate);
+
+ List<TagEntity> tagsAfterTagUpdate =
+ relationOperations.listEntitiesByRelation(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ updatedCatalog.nameIdentifier(),
+ EntityType.CATALOG,
+ true);
+ Assertions.assertEquals(1, tagsAfterTagUpdate.size());
+ Assertions.assertEquals(updatedTag1, tagsAfterTagUpdate.get(0));
+
+ List<Entity> cachedTagsAfterTagReload =
+ cache.get(
+ EntityCacheRelationKey.of(
+ updatedCatalog.nameIdentifier(),
+ EntityType.CATALOG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNotNull(cachedTagsAfterTagReload);
+ Assertions.assertEquals(1, cachedTagsAfterTagReload.size());
+ Assertions.assertEquals(updatedTag1, cachedTagsAfterTagReload.get(0));
+
+ List<GenericEntity> genericEntitiesAfterTagUpdate =
+ relationOperations.listEntitiesByRelation(
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL,
+ updatedTag1.nameIdentifier(),
+ EntityType.TAG,
+ true);
+ Assertions.assertEquals(1, genericEntitiesAfterTagUpdate.size());
+ Assertions.assertEquals(catalog.id(),
genericEntitiesAfterTagUpdate.get(0).id());
+ Assertions.assertEquals(updatedCatalog.name(),
genericEntitiesAfterTagUpdate.get(0).name());
+ List<Entity> cachedTagsByTagAfterTagReload =
+ cache.get(
+ EntityCacheRelationKey.of(
+ updatedTag1.nameIdentifier(),
+ EntityType.TAG,
+ SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL),
+ k -> null);
+ Assertions.assertNotNull(cachedTagsByTagAfterTagReload);
+ Assertions.assertEquals(1, cachedTagsByTagAfterTagReload.size());
+ Assertions.assertEquals(
+ updatedCatalog.id(), ((GenericEntity)
cachedTagsByTagAfterTagReload.get(0)).id());
+ Assertions.assertEquals(
+ updatedCatalog.name(), ((GenericEntity)
cachedTagsByTagAfterTagReload.get(0)).name());
+ }
+ }
+
@ParameterizedTest
@MethodSource("storageProvider")
void testLanceTableCreateAndUpdate(String type) {