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 e85863b7bf [#7177] feat(core): Implement cache integration with
relational (#7354)
e85863b7bf is described below
commit e85863b7bf72922941fa2d5d88b0c22d546dc4fc
Author: Lord of Abyss <[email protected]>
AuthorDate: Sun Jun 15 01:25:41 2025 +0800
[#7177] feat(core): Implement cache integration with relational (#7354)
### What changes were proposed in this pull request?
Change:
- Updated EntityCache interface: `get` no longer delegates to underlying
store
- Enhanced RelationalEntityStore to support entity caching
- Revised multiple test cases to reflect caching behavior and validate
correctness
Tasks:
- [X] Implement the methods related to the `SupportsEntityStoreCache`
and `SupportsRelationEntityCache` interface in `CaffeineEntityCache`.
- [X] Implement cache integration with relational store and add basic
unit tests.
- [ ] Add unit tests for both index and cache.
- [ ] Use JCStress to perform multi-threaded testing on
`CaffeineEntityCache` and the `CacheIndex`.
### Why are the changes needed?
Fix: #7177
### Does this PR introduce _any_ user-facing change?
no
### How was this patch tested?
local test.
---
.../hadoop/TestHadoopCatalogOperations.java | 7 +
.../catalog/kafka/TestKafkaCatalogOperations.java | 7 +
.../catalog/model/TestModelCatalogOperations.java | 7 +
.../apache/gravitino/cache/BaseEntityCache.java | 67 +--
.../gravitino/cache/CaffeineEntityCache.java | 161 ++-----
.../org/apache/gravitino/cache/NoOpsCache.java | 156 +++++++
.../gravitino/cache/SupportsEntityStoreCache.java | 25 +-
.../cache/SupportsRelationEntityCache.java | 19 -
.../storage/relational/RelationalEntityStore.java | 49 +-
.../authorization/TestAccessControlManager.java | 7 +
.../gravitino/authorization/TestOwnerManager.java | 7 +
.../gravitino/cache/TestCaffeineEntityCache.java | 396 ++++++++++++++++
.../gravitino/storage/TestEntityStorage.java | 8 +
.../org/apache/gravitino/tag/TestTagManager.java | 9 +-
.../java/org/apache/gravitino/utils/TestUtil.java | 502 +++++++++++++++++++++
15 files changed, 1203 insertions(+), 224 deletions(-)
diff --git
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
index 2b47097985..4b1b936632 100644
---
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
+++
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
@@ -62,6 +62,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
import org.apache.gravitino.GravitinoEnv;
@@ -219,6 +220,12 @@ public class TestHadoopCatalogOperations {
when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L);
when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 1000L);
+ // Fix cache config for test
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
store = EntityStoreFactory.createEntityStore(config);
store.initialize(config);
diff --git
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
index d09a33a634..e820b8dd4d 100644
---
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
+++
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
@@ -51,6 +51,7 @@ import java.time.Instant;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
import org.apache.gravitino.NameIdentifier;
@@ -150,6 +151,12 @@ public class TestKafkaCatalogOperations extends
KafkaClusterEmbedded {
when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L);
when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 1000L);
+ // Fix cache config for test
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
// Mock
MetalakeMetaService metalakeMetaService =
MetalakeMetaService.getInstance();
diff --git
a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java
index 1d23622bfa..709999fd81 100644
---
a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java
+++
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java
@@ -49,6 +49,7 @@ import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
import org.apache.gravitino.NameIdentifier;
@@ -119,6 +120,12 @@ public class TestModelCatalogOperations {
when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L);
when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 1000L);
+ // Fix cache config for test
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
store = EntityStoreFactory.createEntityStore(config);
store.initialize(config);
diff --git a/core/src/main/java/org/apache/gravitino/cache/BaseEntityCache.java
b/core/src/main/java/org/apache/gravitino/cache/BaseEntityCache.java
index 37ceab2448..b61cc93454 100644
--- a/core/src/main/java/org/apache/gravitino/cache/BaseEntityCache.java
+++ b/core/src/main/java/org/apache/gravitino/cache/BaseEntityCache.java
@@ -20,28 +20,11 @@
package org.apache.gravitino.cache;
import com.google.common.base.Preconditions;
-import java.util.Collections;
-import java.util.EnumMap;
import java.util.List;
-import java.util.Map;
import org.apache.gravitino.Config;
import org.apache.gravitino.Entity;
-import org.apache.gravitino.EntityStore;
import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
-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.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.storage.relational.RelationalEntityStore;
/**
* An abstract class that provides a basic implementation for the {@link
EntityCache} interface.
@@ -49,57 +32,17 @@ import
org.apache.gravitino.storage.relational.RelationalEntityStore;
* other entity cache implementations.
*/
public abstract class BaseEntityCache implements EntityCache {
- private static final Map<Entity.EntityType, Class<?>> ENTITY_CLASS_MAP;
- // The entity store used by the cache, initialized through the constructor.
- protected final RelationalEntityStore entityStore;
protected final Config cacheConfig;
- static {
- Map<Entity.EntityType, Class<?>> map = new
EnumMap<>(Entity.EntityType.class);
- map.put(Entity.EntityType.METALAKE, BaseMetalake.class);
- map.put(Entity.EntityType.CATALOG, CatalogEntity.class);
- map.put(Entity.EntityType.SCHEMA, SchemaEntity.class);
- map.put(Entity.EntityType.TABLE, TableEntity.class);
- map.put(Entity.EntityType.FILESET, FilesetEntity.class);
- map.put(Entity.EntityType.MODEL, ModelEntity.class);
- map.put(Entity.EntityType.TOPIC, TopicEntity.class);
- map.put(Entity.EntityType.TAG, TagEntity.class);
- map.put(Entity.EntityType.MODEL_VERSION, ModelVersionEntity.class);
- map.put(Entity.EntityType.COLUMN, ColumnEntity.class);
- map.put(Entity.EntityType.USER, UserEntity.class);
- map.put(Entity.EntityType.GROUP, Entity.class);
- map.put(Entity.EntityType.ROLE, RoleEntity.class);
- ENTITY_CLASS_MAP = Collections.unmodifiableMap(map);
- }
-
/**
* Constructs a new {@link BaseEntityCache} instance.
*
- * @param entityStore The entity store to be used by the cache.
+ * @param config The cache configuration
*/
- public BaseEntityCache(Config config, EntityStore entityStore) {
+ public BaseEntityCache(Config config) {
Preconditions.checkArgument(config != null, "Config must not be null");
- Preconditions.checkArgument(entityStore != null, "EntityStore must not be
null");
this.cacheConfig = config;
- this.entityStore = (RelationalEntityStore) entityStore;
- }
-
- /**
- * Returns the class of the entity based on its type.
- *
- * @param type The entity type
- * @return The class of the entity
- * @throws IllegalArgumentException if the entity type is not supported
- */
- @SuppressWarnings("unchecked")
- public static <E extends Entity & HasIdentifier> Class<E>
getEntityClass(Entity.EntityType type) {
- Preconditions.checkArgument(type != null, "EntityType must not be null");
-
- Class<?> clazz = ENTITY_CLASS_MAP.get(type);
- Preconditions.checkArgument(clazz != null, "Unsupported EntityType: " +
type);
-
- return (Class<E>) clazz;
}
/**
@@ -108,7 +51,7 @@ public abstract class BaseEntityCache implements EntityCache
{
* @param entity The {@link Entity} instance.
* @return The {@link NameIdentifier} of the entity
*/
- protected static NameIdentifier getIdentFromEntity(Entity entity) {
+ public static NameIdentifier getIdentFromEntity(Entity entity) {
validateEntityHasIdentifier(entity);
HasIdentifier hasIdentifier = (HasIdentifier) entity;
@@ -133,7 +76,7 @@ public abstract class BaseEntityCache implements EntityCache
{
* @param <E> The type of the entities in the new list.
*/
@SuppressWarnings("unchecked")
- protected static <E extends Entity & HasIdentifier> List<E>
convertEntity(List<Entity> entities) {
+ public static <E extends Entity & HasIdentifier> List<E>
convertEntities(List<Entity> entities) {
entities.forEach(BaseEntityCache::validateEntityHasIdentifier);
return (List<E>) (List<? extends Entity>) entities;
@@ -147,7 +90,7 @@ public abstract class BaseEntityCache implements EntityCache
{
* @param <E> The type of the new entity.
*/
@SuppressWarnings("unchecked")
- protected static <E extends Entity & HasIdentifier> E convertEntity(Entity
entity) {
+ public static <E extends Entity & HasIdentifier> E convertEntity(Entity
entity) {
validateEntityHasIdentifier(entity);
return (E) entity;
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 c002bf64fe..317beff0d1 100644
--- a/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
+++ b/core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java
@@ -23,7 +23,6 @@ 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.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
@@ -32,6 +31,7 @@ import
com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFa
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
@@ -44,14 +44,14 @@ import java.util.stream.Collectors;
import org.apache.gravitino.Config;
import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
-import org.apache.gravitino.EntityStore;
import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.ModelVersionEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/** This class implements the {@link EntityCache} using Caffeine. */
+/** This class implements the {@link org.apache.gravitino.cache.EntityCache}
using Caffeine */
public class CaffeineEntityCache extends BaseEntityCache {
private static final int CACHE_CLEANUP_CORE_THREADS = 1;
private static final int CACHE_CLEANUP_MAX_THREADS = 1;
@@ -59,7 +59,6 @@ public class CaffeineEntityCache extends BaseEntityCache {
private static final int CACHE_MONITOR_PERIOD_MINUTES = 5;
private static final int CACHE_MONITOR_INITIAL_DELAY_MINUTES = 0;
private static final Logger LOG =
LoggerFactory.getLogger(CaffeineEntityCache.class.getName());
- private static volatile CaffeineEntityCache INSTANCE;
private final ReentrantLock opLock = new ReentrantLock();
/** Cache part */
@@ -70,38 +69,13 @@ public class CaffeineEntityCache extends BaseEntityCache {
private ScheduledExecutorService scheduler;
- @VisibleForTesting
- static void resetForTest() {
- INSTANCE = null;
- }
-
- /**
- * Returns the instance of {@link CaffeineEntityCache} based on the cache
configuration and entity
- * store.
- *
- * @param cacheConfig The cache configuration
- * @param entityStore The entity store to load entities from the database
- * @return The instance of {@link CaffeineEntityCache}
- */
- public static CaffeineEntityCache getInstance(Config cacheConfig,
EntityStore entityStore) {
- if (INSTANCE == null) {
- synchronized (CaffeineEntityCache.class) {
- if (INSTANCE == null) {
- INSTANCE = new CaffeineEntityCache(cacheConfig, entityStore);
- }
- }
- }
- return INSTANCE;
- }
-
/**
* Constructs a new {@link CaffeineEntityCache}.
*
* @param cacheConfig the cache configuration
- * @param entityStore The entity store to load entities from the database
*/
- private CaffeineEntityCache(Config cacheConfig, EntityStore entityStore) {
- super(cacheConfig, entityStore);
+ public CaffeineEntityCache(Config cacheConfig) {
+ super(cacheConfig);
this.cacheIndex = new ConcurrentRadixTree<>(new
DefaultCharArrayNodeFactory());
ThreadPoolExecutor cleanupExec = buildCleanupExecutor();
@@ -134,58 +108,6 @@ public class CaffeineEntityCache extends BaseEntityCache {
}
}
- /** {@inheritDoc} */
- @Override
- public <E extends Entity & HasIdentifier> List<E> getOrLoad(
- NameIdentifier ident, Entity.EntityType type,
SupportsRelationOperations.Type relType)
- throws IOException {
- checkArguments(ident, type, relType);
-
- // Executes a thread-safe operation: attempts to load the entity from the
cache,
- // falls back to the underlying store if not present, and updates the
cache.
- // If an error occurs during loading, the exception will be propagated.
- return withLockAndThrow(
- () -> {
- EntityCacheKey entityCacheKey = EntityCacheKey.of(ident, type,
relType);
- List<Entity> entitiesFromCache =
cacheData.getIfPresent(entityCacheKey);
-
- if (entitiesFromCache != null) {
- return convertEntity(entitiesFromCache);
- }
-
- List<E> entities = entityStore.listEntitiesByRelation(relType,
ident, type);
- syncEntitiesToCache(
- entityCacheKey, entities.stream().map(e -> (Entity)
e).collect(Collectors.toList()));
-
- return entities;
- });
- }
-
- /** {@inheritDoc} */
- @Override
- public <E extends Entity & HasIdentifier> E getOrLoad(
- NameIdentifier ident, Entity.EntityType type) throws IOException {
- checkArguments(ident, type);
-
- // Executes a thread-safe operation: attempts to load the entity from the
cache,
- // falls back to the underlying store if not present, and updates the
cache.
- // If an error occurs during loading, the exception will be propagated.
- return withLockAndThrow(
- () -> {
- EntityCacheKey entityCacheKey = EntityCacheKey.of(ident, type);
- List<Entity> entitiesFromCache =
cacheData.getIfPresent(entityCacheKey);
-
- if (entitiesFromCache != null) {
- return convertEntity(entitiesFromCache.get(0));
- }
-
- E entityFromStore = entityStore.get(ident, type,
getEntityClass(type));
- syncEntitiesToCache(entityCacheKey,
Lists.newArrayList(entityFromStore));
-
- return entityFromStore;
- });
- }
-
/** {@inheritDoc} */
@Override
public <E extends Entity & HasIdentifier> Optional<List<E>> getIfPresent(
@@ -194,12 +116,9 @@ public class CaffeineEntityCache extends BaseEntityCache {
Entity.EntityType identType) {
checkArguments(nameIdentifier, identType, relType);
- return withLock(
- () -> {
- List<Entity> entitiesFromCache =
- cacheData.getIfPresent(EntityCacheKey.of(nameIdentifier,
identType, relType));
- return
Optional.ofNullable(entitiesFromCache).map(BaseEntityCache::convertEntity);
- });
+ List<Entity> entitiesFromCache =
+ cacheData.getIfPresent(EntityCacheKey.of(nameIdentifier, identType,
relType));
+ return
Optional.ofNullable(entitiesFromCache).map(BaseEntityCache::convertEntities);
}
/** {@inheritDoc} */
@@ -208,14 +127,11 @@ public class CaffeineEntityCache extends BaseEntityCache {
NameIdentifier ident, Entity.EntityType type) {
checkArguments(ident, type);
- return withLock(
- () -> {
- List<Entity> entitiesFromCache =
cacheData.getIfPresent(EntityCacheKey.of(ident, type));
+ List<Entity> entitiesFromCache =
cacheData.getIfPresent(EntityCacheKey.of(ident, type));
- return Optional.ofNullable(entitiesFromCache)
- .filter(l -> !l.isEmpty())
- .map(entities -> convertEntity(entities.get(0)));
- });
+ return Optional.ofNullable(entitiesFromCache)
+ .filter(l -> !l.isEmpty())
+ .map(entities -> convertEntity(entities.get(0)));
}
/** {@inheritDoc} */
@@ -224,15 +140,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
NameIdentifier ident, Entity.EntityType type,
SupportsRelationOperations.Type relType) {
checkArguments(ident, type, relType);
- return withLock(
- () -> {
- boolean existed = contains(ident, type, relType);
- if (existed) {
- invalidateEntities(ident);
- }
-
- return existed;
- });
+ return withLock(() -> invalidateEntities(ident));
}
/** {@inheritDoc} */
@@ -240,15 +148,7 @@ public class CaffeineEntityCache extends BaseEntityCache {
public boolean invalidate(NameIdentifier ident, Entity.EntityType type) {
checkArguments(ident, type);
- return withLock(
- () -> {
- boolean existed = contains(ident, type);
- if (existed) {
- invalidateEntities(ident);
- }
-
- return existed;
- });
+ return withLock(() -> invalidateEntities(ident));
}
/** {@inheritDoc} */
@@ -256,16 +156,14 @@ public class CaffeineEntityCache extends BaseEntityCache {
public boolean contains(
NameIdentifier ident, Entity.EntityType type,
SupportsRelationOperations.Type relType) {
checkArguments(ident, type, relType);
-
- return withLock(() -> cacheData.getIfPresent(EntityCacheKey.of(ident,
type, relType)) != null);
+ return cacheData.getIfPresent(EntityCacheKey.of(ident, type, relType)) !=
null;
}
/** {@inheritDoc} */
@Override
public boolean contains(NameIdentifier ident, Entity.EntityType type) {
checkArguments(ident, type);
-
- return withLock(() -> cacheData.getIfPresent(EntityCacheKey.of(ident,
type)) != null);
+ return cacheData.getIfPresent(EntityCacheKey.of(ident, type)) != null;
}
/** {@inheritDoc} */
@@ -293,6 +191,9 @@ public class CaffeineEntityCache extends BaseEntityCache {
List<E> entities) {
checkArguments(ident, type, relType);
Preconditions.checkArgument(entities != null, "Entities cannot be null");
+ if (entities.isEmpty()) {
+ return;
+ }
syncEntitiesToCache(
EntityCacheKey.of(ident, type, relType),
@@ -306,12 +207,26 @@ public class CaffeineEntityCache extends BaseEntityCache {
withLock(
() -> {
+ invalidateOnKeyChange(entity);
NameIdentifier identifier = getIdentFromEntity(entity);
EntityCacheKey entityCacheKey = EntityCacheKey.of(identifier,
entity.type());
+
syncEntitiesToCache(entityCacheKey, Lists.newArrayList(entity));
});
}
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Entity & HasIdentifier> void invalidateOnKeyChange(E
entity) {
+ // Invalidate the cache if inserting the entity may affect related cache
keys.
+ // For example, inserting a model version changes the latest version of
the model,
+ // so the corresponding model cache entry should be invalidated.
+ if (Objects.requireNonNull(entity.type()) ==
Entity.EntityType.MODEL_VERSION) {
+ NameIdentifier modelIdent = ((ModelVersionEntity)
entity).modelIdentifier();
+ invalidate(modelIdent, Entity.EntityType.MODEL);
+ }
+ }
+
/** {@inheritDoc} */
@Override
public <E extends Exception> void withCacheLock(ThrowingRunnable<E> action)
throws E {
@@ -402,12 +317,14 @@ public class CaffeineEntityCache extends BaseEntityCache {
*
* @param identifier The identifier of the entity to invalidate
*/
- private void invalidateEntities(NameIdentifier identifier) {
- Iterable<EntityCacheKey> entityKeysToRemove =
- cacheIndex.getValuesForKeysStartingWith(identifier.toString());
+ private boolean invalidateEntities(NameIdentifier identifier) {
+ List<EntityCacheKey> entityKeysToRemove =
+
Lists.newArrayList(cacheIndex.getValuesForKeysStartingWith(identifier.toString()));
cacheData.invalidateAll(entityKeysToRemove);
entityKeysToRemove.forEach(key -> cacheIndex.remove(key.toString()));
+
+ return !entityKeysToRemove.isEmpty();
}
/**
@@ -433,8 +350,8 @@ public class CaffeineEntityCache extends BaseEntityCache {
* Runs the given action with the lock and returns the result.
*
* @param action The action to run with the lock
- * @return The result of the action
* @param <T> The type of the result
+ * @return The result of the action
*/
private <T> T withLock(Supplier<T> action) {
try {
diff --git a/core/src/main/java/org/apache/gravitino/cache/NoOpsCache.java
b/core/src/main/java/org/apache/gravitino/cache/NoOpsCache.java
new file mode 100644
index 0000000000..56cdb93511
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/cache/NoOpsCache.java
@@ -0,0 +1,156 @@
+/*
+ * 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 java.util.List;
+import java.util.Optional;
+import java.util.concurrent.locks.ReentrantLock;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+
+/** A cache implementation that does not cache anything. */
+public class NoOpsCache extends BaseEntityCache {
+ private final ReentrantLock opLock = new ReentrantLock();
+ /**
+ * Constructs a new {@link BaseEntityCache} instance.
+ *
+ * @param config The cache configuration
+ */
+ public NoOpsCache(Config config) {
+ super(config);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void invalidateExpiredItem(EntityCacheKey key) {
+ // do nothing
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clear() {
+ // do nothing
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long size() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Exception> void withCacheLock(ThrowingRunnable<E> action)
throws E {
+ try {
+ opLock.lockInterruptibly();
+ try {
+ action.run();
+ } finally {
+ opLock.unlock();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Thread was interrupted while waiting
for lock", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <T, E extends Exception> T withCacheLock(ThrowingSupplier<T, E>
action) throws E {
+ try {
+ opLock.lockInterruptibly();
+ try {
+ return action.get();
+ } finally {
+ opLock.unlock();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Thread was interrupted while waiting
for lock", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Entity & HasIdentifier> Optional<E> getIfPresent(
+ NameIdentifier ident, Entity.EntityType type) {
+ return Optional.empty();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean invalidate(NameIdentifier ident, Entity.EntityType type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean contains(NameIdentifier ident, Entity.EntityType type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Entity & HasIdentifier> void put(E entity) {
+ // do nothing
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Entity & HasIdentifier> void invalidateOnKeyChange(E
entity) {
+ // do nothing
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Entity & HasIdentifier> Optional<List<E>> getIfPresent(
+ SupportsRelationOperations.Type relType,
+ NameIdentifier nameIdentifier,
+ Entity.EntityType identType) {
+ return Optional.empty();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean invalidate(
+ NameIdentifier ident, Entity.EntityType type,
SupportsRelationOperations.Type relType) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean contains(
+ NameIdentifier ident, Entity.EntityType type,
SupportsRelationOperations.Type relType) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E extends Entity & HasIdentifier> void put(
+ NameIdentifier ident,
+ Entity.EntityType type,
+ SupportsRelationOperations.Type relType,
+ List<E> entities) {
+ // do nothing
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/cache/SupportsEntityStoreCache.java
b/core/src/main/java/org/apache/gravitino/cache/SupportsEntityStoreCache.java
index 177c0ecc77..ed42084fb0 100644
---
a/core/src/main/java/org/apache/gravitino/cache/SupportsEntityStoreCache.java
+++
b/core/src/main/java/org/apache/gravitino/cache/SupportsEntityStoreCache.java
@@ -19,7 +19,6 @@
package org.apache.gravitino.cache;
-import java.io.IOException;
import java.util.Optional;
import org.apache.gravitino.Entity;
import org.apache.gravitino.HasIdentifier;
@@ -30,19 +29,6 @@ import org.apache.gravitino.NameIdentifier;
* loading, storing, and invalidating individual metadata entities.
*/
public interface SupportsEntityStoreCache {
- /**
- * Retrieves an entity by its name identifier and type. If the entity is not
present in the cache,
- * it will be loaded from the backing EntityStore.
- *
- * @param ident The name identifier of the entity
- * @param type The type of the entity
- * @return The cached or newly loaded Entity instance
- * @param <E> The class of the entity
- * @throws IOException if the operation fails
- */
- <E extends Entity & HasIdentifier> E getOrLoad(NameIdentifier ident,
Entity.EntityType type)
- throws IOException;
-
/**
* Retrieves an entity from the cache if it exists. Will not attempt to load
from the store if
* missing.
@@ -80,4 +66,15 @@ public interface SupportsEntityStoreCache {
* @param <E> The class of the entity
*/
<E extends Entity & HasIdentifier> void put(E entity);
+
+ /**
+ * Invalidates related cache entries when inserting the given entity, if
necessary.
+ *
+ * <p>For example, inserting a {@code ModelVersion} may require invalidating
the corresponding
+ * {@code Model} entry in the cache to maintain consistency.
+ *
+ * @param entity The entity being inserted
+ * @param <E> The type of the entity
+ */
+ <E extends Entity & HasIdentifier> void invalidateOnKeyChange(E entity);
}
diff --git
a/core/src/main/java/org/apache/gravitino/cache/SupportsRelationEntityCache.java
b/core/src/main/java/org/apache/gravitino/cache/SupportsRelationEntityCache.java
index 42db55ca1f..98619db006 100644
---
a/core/src/main/java/org/apache/gravitino/cache/SupportsRelationEntityCache.java
+++
b/core/src/main/java/org/apache/gravitino/cache/SupportsRelationEntityCache.java
@@ -19,11 +19,9 @@
package org.apache.gravitino.cache;
-import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.apache.gravitino.Entity;
-import org.apache.gravitino.EntityStore;
import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsRelationOperations;
@@ -33,23 +31,6 @@ import org.apache.gravitino.SupportsRelationOperations;
* relationships.
*/
public interface SupportsRelationEntityCache {
- /**
- * Retrieves a list of entities related to the specified entity under the
given relation type. If
- * the related entities are not present in the cache, they will be loaded
from the underlying
- * {@link EntityStore}.
- *
- * @param ident The name identifier of the entity to find related entities
for
- * @param type The type of the entity to find related entities for
- * @param relType The relation type to find related entities for
- * @return A list of related entities, or an empty list if none are found
- * @param <E> The class of the related entities
- * @throws IOException if an I/O error occurs while loading related entities
from the underlying
- * {@link EntityStore}
- */
- <E extends Entity & HasIdentifier> List<E> getOrLoad(
- NameIdentifier ident, Entity.EntityType type,
SupportsRelationOperations.Type relType)
- throws IOException;
-
/**
* Retrieves a list of related entities from the cache, if present.
*
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 a6d650c547..2bc2075f70 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
@@ -23,6 +23,7 @@ import static
org.apache.gravitino.Configs.ENTITY_RELATIONAL_STORE;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.List;
+import java.util.Optional;
import java.util.function.Function;
import org.apache.gravitino.Config;
import org.apache.gravitino.Configs;
@@ -34,6 +35,9 @@ import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.cache.CaffeineEntityCache;
+import org.apache.gravitino.cache.EntityCache;
+import org.apache.gravitino.cache.NoOpsCache;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.meta.TagEntity;
import org.apache.gravitino.tag.SupportsTagOperations;
@@ -54,12 +58,18 @@ public class RelationalEntityStore
Configs.DEFAULT_ENTITY_RELATIONAL_STORE,
JDBCBackend.class.getCanonicalName());
private RelationalBackend backend;
private RelationalGarbageCollector garbageCollector;
+ private EntityCache cache;
@Override
public void initialize(Config config) throws RuntimeException {
this.backend = createRelationalEntityBackend(config);
this.garbageCollector = new RelationalGarbageCollector(backend, config);
this.garbageCollector.start();
+ // TODO USE SPI to load the cache
+ this.cache =
+ config.get(Configs.CACHE_ENABLED)
+ ? new CaffeineEntityCache(config)
+ : new NoOpsCache(config);
}
private static RelationalBackend createRelationalEntityBackend(Config
config) {
@@ -95,19 +105,22 @@ public class RelationalEntityStore
@Override
public boolean exists(NameIdentifier ident, Entity.EntityType entityType)
throws IOException {
- return backend.exists(ident, entityType);
+ boolean existsInCache = cache.contains(ident, entityType);
+ return existsInCache || backend.exists(ident, entityType);
}
@Override
public <E extends Entity & HasIdentifier> void put(E e, boolean overwritten)
throws IOException, EntityAlreadyExistsException {
backend.insert(e, overwritten);
+ cache.put(e);
}
@Override
public <E extends Entity & HasIdentifier> E update(
NameIdentifier ident, Class<E> type, Entity.EntityType entityType,
Function<E, E> updater)
throws IOException, NoSuchEntityException, EntityAlreadyExistsException {
+ cache.invalidate(ident, entityType);
return backend.update(ident, entityType, updater);
}
@@ -115,15 +128,26 @@ public class RelationalEntityStore
public <E extends Entity & HasIdentifier> E get(
NameIdentifier ident, Entity.EntityType entityType, Class<E> e)
throws NoSuchEntityException, IOException {
- return backend.get(ident, entityType);
+ return cache.withCacheLock(
+ () -> {
+ Optional<E> entityFromCache = cache.getIfPresent(ident, entityType);
+ if (entityFromCache.isPresent()) {
+ return entityFromCache.get();
+ }
+
+ E entity = backend.get(ident, entityType);
+ cache.put(entity);
+ return entity;
+ });
}
@Override
public boolean delete(NameIdentifier ident, Entity.EntityType entityType,
boolean cascade)
throws IOException {
try {
+ cache.invalidate(ident, entityType);
return backend.delete(ident, entityType, cascade);
- } catch (NoSuchEntityException nse) {
+ } catch (NoSuchEntityException e) {
return false;
}
}
@@ -135,6 +159,7 @@ public class RelationalEntityStore
@Override
public void close() throws IOException {
+ cache.clear();
garbageCollector.close();
backend.close();
}
@@ -184,7 +209,20 @@ public class RelationalEntityStore
public <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
Type relType, NameIdentifier nameIdentifier, Entity.EntityType
identType, boolean allFields)
throws IOException {
- return backend.listEntitiesByRelation(relType, nameIdentifier, identType,
allFields);
+ return cache.withCacheLock(
+ () -> {
+ Optional<List<E>> entities = cache.getIfPresent(relType,
nameIdentifier, identType);
+ if (entities.isPresent()) {
+ return entities.get();
+ }
+
+ List<E> backendEntities =
+ backend.listEntitiesByRelation(relType, nameIdentifier,
identType, allFields);
+
+ cache.put(nameIdentifier, identType, relType, backendEntities);
+
+ return backendEntities;
+ });
}
@Override
@@ -196,6 +234,7 @@ public class RelationalEntityStore
Entity.EntityType dstType,
boolean override)
throws IOException {
- backend.insertRelation(relType, srcIdentifier, srcType, dstIdentifier,
dstType, true);
+ cache.invalidate(srcIdentifier, srcType, relType);
+ backend.insertRelation(relType, srcIdentifier, srcType, dstIdentifier,
dstType, override);
}
}
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
index d62a641438..e93deab223 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
@@ -52,6 +52,7 @@ import java.util.UUID;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
import org.apache.gravitino.GravitinoEnv;
@@ -130,6 +131,12 @@ public class TestAccessControlManager {
Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 *
1000L);
Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
Mockito.when(config.get(CATALOG_CACHE_EVICTION_INTERVAL_MS)).thenReturn(1000L);
+ // Fix cache for testing.
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
b/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
index 98b83a77b3..95dd6fcd21 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
@@ -44,6 +44,7 @@ import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
import org.apache.gravitino.GravitinoEnv;
@@ -103,6 +104,12 @@ public class TestOwnerManager {
Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 *
1000L);
Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
Mockito.when(config.get(CATALOG_CACHE_EVICTION_INTERVAL_MS)).thenReturn(1000L);
+ // Fix the cache config for testing
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
diff --git
a/core/src/test/java/org/apache/gravitino/cache/TestCaffeineEntityCache.java
b/core/src/test/java/org/apache/gravitino/cache/TestCaffeineEntityCache.java
new file mode 100644
index 0000000000..57a60f1f4b
--- /dev/null
+++ b/core/src/test/java/org/apache/gravitino/cache/TestCaffeineEntityCache.java
@@ -0,0 +1,396 @@
+/*
+ * 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 org.mockito.Mockito.spy;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+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.utils.NameIdentifierUtil;
+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;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class TestCaffeineEntityCache {
+ private CaffeineEntityCache real;
+ private CaffeineEntityCache cache;
+
+ private NameIdentifier ident1;
+ private NameIdentifier ident2;
+ private NameIdentifier ident3;
+ private NameIdentifier ident4;
+ private NameIdentifier ident5;
+ private NameIdentifier ident6;
+ private NameIdentifier ident7;
+ private NameIdentifier ident8;
+ private NameIdentifier ident9;
+ private NameIdentifier ident10;
+ private NameIdentifier ident11;
+ private NameIdentifier ident12;
+ private NameIdentifier ident13;
+
+ // Test Entities
+ private SchemaEntity entity1;
+ private SchemaEntity entity2;
+ private TableEntity entity3;
+ private TableEntity entity4;
+ private TableEntity entity5;
+ private CatalogEntity entity6;
+ private BaseMetalake entity7;
+ private UserEntity entity8;
+ private UserEntity entity9;
+ private GroupEntity entity10;
+ private GroupEntity entity11;
+ private RoleEntity entity12;
+ private RoleEntity entity13;
+
+ @BeforeAll
+ void init() {
+ initTestNameIdentifier();
+ initTestEntities();
+ }
+
+ @Test
+ void testPutAllTypeInCache() {
+
+ initCache();
+
+ 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 testGetIfPresent() {
+
+ initCache();
+
+ 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(ident1,
Entity.EntityType.SCHEMA).isPresent());
+ Assertions.assertTrue(cache.getIfPresent(ident2,
Entity.EntityType.SCHEMA).isPresent());
+ Assertions.assertTrue(cache.getIfPresent(ident3,
Entity.EntityType.TABLE).isPresent());
+ Assertions.assertTrue(
+ cache
+ .getIfPresent(
+ SupportsRelationOperations.Type.ROLE_USER_REL, ident12,
Entity.EntityType.ROLE)
+ .isPresent());
+ Assertions.assertEquals(cache.size(), 4);
+
+ Assertions.assertFalse(cache.getIfPresent(ident4,
Entity.EntityType.TABLE).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident5,
Entity.EntityType.TABLE).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident6,
Entity.EntityType.CATALOG).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident7,
Entity.EntityType.METALAKE).isPresent());
+ }
+
+ @Test
+ void testContains() {
+
+ initCache();
+
+ cache.put(entity1);
+ cache.put(entity2);
+ cache.put(entity3);
+
+ Assertions.assertTrue(cache.contains(ident1, Entity.EntityType.SCHEMA));
+ Assertions.assertTrue(cache.contains(ident2, Entity.EntityType.SCHEMA));
+ Assertions.assertTrue(cache.contains(ident3, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident4, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident5, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident6, Entity.EntityType.CATALOG));
+ Assertions.assertFalse(cache.contains(ident7, Entity.EntityType.METALAKE));
+ }
+
+ @Test
+ void testSize() {
+
+ initCache();
+
+ cache.put(entity1);
+ cache.put(entity2);
+ cache.put(entity3);
+
+ Assertions.assertEquals(3, cache.size());
+ }
+
+ @Test
+ void testClear() {
+
+ initCache();
+
+ 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(ident1,
Entity.EntityType.SCHEMA).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident2,
Entity.EntityType.SCHEMA).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident3,
Entity.EntityType.TABLE).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident4,
Entity.EntityType.TABLE).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident5,
Entity.EntityType.TABLE).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident6,
Entity.EntityType.CATALOG).isPresent());
+ Assertions.assertFalse(cache.getIfPresent(ident7,
Entity.EntityType.METALAKE).isPresent());
+ Assertions.assertFalse(
+ cache
+ .getIfPresent(
+ SupportsRelationOperations.Type.ROLE_USER_REL, ident12,
Entity.EntityType.ROLE)
+ .isPresent());
+ Assertions.assertFalse(
+ cache
+ .getIfPresent(
+ SupportsRelationOperations.Type.ROLE_GROUP_REL, ident13,
Entity.EntityType.ROLE)
+ .isPresent());
+ }
+
+ @Test
+ void testInvalidateMetalake() {
+
+ initCache();
+
+ 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(ident7, Entity.EntityType.METALAKE);
+
+ Assertions.assertEquals(4, cache.size());
+ Assertions.assertTrue(cache.contains(ident10, Entity.EntityType.GROUP));
+ Assertions.assertTrue(cache.contains(ident11, Entity.EntityType.GROUP));
+ Assertions.assertTrue(
+ cache.contains(
+ ident13, Entity.EntityType.ROLE,
SupportsRelationOperations.Type.ROLE_GROUP_REL));
+ Assertions.assertTrue(cache.getIfPresent(ident2,
Entity.EntityType.SCHEMA).isPresent());
+
+ Assertions.assertFalse(cache.contains(ident1, Entity.EntityType.SCHEMA));
+ Assertions.assertFalse(cache.contains(ident3, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident4, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident5, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident6, Entity.EntityType.TABLE));
+ Assertions.assertFalse(cache.contains(ident7, Entity.EntityType.TABLE));
+ }
+
+ private void initCache() {
+ initCache(new Config() {});
+ }
+
+ // TODO Add other tests for cache
+
+ private void initCache(Config config) {
+ real = new CaffeineEntityCache(config);
+ cache = spy(real);
+ }
+
+ private void initTestNameIdentifier() {
+ ident1 = NameIdentifier.of("metalake1", "catalog1", "schema1");
+ ident2 = NameIdentifier.of("metalake2", "catalog2", "schema2");
+ ident3 = NameIdentifier.of("metalake1", "catalog1", "schema1", "table1");
+ ident4 = NameIdentifier.of("metalake1", "catalog2", "schema1", "table2");
+ ident5 = NameIdentifier.of("metalake1", "catalog1", "schema2", "table3");
+ ident6 = NameIdentifier.of("metalake1", "catalog1");
+ ident7 = NameIdentifier.of("metalake1");
+
+ ident8 = NameIdentifierUtil.ofUser("metalake1", "user1");
+ ident9 = NameIdentifierUtil.ofUser("metalake1", "user2");
+
+ ident10 = NameIdentifierUtil.ofGroup("metalake2", "group1");
+ ident11 = NameIdentifierUtil.ofGroup("metalake2", "group2");
+
+ ident12 = NameIdentifierUtil.ofRole("metalake1", "role1");
+ ident13 = NameIdentifierUtil.ofRole("metalake2", "role2");
+
+ // TODO remove next PR
+ System.out.println(ident8 + " " + ident9);
+ }
+
+ private 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");
+ }
+}
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 00a7358222..686c61a323 100644
--- a/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
+++ b/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java
@@ -55,6 +55,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
import org.apache.gravitino.Entity.EntityType;
import org.apache.gravitino.EntityAlreadyExistsException;
@@ -139,6 +140,13 @@ public class TestEntityStorage {
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_WAIT_MILLISECONDS)).thenReturn(1000L);
Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 *
1000L);
Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
+ // Fix cache config for test
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
+
BaseIT baseIT = new BaseIT();
try {
diff --git a/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
b/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
index 5cbffb3f97..3506e1755e 100644
--- a/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
+++ b/core/src/test/java/org/apache/gravitino/tag/TestTagManager.java
@@ -18,7 +18,6 @@
*/
package org.apache.gravitino.tag;
-import static org.apache.gravitino.Configs.CATALOG_CACHE_EVICTION_INTERVAL_MS;
import static org.apache.gravitino.Configs.DEFAULT_ENTITY_RELATIONAL_STORE;
import static
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER;
import static
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS;
@@ -52,6 +51,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.EntityStoreFactory;
@@ -132,7 +132,12 @@ public class TestTagManager {
Mockito.when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L);
Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 *
1000L);
Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
-
Mockito.when(config.get(CATALOG_CACHE_EVICTION_INTERVAL_MS)).thenReturn(1000L);
+ // Fix cache config for test
+ Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+ Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+ Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
diff --git a/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
b/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
new file mode 100644
index 0000000000..4c6beb2a99
--- /dev/null
+++ b/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
@@ -0,0 +1,502 @@
+/*
+ * 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.utils;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.Privileges;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.file.Fileset;
+import org.apache.gravitino.meta.AuditInfo;
+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.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.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.rel.types.Types;
+import org.apache.gravitino.storage.RandomIdGenerator;
+
+/**
+ * Utility class providing factory methods for creating mock and test
instances of various metadata
+ * entities used in unit tests.
+ *
+ * <p>This class includes shortcut methods for constructing entities such as
{@link BaseMetalake},
+ * {@link CatalogEntity}, {@link SchemaEntity}, {@link TableEntity}, {@link
ModelEntity}, {@link
+ * FilesetEntity}, {@link TopicEntity}, {@link TagEntity}, {@link UserEntity},
{@link GroupEntity},
+ * {@link RoleEntity}, and {@link ModelVersionEntity}. It also includes
methods to create mock
+ * {@link ColumnEntity} and {@link SecurableObject} instances for use in
mocking behavior-dependent
+ * components.
+ *
+ * <p>These utilities are intended for testing purposes only and rely on
in-memory mock objects or
+ * randomly generated IDs to simulate real entities.
+ *
+ * <p>Note: This class is not thread-safe.
+ */
+public class TestUtil {
+ // Random ID generator used to generate IDs for test entities.
+ private static RandomIdGenerator generator = new RandomIdGenerator();
+
+ /**
+ * Shorthand for creating a test metalake entity with default values.
+ *
+ * @return The test {@link BaseMetalake} entity.
+ */
+ public static BaseMetalake getTestMetalake() {
+ return getTestMetalake(generator.nextId(), "test_metalake", "metalake
entity test");
+ }
+
+ /**
+ * Get the test metalake entity with the given ID, name, and comment.
+ *
+ * @param id The ID of the metalake entity.
+ * @param name The name of the metalake entity.
+ * @param comment The comment of the metalake entity.
+ * @return The test {@link BaseMetalake} entity.
+ */
+ public static BaseMetalake getTestMetalake(long id, String name, String
comment) {
+ return BaseMetalake.builder()
+ .withId(id)
+ .withName(name)
+ .withVersion(SchemaVersion.V_0_1)
+ .withAuditInfo(getTestAuditInfo())
+ .withComment(comment)
+ .withProperties(ImmutableMap.of())
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test catalog entity with default values.
+ *
+ * @return The test {@link CatalogEntity} entity.
+ */
+ public static CatalogEntity getTestCatalogEntity() {
+ return getTestCatalogEntity(
+ generator.nextId(), "test_catalog", Namespace.of("m1"), "hive",
"catalog entity test");
+ }
+
+ /**
+ * Returns a test catalog entity with the given ID, name, namespace,
provider, and comment.
+ *
+ * @param id The ID of the catalog entity.
+ * @param name The name of the catalog entity.
+ * @param namespace The namespace of the catalog entity.
+ * @param provider The provider of the catalog entity.
+ * @param comment The comment of the catalog entity.
+ * @return The test {@link CatalogEntity} entity.
+ */
+ public static CatalogEntity getTestCatalogEntity(
+ long id, String name, Namespace namespace, String provider, String
comment) {
+ return CatalogEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(namespace)
+ .withType(Catalog.Type.RELATIONAL)
+ .withProvider(provider)
+ .withAuditInfo(getTestAuditInfo())
+ .withComment(comment)
+ .withProperties(ImmutableMap.of())
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test schema entity with default values.
+ *
+ * @return The test {@link SchemaEntity} entity.
+ */
+ public static SchemaEntity getTestSchemaEntity() {
+ return getTestSchemaEntity(
+ generator.nextId(), "test_schema", Namespace.of("m1", "c1"), "schema
entity test");
+ }
+
+ /**
+ * Returns a test schema entity with the given ID, name, namespace, and
comment.
+ *
+ * @param id The ID of the schema entity.
+ * @param name The name of the schema entity.
+ * @param namespace The namespace of the schema entity.
+ * @param comment The comment of the schema entity.
+ * @return The test {@link SchemaEntity} entity.
+ */
+ public static SchemaEntity getTestSchemaEntity(
+ long id, String name, Namespace namespace, String comment) {
+ return SchemaEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(namespace)
+ .withAuditInfo(getTestAuditInfo())
+ .withComment(comment)
+ .withProperties(ImmutableMap.of())
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test model entity with default values.
+ *
+ * @return The test {@link TableEntity} entity.
+ */
+ public static ModelEntity getTestModelEntity() {
+ return getTestModelEntity(generator.nextId(), "test_model",
Namespace.of("m1", "c1", "s1"));
+ }
+
+ /**
+ * Returns a test model entity with the given ID, name, and namespace.
+ *
+ * @param id The ID of the model entity.
+ * @param name The name of the model entity.
+ * @param namespace The namespace of the model entity.
+ * @return The test {@link ModelEntity} entity.
+ */
+ public static ModelEntity getTestModelEntity(long id, String name, Namespace
namespace) {
+ return ModelEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(namespace)
+ .withLatestVersion(1)
+ .withAuditInfo(getTestAuditInfo())
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test table entity with default values.
+ *
+ * @return The test {@link TableEntity} entity.
+ */
+ public static TableEntity getTestTableEntity() {
+ return getTestTableEntity(generator.nextId(), "test_table",
Namespace.of("m1", "c2", "s2"));
+ }
+
+ /**
+ * Returns a test table entity with the given ID, name, and namespace.
+ *
+ * @param id The ID of the table entity.
+ * @param name The name of the table entity.
+ * @param namespace The namespace of the table entity.
+ * @return The test {@link TableEntity} entity.
+ */
+ public static TableEntity getTestTableEntity(long id, String name, Namespace
namespace) {
+ return TableEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withAuditInfo(getTestAuditInfo())
+ .withNamespace(namespace)
+ .withColumns(ImmutableList.of(getMockColumnEntity()))
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test fileset entity with default values.
+ *
+ * @return The test {@link FilesetEntity} entity.
+ */
+ public static FilesetEntity getTestFileSetEntity() {
+ return getTestFileSetEntity(
+ generator.nextId(),
+ "fileset_test",
+ "file:///tmp/fileset_test",
+ Namespace.of("m1", "c3", "s3"),
+ "fileset entity test",
+ Fileset.Type.EXTERNAL);
+ }
+
+ /**
+ * Returns a test fileset entity with the given ID, name, storage location,
namespace, and
+ * comment.
+ *
+ * @param id The ID of the fileset entity.
+ * @param name The name of the fileset entity.
+ * @param storageLocation The storage location of the fileset entity.
+ * @param namespace The namespace of the fileset entity.
+ * @param comment The comment of the fileset entity.
+ * @param type The type of the fileset entity.
+ * @return The test {@link FilesetEntity} entity.
+ */
+ public static FilesetEntity getTestFileSetEntity(
+ long id,
+ String name,
+ String storageLocation,
+ Namespace namespace,
+ String comment,
+ Fileset.Type type) {
+ return FilesetEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(namespace)
+ .withStorageLocations(ImmutableMap.of("location1", storageLocation))
+ .withFilesetType(type)
+ .withAuditInfo(getTestAuditInfo())
+ .withComment(comment)
+ .withProperties(ImmutableMap.of())
+ .build();
+ }
+
+ /**
+ * Returns a test topic entity with default values.
+ *
+ * @return The test {@link TopicEntity} entity.
+ */
+ public static TopicEntity getTestTopicEntity() {
+ return getTestTopicEntity(
+ generator.nextId(), "topic_test", Namespace.of("m1", "c4", "s4"),
"topic entity test");
+ }
+
+ /**
+ * Returns a test topic entity with the given ID, name, namespace, and
comment.
+ *
+ * @param id The ID of the topic entity.
+ * @param name The name of the topic entity.
+ * @param namespace The namespace of the topic entity.
+ * @param comment The comment of the topic entity.
+ * @return The test {@link TopicEntity} entity.
+ */
+ public static TopicEntity getTestTopicEntity(
+ long id, String name, Namespace namespace, String comment) {
+ return TopicEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(namespace)
+ .withAuditInfo(getTestAuditInfo())
+ .withComment(comment)
+ .withProperties(ImmutableMap.of())
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test tag entity with default values.
+ *
+ * @return The test {@link TagEntity} entity.
+ */
+ public static TagEntity getTestTagEntity() {
+ return getTestTagEntity(generator.nextId(), "tag_test",
Namespace.of("m1"), "tag entity test");
+ }
+
+ /**
+ * Returns a test tag entity with the given ID, name, namespace, and comment.
+ *
+ * @param id The ID of the tag entity.
+ * @param name The name of the tag entity.
+ * @param namespace The namespace of the tag entity.
+ * @param comment The comment of the tag entity.
+ * @return The test {@link TagEntity} entity.
+ */
+ public static TagEntity getTestTagEntity(
+ long id, String name, Namespace namespace, String comment) {
+ return TagEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(namespace)
+ .withAuditInfo(getTestAuditInfo())
+ .withComment(comment)
+ .withProperties(ImmutableMap.of())
+ .build();
+ }
+
+ /**
+ * Returns a column entity with default values.
+ *
+ * @return The test {@link ColumnEntity} entity.
+ */
+ public static ColumnEntity getMockColumnEntity() {
+ ColumnEntity mockColumn = mock(ColumnEntity.class);
+ when(mockColumn.name()).thenReturn("filed1");
+ when(mockColumn.dataType()).thenReturn(Types.StringType.get());
+ when(mockColumn.nullable()).thenReturn(false);
+ when(mockColumn.auditInfo()).thenReturn(getTestAuditInfo());
+
+ return mockColumn;
+ }
+
+ /**
+ * Returns an audit info entity with default values.
+ *
+ * @return The test {@link AuditInfo} entity.
+ */
+ public static AuditInfo getTestAuditInfo() {
+ return AuditInfo.builder()
+ .withCreator("admin")
+ .withCreateTime(Instant.now())
+ .withLastModifier("admin")
+ .withLastModifiedTime(Instant.now())
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test user entity with default values.
+ *
+ * @return The test {@link UserEntity} entity.
+ */
+ public static UserEntity getTestUserEntity() {
+ return getTestUserEntity(
+ generator.nextId(), "test_user", "test_metalake",
ImmutableList.of(1L));
+ }
+
+ /**
+ * Returns a test user entity with the given ID, name, and metalake.
+ *
+ * @param id The ID of the user entity.
+ * @param name The name of the user entity.
+ * @param metalake The metalake of the user entity.
+ * @param roles The roles of the user entity.
+ * @return The test {@link UserEntity} entity.
+ */
+ public static UserEntity getTestUserEntity(
+ long id, String name, String metalake, List<Long> roles) {
+ return UserEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(NamespaceUtil.ofUser(metalake))
+ .withAuditInfo(getTestAuditInfo())
+ .withRoleIds(roles)
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test group entity with default values.
+ *
+ * @return The test {@link GroupEntity} entity.
+ */
+ public static GroupEntity getTestGroupEntity() {
+ return getTestGroupEntity(
+ generator.nextId(), "test_group", "test_metalake",
ImmutableList.of("role1", "role2"));
+ }
+
+ /**
+ * Returns a test group entity with the given ID, name, metalake, and roles.
+ *
+ * @param id The ID of the group entity.
+ * @param name The name of the group entity.
+ * @param metalake The metalake of the group entity.
+ * @param roles The roles of the group entity.
+ * @return The test {@link GroupEntity} entity.
+ */
+ public static GroupEntity getTestGroupEntity(
+ long id, String name, String metalake, List<String> roles) {
+ return GroupEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(NamespaceUtil.ofGroup(metalake))
+ .withAuditInfo(getTestAuditInfo())
+ .withRoleNames(roles)
+ .build();
+ }
+
+ /**
+ * Shorthand for creating a test role entity with default values.
+ *
+ * @return The test {@link RoleEntity} entity.
+ */
+ public static RoleEntity getTestRoleEntity() {
+ return getTestRoleEntity(generator.nextId(), "test_role", "test_metalake");
+ }
+
+ /**
+ * Returns a test role entity with the given ID, name, and metalake.
+ *
+ * @param id The ID of the role entity.
+ * @param name The name of the role entity.
+ * @param metalake The metalake of the role entity.
+ * @return The test {@link RoleEntity} entity.
+ */
+ public static RoleEntity getTestRoleEntity(long id, String name, String
metalake) {
+ return RoleEntity.builder()
+ .withId(id)
+ .withName(name)
+ .withNamespace(NamespaceUtil.ofRole(metalake))
+ .withAuditInfo(getTestAuditInfo())
+ .withSecurableObjects(ImmutableList.of(getMockSecurableObject()))
+ .build();
+ }
+
+ /**
+ * Returns a mock securable object with default values.
+ *
+ * @return The test {@link SecurableObject} entity.
+ */
+ public static SecurableObject getMockSecurableObject() {
+ SecurableObject mockObject = mock(SecurableObject.class);
+ when(mockObject.privileges())
+ .thenReturn(
+ ImmutableList.of(Privileges.CreateSchema.allow(),
Privileges.CreateTable.allow()));
+
+ return mockObject;
+ }
+
+ /**
+ * Shorthand for creating a test model version entity with default values.
+ *
+ * @return The test {@link ModelVersionEntity} entity.
+ */
+ public static ModelVersionEntity getTestModelVersionEntity() {
+ return getTestModelVersionEntity(
+ NameIdentifier.of("m1", "c1", "s1", "model-version-test"),
+ 1,
+ "uri",
+ ImmutableMap.of("key1", "value1"),
+ "model version entity test",
+ ImmutableList.of("alias1", "alias2"));
+ }
+
+ /**
+ * Returns a test model version entity with the given model identifier,
version, uri, properties,
+ * comment, and aliases.
+ *
+ * @param modelIdentifier The model identifier of the model version entity.
+ * @param version The version of the model version entity.
+ * @param uri The uri of the model version entity.
+ * @param properties The properties of the model version entity.
+ * @param comment The comment of the model version entity.
+ * @param aliases The aliases of the model version entity.
+ * @return The test {@link ModelVersionEntity} entity.
+ */
+ public static ModelVersionEntity getTestModelVersionEntity(
+ NameIdentifier modelIdentifier,
+ int version,
+ String uri,
+ Map<String, String> properties,
+ String comment,
+ List<String> aliases) {
+ return ModelVersionEntity.builder()
+ .withVersion(version)
+ .withModelIdentifier(modelIdentifier)
+ .withUri(uri)
+ .withProperties(properties)
+ .withComment(comment)
+ .withAuditInfo(getTestAuditInfo())
+ .withAliases(aliases)
+ .build();
+ }
+}