This is an automated email from the ASF dual-hosted git repository.
yuqi4733 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 2b31bb2eb9 [#10459] improvement(core): Eliminate N+1 queries in
metadata object (#10589)
2b31bb2eb9 is described below
commit 2b31bb2eb9a78e3cd82ca79019a8423886d7aafa
Author: Babu Mahesh <[email protected]>
AuthorDate: Thu Apr 2 08:44:27 2026 +0530
[#10459] improvement(core): Eliminate N+1 queries in metadata object
(#10589)
### What changes were proposed in this pull request?
Eliminates N+1 query problems when resolving metadata object names for
Tags, Policies, and Job Templates by implementing batch query methods.
### Why are the changes needed
- Previously, when fetching names for multiple metadata objects
(tags/policies/job templates), the code would issue one SQL query per
object ID in a stream. This creates N+1 query performance problems,
especially when dealing with large numbers of objects.
- Implemented batch query methods using MyBatis `<foreach>` to fetch
multiple records in a single SQL query with an `IN` clause.
- The service layer now batches all IDs and makes one query instead of N
individual queries.
Fix: #10459
### How was this patch tested?
All the existing unit tests and integrations tests ran and should be
sufficient as there is no behaviour changes
---------
Co-authored-by: Babu Mahesh <[email protected]>
---
.../relational/mapper/JobTemplateMetaMapper.java | 6 +
.../mapper/JobTemplateMetaSQLProviderFactory.java | 5 +
.../relational/mapper/PolicyMetaMapper.java | 13 +
.../mapper/PolicyMetaSQLProviderFactory.java | 4 +
.../storage/relational/mapper/TagMetaMapper.java | 3 +
.../mapper/TagMetaSQLProviderFactory.java | 4 +
.../base/JobTemplateMetaBaseSQLProvider.java | 20 ++
.../provider/base/PolicyMetaBaseSQLProvider.java | 17 ++
.../provider/base/TagMetaBaseSQLProvider.java | 21 ++
.../relational/service/MetadataObjectService.java | 87 ++++---
.../storage/relational/TestJDBCBackend.java | 96 +++++++
.../service/TestJobTemplateMetaService.java | 37 ---
.../service/TestMetadataObjectService.java | 285 +++++++++++++++++++++
13 files changed, 530 insertions(+), 68 deletions(-)
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
index c264294e32..40e1100520 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
@@ -80,6 +80,12 @@ public interface JobTemplateMetaMapper {
@SelectProvider(type = JobTemplateMetaSQLProviderFactory.class, method =
"selectJobTemplateById")
JobTemplatePO selectJobTemplateById(Long jobTemplateId);
+ @SelectProvider(
+ type = JobTemplateMetaSQLProviderFactory.class,
+ method = "listJobTemplatePOsByJobTemplateIds")
+ List<JobTemplatePO> listJobTemplatePOsByJobTemplateIds(
+ @Param("jobTemplateIds") List<Long> jobTemplateIds);
+
@SelectProvider(
type = JobTemplateMetaSQLProviderFactory.class,
method = "batchSelectJobTemplateByIdentifier")
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
index 0598a9a9a0..04383da401 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
@@ -104,6 +104,11 @@ public class JobTemplateMetaSQLProviderFactory {
return getProvider().selectJobTemplateById(jobTemplateId);
}
+ public static String listJobTemplatePOsByJobTemplateIds(
+ @Param("jobTemplateIds") List<Long> jobTemplateIds) {
+ return getProvider().listJobTemplatePOsByJobTemplateIds(jobTemplateIds);
+ }
+
public static String batchSelectJobTemplateByIdentifier(
@Param("metalakeName") String metalakeName,
@Param("jobTemplateNames") List<String> jobTemplateNames) {
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaMapper.java
index e2e35cc6e0..9862c5f151 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaMapper.java
@@ -159,6 +159,19 @@ public interface PolicyMetaMapper {
@SelectProvider(type = PolicyMetaSQLProviderFactory.class, method =
"selectPolicyByPolicyId")
PolicyPO selectPolicyByPolicyId(@Param("policyId") Long policyId);
+ @Results({
+ @Result(property = "policyId", column = "policy_id"),
+ @Result(property = "policyName", column = "policy_name"),
+ @Result(property = "policyType", column = "policy_type"),
+ @Result(property = "metalakeId", column = "metalake_id"),
+ @Result(property = "auditInfo", column = "audit_info"),
+ @Result(property = "currentVersion", column = "current_version"),
+ @Result(property = "lastVersion", column = "last_version"),
+ @Result(property = "deletedAt", column = "deleted_at")
+ })
+ @SelectProvider(type = PolicyMetaSQLProviderFactory.class, method =
"listPolicyPOsByPolicyIds")
+ List<PolicyPO> listPolicyPOsByPolicyIds(@Param("policyIds") List<Long>
policyIds);
+
@Results({
@Result(property = "policyId", column = "policy_id"),
@Result(property = "policyName", column = "policy_name"),
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaSQLProviderFactory.java
index afb257227a..4f7b1d1177 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/PolicyMetaSQLProviderFactory.java
@@ -100,6 +100,10 @@ public class PolicyMetaSQLProviderFactory {
return getProvider().selectPolicyByPolicyId(policyId);
}
+ public static String listPolicyPOsByPolicyIds(@Param("policyIds") List<Long>
policyIds) {
+ return getProvider().listPolicyPOsByPolicyIds(policyIds);
+ }
+
public static String batchSelectPolicyByIdentifier(
@Param("metalakeName") String metalakeName, @Param("policyNames")
List<String> policyNames) {
return getProvider().batchSelectPolicyByIdentifier(metalakeName,
policyNames);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaMapper.java
index 7732c8b1a0..68cb3d721f 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaMapper.java
@@ -80,6 +80,9 @@ public interface TagMetaMapper {
@SelectProvider(type = TagMetaSQLProviderFactory.class, method =
"selectTagByTagId")
TagPO selectTagByTagId(@Param("tagId") Long tagId);
+ @SelectProvider(type = TagMetaSQLProviderFactory.class, method =
"listTagPOsByTagIds")
+ List<TagPO> listTagPOsByTagIds(@Param("tagIds") List<Long> tagIds);
+
@SelectProvider(type = TagMetaSQLProviderFactory.class, method =
"batchSelectTagByIdentifier")
List<TagPO> batchSelectTagByIdentifier(
@Param("metalakeName") String metalakeName, @Param("tagNames")
List<String> tagNames);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaSQLProviderFactory.java
index 7273279e6b..1dde368e69 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TagMetaSQLProviderFactory.java
@@ -106,6 +106,10 @@ public class TagMetaSQLProviderFactory {
return getProvider().selectTagByTagId(tagId);
}
+ public static String listTagPOsByTagIds(@Param("tagIds") List<Long> tagIds) {
+ return getProvider().listTagPOsByTagIds(tagIds);
+ }
+
public static String batchSelectTagByIdentifier(
@Param("metalakeName") String metalakeName, @Param("tagNames")
List<String> tagNames) {
return getProvider().batchSelectTagByIdentifier(metalakeName, tagNames);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
index 782dd362a8..807eba1cdc 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
@@ -163,6 +163,26 @@ public class JobTemplateMetaBaseSQLProvider {
+ " AND jtm.deleted_at = 0";
}
+ public String listJobTemplatePOsByJobTemplateIds(
+ @Param("jobTemplateIds") List<Long> jobTemplateIds) {
+ return "<script>"
+ + "SELECT jtm.job_template_id AS jobTemplateId, jtm.job_template_name
AS jobTemplateName,"
+ + " jtm.metalake_id AS metalakeId, jtm.job_template_comment AS
jobTemplateComment,"
+ + " jtm.job_template_content AS jobTemplateContent, jtm.audit_info AS
auditInfo,"
+ + " jtm.current_version AS currentVersion, jtm.last_version AS
lastVersion,"
+ + " jtm.deleted_at AS deletedAt"
+ + " FROM "
+ + JobTemplateMetaMapper.TABLE_NAME
+ + " jtm"
+ + " WHERE jtm.deleted_at = 0"
+ + " AND jtm.job_template_id IN ("
+ + "<foreach collection='jobTemplateIds' item='jobTemplateId'
separator=','>"
+ + "#{jobTemplateId}"
+ + "</foreach>"
+ + ")"
+ + "</script>";
+ }
+
public String batchSelectJobTemplateByIdentifier(
@Param("metalakeName") String metalakeName,
@Param("jobTemplateNames") List<String> jobTemplateNames) {
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetaBaseSQLProvider.java
index fa25c66aec..c0f6b21f6d 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetaBaseSQLProvider.java
@@ -184,6 +184,23 @@ public class PolicyMetaBaseSQLProvider {
+ " AND pm.deleted_at = 0 ";
}
+ public String listPolicyPOsByPolicyIds(@Param("policyIds") List<Long>
policyIds) {
+ return "<script>"
+ + "SELECT pm.policy_id, pm.policy_name, pm.policy_type,
pm.metalake_id,"
+ + " pm.audit_info, pm.current_version, pm.last_version,"
+ + " pm.deleted_at"
+ + " FROM "
+ + POLICY_META_TABLE_NAME
+ + " pm"
+ + " WHERE pm.deleted_at = 0"
+ + " AND pm.policy_id IN ("
+ + "<foreach collection='policyIds' item='policyId' separator=','>"
+ + "#{policyId}"
+ + "</foreach>"
+ + ")"
+ + "</script>";
+ }
+
public String selectPolicyMetaByMetalakeIdAndName(
@Param("metalakeId") Long metalakeId, @Param("policyName") String
policyName) {
return "SELECT pm.policy_id, pm.policy_name, pm.policy_type,
pm.metalake_id,"
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TagMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TagMetaBaseSQLProvider.java
index 74dbfaeeb7..8aac66bf88 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TagMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TagMetaBaseSQLProvider.java
@@ -226,6 +226,27 @@ public class TagMetaBaseSQLProvider {
+ " WHERE tag_id = #{tagId} and deleted_at = 0";
}
+ public String listTagPOsByTagIds(@Param("tagIds") List<Long> tagIds) {
+ return "<script>"
+ + "SELECT tag_id as tagId, tag_name as tagName,"
+ + " metalake_id as metalakeId,"
+ + " tag_comment as comment,"
+ + " properties as properties,"
+ + " audit_info as auditInfo,"
+ + " current_version as currentVersion,"
+ + " last_version as lastVersion,"
+ + " deleted_at as deletedAt"
+ + " FROM "
+ + TAG_TABLE_NAME
+ + " WHERE deleted_at = 0"
+ + " AND tag_id IN ("
+ + "<foreach collection='tagIds' item='tagId' separator=','>"
+ + "#{tagId}"
+ + "</foreach>"
+ + ")"
+ + "</script>";
+ }
+
public String batchSelectTagByIdentifier(
@Param("metalakeName") String metalakeName, @Param("tagNames")
List<String> tagNames) {
return "<script>"
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
index 06656ba964..efb7c7e8d3 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
@@ -50,10 +50,13 @@ import
org.apache.gravitino.storage.relational.mapper.ViewMetaMapper;
import org.apache.gravitino.storage.relational.po.CatalogPO;
import org.apache.gravitino.storage.relational.po.ColumnPO;
import org.apache.gravitino.storage.relational.po.FilesetPO;
+import org.apache.gravitino.storage.relational.po.JobTemplatePO;
import org.apache.gravitino.storage.relational.po.MetalakePO;
import org.apache.gravitino.storage.relational.po.ModelPO;
+import org.apache.gravitino.storage.relational.po.PolicyPO;
import org.apache.gravitino.storage.relational.po.SchemaPO;
import org.apache.gravitino.storage.relational.po.TablePO;
+import org.apache.gravitino.storage.relational.po.TagPO;
import org.apache.gravitino.storage.relational.po.TopicPO;
import org.apache.gravitino.storage.relational.po.ViewPO;
import org.apache.gravitino.storage.relational.utils.SessionUtils;
@@ -92,17 +95,23 @@ public class MetadataObjectService {
private static Map<Long, String> getPolicyObjectsFullName(List<Long>
policyIds) {
if (policyIds == null || policyIds.isEmpty()) {
- return Map.of();
+ return Maps.newHashMap();
+ }
+
+ List<PolicyPO> policyPOs =
+ SessionUtils.getWithoutCommit(
+ PolicyMetaMapper.class, mapper ->
mapper.listPolicyPOsByPolicyIds(policyIds));
+
+ if (policyPOs == null || policyPOs.isEmpty()) {
+ return Maps.newHashMap();
}
- return policyIds.stream()
- .collect(
- Collectors.toMap(
- policyId -> policyId,
- policyId ->
- SessionUtils.getWithoutCommit(
- PolicyMetaMapper.class,
- policyMetaMapper ->
-
policyMetaMapper.selectPolicyByPolicyId(policyId).getPolicyName())));
+
+ HashMap<Long, String> policyIdAndNameMap = new HashMap<>();
+
+ policyPOs.forEach(
+ policyPO -> policyIdAndNameMap.put(policyPO.getPolicyId(),
policyPO.getPolicyName()));
+
+ return policyIdAndNameMap;
}
private static Map<Long, String> getJobObjectsFullName(List<Long> jobIds) {
@@ -119,31 +128,43 @@ public class MetadataObjectService {
return Maps.newHashMap();
}
- return jobTemplateIds.stream()
- .collect(
- Collectors.toMap(
- jobTemplateId -> jobTemplateId,
- jobTemplateId ->
- SessionUtils.getWithoutCommit(
- JobTemplateMetaMapper.class,
- jobTemplateMetaMapper ->
- jobTemplateMetaMapper
- .selectJobTemplateById(jobTemplateId)
- .jobTemplateName())));
+ List<JobTemplatePO> jobTemplatePOs =
+ SessionUtils.getWithoutCommit(
+ JobTemplateMetaMapper.class,
+ mapper ->
mapper.listJobTemplatePOsByJobTemplateIds(jobTemplateIds));
+
+ if (jobTemplatePOs == null || jobTemplatePOs.isEmpty()) {
+ return Maps.newHashMap();
+ }
+
+ HashMap<Long, String> jobTemplateIdAndNameMap = new HashMap<>();
+
+ jobTemplatePOs.forEach(
+ jobTemplatePO ->
+ jobTemplateIdAndNameMap.put(
+ jobTemplatePO.jobTemplateId(),
jobTemplatePO.jobTemplateName()));
+
+ return jobTemplateIdAndNameMap;
}
private static Map<Long, String> getTagObjectsFullName(List<Long> tagIds) {
if (tagIds == null || tagIds.isEmpty()) {
- return Map.of();
+ return Maps.newHashMap();
+ }
+
+ List<TagPO> tagPOs =
+ SessionUtils.getWithoutCommit(
+ TagMetaMapper.class, mapper -> mapper.listTagPOsByTagIds(tagIds));
+
+ if (tagPOs == null || tagPOs.isEmpty()) {
+ return Maps.newHashMap();
}
- return tagIds.stream()
- .collect(
- Collectors.toMap(
- tagId -> tagId,
- tagId ->
- SessionUtils.getWithoutCommit(
- TagMetaMapper.class,
- tagMetaMapper ->
tagMetaMapper.selectTagByTagId(tagId).getTagName())));
+
+ HashMap<Long, String> tagIdAndNameMap = new HashMap<>();
+
+ tagPOs.forEach(tagPO -> tagIdAndNameMap.put(tagPO.getTagId(),
tagPO.getTagName()));
+
+ return tagIdAndNameMap;
}
private MetadataObjectService() {}
@@ -317,12 +338,16 @@ public class MetadataObjectService {
metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
baseMetricName = "getTableObjectsFullName")
public static Map<Long, String> getTableObjectsFullName(List<Long> tableIds)
{
+ if (tableIds == null || tableIds.isEmpty()) {
+ return Maps.newHashMap();
+ }
+
List<TablePO> tablePOs =
SessionUtils.getWithoutCommit(
TableMetaMapper.class, mapper ->
mapper.listTablePOsByTableIds(tableIds));
if (tablePOs == null || tablePOs.isEmpty()) {
- return new HashMap<>();
+ return Maps.newHashMap();
}
List<Long> schemaIds =
tablePOs.stream().map(TablePO::getSchemaId).collect(Collectors.toList());
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
index c34ec55491..00a9a5aed4 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
@@ -43,24 +43,30 @@ import org.apache.gravitino.authorization.SecurableObjects;
import org.apache.gravitino.file.Fileset;
import org.apache.gravitino.integration.test.util.CloseContainerExtension;
import org.apache.gravitino.integration.test.util.PrintFuncNameExtension;
+import org.apache.gravitino.job.ShellJobTemplate;
+import org.apache.gravitino.job.SparkJobTemplate;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.meta.FilesetEntity;
import org.apache.gravitino.meta.GenericEntity;
import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.JobTemplateEntity;
import org.apache.gravitino.meta.ModelEntity;
import org.apache.gravitino.meta.PolicyEntity;
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.policy.Policy;
+import org.apache.gravitino.policy.PolicyContent;
import org.apache.gravitino.policy.PolicyContents;
import org.apache.gravitino.storage.RandomIdGenerator;
import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
+import org.apache.gravitino.utils.NamespaceUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance;
@@ -402,6 +408,14 @@ public abstract class TestJDBCBackend {
.build();
}
+ protected TableEntity createAndInsertTableEntity(Namespace namespace, String
name)
+ throws IOException {
+ TableEntity tableEntity =
+ createTableEntity(RandomIdGenerator.INSTANCE.nextId(), namespace,
name, AUDIT_INFO);
+ backend.insert(tableEntity, false);
+ return tableEntity;
+ }
+
protected FilesetEntity createFilesetEntity(
Long id, Namespace namespace, String name, AuditInfo auditInfo) {
return FilesetEntity.builder()
@@ -541,4 +555,86 @@ public abstract class TestJDBCBackend {
createAndInsertCatalog(metalakeName, catalogName);
createAndInsertSchema(metalakeName, catalogName, schemaName);
}
+
+ protected static JobTemplateEntity newShellJobTemplateEntity(
+ String name, String comment, String metalake) {
+ ShellJobTemplate shellJobTemplate =
+ ShellJobTemplate.builder()
+ .withName(name)
+ .withComment(comment)
+ .withExecutable("/bin/echo")
+ .build();
+
+ return JobTemplateEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withName(name)
+ .withNamespace(NamespaceUtil.ofJobTemplate(metalake))
+
.withTemplateContent(JobTemplateEntity.TemplateContent.fromJobTemplate(shellJobTemplate))
+ .withAuditInfo(AUDIT_INFO)
+ .build();
+ }
+
+ protected static JobTemplateEntity newSparkJobTemplateEntity(
+ String name, String comment, String metalake) {
+ SparkJobTemplate sparkJobTemplate =
+ SparkJobTemplate.builder()
+ .withName(name)
+ .withComment(comment)
+ .withClassName("org.apache.spark.examples.SparkPi")
+ .withExecutable("file:/path/to/spark-examples.jar")
+ .build();
+
+ return JobTemplateEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withName(name)
+ .withNamespace(NamespaceUtil.ofJobTemplate(metalake))
+
.withTemplateContent(JobTemplateEntity.TemplateContent.fromJobTemplate(sparkJobTemplate))
+ .withAuditInfo(AUDIT_INFO)
+ .build();
+ }
+
+ protected JobTemplateEntity createAndInsertShellJobTemplateEntity(
+ String name, String comment, String metalake) throws IOException {
+ JobTemplateEntity jobTemplateEntity = newShellJobTemplateEntity(name,
comment, metalake);
+ backend.insert(jobTemplateEntity, false);
+ return jobTemplateEntity;
+ }
+
+ protected JobTemplateEntity createAndInsertSparkJobTemplateEntity(
+ String name, String comment, String metalake) throws IOException {
+ JobTemplateEntity jobTemplateEntity = newSparkJobTemplateEntity(name,
comment, metalake);
+ backend.insert(jobTemplateEntity, false);
+ return jobTemplateEntity;
+ }
+
+ protected TagEntity createAndInsertTagEntity(String name, String comment,
String metalake)
+ throws IOException {
+ TagEntity tagEntity =
+ TagEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withName(name)
+ .withNamespace(NamespaceUtil.ofTag(metalake))
+ .withComment(comment)
+ .withAuditInfo(AUDIT_INFO)
+ .build();
+ backend.insert(tagEntity, false);
+ return tagEntity;
+ }
+
+ protected PolicyEntity createAndInsertPolicyEntity(
+ String policyName, String comment, PolicyContent policyContent, String
metalake)
+ throws IOException {
+ PolicyEntity policyEntity =
+ PolicyEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withName(policyName)
+ .withNamespace(NamespaceUtil.ofPolicy(metalake))
+ .withComment(comment)
+ .withPolicyType(Policy.BuiltInType.CUSTOM)
+ .withContent(policyContent)
+ .withAuditInfo(AUDIT_INFO)
+ .build();
+ backend.insert(policyEntity, false);
+ return policyEntity;
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
index 76ca0203a1..3dfe80f408 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
@@ -24,8 +24,6 @@ import java.util.List;
import org.apache.gravitino.EntityAlreadyExistsException;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.job.JobHandle;
-import org.apache.gravitino.job.ShellJobTemplate;
-import org.apache.gravitino.job.SparkJobTemplate;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.JobEntity;
@@ -278,41 +276,6 @@ public class TestJobTemplateMetaService extends
TestJDBCBackend {
updatedJobTemplateEntity.nameIdentifier(), e ->
duplicateNameJobTemplateEntity));
}
- static JobTemplateEntity newShellJobTemplateEntity(String name, String
comment, String metalake) {
- ShellJobTemplate shellJobTemplate =
- ShellJobTemplate.builder()
- .withName(name)
- .withComment(comment)
- .withExecutable("/bin/echo")
- .build();
-
- return JobTemplateEntity.builder()
- .withId(RandomIdGenerator.INSTANCE.nextId())
- .withName(name)
- .withNamespace(NamespaceUtil.ofJobTemplate(metalake))
-
.withTemplateContent(JobTemplateEntity.TemplateContent.fromJobTemplate(shellJobTemplate))
- .withAuditInfo(AUDIT_INFO)
- .build();
- }
-
- static JobTemplateEntity newSparkJobTemplateEntity(String name, String
comment, String metalake) {
- SparkJobTemplate sparkJobTemplate =
- SparkJobTemplate.builder()
- .withName(name)
- .withComment(comment)
- .withClassName("org.apache.spark.examples.SparkPi")
- .withExecutable("file:/path/to/spark-examples.jar")
- .build();
-
- return JobTemplateEntity.builder()
- .withId(RandomIdGenerator.INSTANCE.nextId())
- .withName(name)
- .withNamespace(NamespaceUtil.ofJobTemplate(metalake))
-
.withTemplateContent(JobTemplateEntity.TemplateContent.fromJobTemplate(sparkJobTemplate))
- .withAuditInfo(AUDIT_INFO)
- .build();
- }
-
static JobEntity newJobEntity(String templateName, JobHandle.Status status,
String metalake) {
return JobEntity.builder()
.withId(RandomIdGenerator.INSTANCE.nextId())
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetadataObjectService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetadataObjectService.java
new file mode 100644
index 0000000000..4d14e21b5c
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetadataObjectService.java
@@ -0,0 +1,285 @@
+/*
+ * 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.storage.relational.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.meta.GenericEntity;
+import org.apache.gravitino.meta.JobTemplateEntity;
+import org.apache.gravitino.meta.PolicyEntity;
+import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.TagEntity;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.storage.relational.TestJDBCBackend;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+
+public class TestMetadataObjectService extends TestJDBCBackend {
+ private static final String METALAKE_NAME =
"metalake_for_metadata_object_test";
+
+ private final Set<MetadataObject.Type> supportedObjectTypes =
+ ImmutableSet.of(
+ MetadataObject.Type.CATALOG,
+ MetadataObject.Type.SCHEMA,
+ MetadataObject.Type.TABLE,
+ MetadataObject.Type.FILESET,
+ MetadataObject.Type.MODEL,
+ MetadataObject.Type.TOPIC);
+ private final PolicyContent policyContent =
+ PolicyContents.custom(ImmutableMap.of("field", 123),
supportedObjectTypes, null);
+
+ @BeforeEach
+ public void prepare() throws IOException {
+ createAndInsertMakeLake(METALAKE_NAME);
+ }
+
+ @TestTemplate
+ public void testBatchGetPolicyObjectsFullName() throws IOException {
+
+ // Create multiple policies
+ String policyOne = "policy_one";
+ String policyTwo = "policy_two";
+ PolicyEntity policy1 =
+ createAndInsertPolicyEntity(policyOne, "test policy 1", policyContent,
METALAKE_NAME);
+ createAndInsertPolicyEntity(policyTwo, "test policy 2", policyContent,
METALAKE_NAME);
+
+ // Batch query using MetadataObjectService to fetch only one Entity
belongs to policy1
+ List<GenericEntity> genericEntities =
+ List.of(
+ GenericEntity.builder()
+ .withId(policy1.id())
+ .withName(policy1.name())
+ .withNamespace(policy1.namespace())
+ .withEntityType(Entity.EntityType.POLICY)
+ .build());
+
+ // Call MetadataObjectService.fromGenericEntities which will internally
call
+ // listPolicyPOsByPolicyIds
+ List<MetadataObject> metadataObjects =
+ MetadataObjectService.fromGenericEntities(genericEntities);
+
+ // Verify results
+ assertEquals(1, metadataObjects.size());
+ Set<String> policyNames =
+
metadataObjects.stream().map(MetadataObject::name).collect(Collectors.toSet());
+ assertTrue(policyNames.contains(policyOne));
+
+ // Verify all are POLICY type
+ assertTrue(metadataObjects.stream().allMatch(obj -> obj.type() ==
MetadataObject.Type.POLICY));
+ }
+
+ @TestTemplate
+ public void testBatchGetMixedObjectsFullName() throws IOException {
+ String policyOne = "policy_one";
+ String policyTwo = "policy_two";
+ String catalogOne = "catalog_one";
+ PolicyEntity policy1 =
+ createAndInsertPolicyEntity(policyOne, "test policy 1", policyContent,
METALAKE_NAME);
+ createAndInsertPolicyEntity(policyTwo, "test policy 2", policyContent,
METALAKE_NAME);
+ CatalogEntity catalog = createAndInsertCatalog(METALAKE_NAME, catalogOne);
+
+ List<GenericEntity> mixedEntities =
+ List.of(
+ GenericEntity.builder()
+ .withId(policy1.id())
+ .withName(policy1.name())
+ .withNamespace(policy1.namespace())
+ .withEntityType(Entity.EntityType.POLICY)
+ .build(),
+ GenericEntity.builder()
+ .withId(catalog.id())
+ .withName(catalog.name())
+ .withNamespace(catalog.namespace())
+ .withEntityType(Entity.EntityType.CATALOG)
+ .build());
+
+ List<MetadataObject> mixedResult =
MetadataObjectService.fromGenericEntities(mixedEntities);
+ assertEquals(2, mixedResult.size());
+
+ Map<MetadataObject.Type, Long> typeCounts =
+ mixedResult.stream()
+ .collect(Collectors.groupingBy(MetadataObject::type,
Collectors.counting()));
+ assertEquals(1L, typeCounts.get(MetadataObject.Type.POLICY));
+ assertEquals(1L, typeCounts.get(MetadataObject.Type.CATALOG));
+
+ // Verify names match
+ MetadataObject policyObject =
+ mixedResult.stream()
+ .filter(obj -> obj.type() == MetadataObject.Type.POLICY)
+ .findFirst()
+ .orElseThrow();
+ assertEquals(policyOne, policyObject.name());
+
+ MetadataObject catalogObject =
+ mixedResult.stream()
+ .filter(obj -> obj.type() == MetadataObject.Type.CATALOG)
+ .findFirst()
+ .orElseThrow();
+ assertEquals(catalogOne, catalogObject.name());
+ }
+
+ @TestTemplate
+ public void testBatchGetMetadataObjectsFullNameWithEmptyList() {
+ // Test with empty list - should return empty result
+ List<MetadataObject> emptyResult =
MetadataObjectService.fromGenericEntities(List.of());
+ assertEquals(0, emptyResult.size());
+ }
+
+ @TestTemplate
+ public void testBatchGetTagObjectsFullName() throws IOException {
+
+ // Create multiple tags
+ String tagOne = "tag_one";
+ String tagTwo = "tag_two";
+ TagEntity tag1 = createAndInsertTagEntity(tagOne, "test tag 1",
METALAKE_NAME);
+ TagEntity tag2 = createAndInsertTagEntity(tagTwo, "test tag 2",
METALAKE_NAME);
+ // Test batch query using MetadataObjectService
+ List<GenericEntity> genericEntities =
+ List.of(
+ GenericEntity.builder()
+ .withId(tag1.id())
+ .withName(tag1.name())
+ .withNamespace(tag1.namespace())
+ .withEntityType(Entity.EntityType.TAG)
+ .build(),
+ GenericEntity.builder()
+ .withId(tag2.id())
+ .withName(tag2.name())
+ .withNamespace(tag2.namespace())
+ .withEntityType(Entity.EntityType.TAG)
+ .build());
+
+ List<MetadataObject> metadataObjects =
+ MetadataObjectService.fromGenericEntities(genericEntities);
+
+ // Verify results
+ assertEquals(2, metadataObjects.size());
+ Set<String> tagNames =
+
metadataObjects.stream().map(MetadataObject::name).collect(Collectors.toSet());
+ Set<String> expected = Set.of(tagOne, tagTwo);
+ assertEquals(expected, tagNames);
+
+ // Verify all are TAG type
+ assertTrue(metadataObjects.stream().allMatch(obj -> obj.type() ==
MetadataObject.Type.TAG));
+ }
+
+ @TestTemplate
+ public void testBatchGetJobTemplateObjectsFullName() throws IOException {
+ // Create multiple job templates
+ String shellTemplate = "shell_template";
+ String sparkTemplate = "spark_template";
+ JobTemplateEntity shellJObTemplate =
+ createAndInsertShellJobTemplateEntity(shellTemplate, "A shell job
template", METALAKE_NAME);
+ JobTemplateEntity sparkJobTemplate =
+ createAndInsertSparkJobTemplateEntity(sparkTemplate, "A spark job
template", METALAKE_NAME);
+
+ // Test batch query using MetadataObjectService
+ List<GenericEntity> jobTemplateEntities =
+ List.of(
+ GenericEntity.builder()
+ .withId(shellJObTemplate.id())
+ .withName(shellJObTemplate.name())
+ .withNamespace(shellJObTemplate.namespace())
+ .withEntityType(Entity.EntityType.JOB_TEMPLATE)
+ .build(),
+ GenericEntity.builder()
+ .withId(sparkJobTemplate.id())
+ .withName(sparkJobTemplate.name())
+ .withNamespace(sparkJobTemplate.namespace())
+ .withEntityType(Entity.EntityType.JOB_TEMPLATE)
+ .build());
+
+ List<MetadataObject> metadataObjects =
+ MetadataObjectService.fromGenericEntities(jobTemplateEntities);
+
+ // Verify results
+ assertEquals(2, metadataObjects.size());
+ Set<String> jobTemplateNames =
+
metadataObjects.stream().map(MetadataObject::name).collect(Collectors.toSet());
+
+ Set<String> expected = Set.of(shellTemplate, sparkTemplate);
+ assertEquals(expected, jobTemplateNames);
+
+ // Verify all are JOB_TEMPLATE type
+ assertTrue(
+ metadataObjects.stream().allMatch(obj -> obj.type() ==
MetadataObject.Type.JOB_TEMPLATE));
+ }
+
+ @TestTemplate
+ public void testBatchGetTableObjectsFullName() throws IOException {
+ // Create catalog and schema
+ String catalogName = "test_catalog";
+ String schemaName = "test_schema";
+ String testTableOne = "test_table_one";
+ String testTableTwo = "test_table_two";
+ Namespace namespace = Namespace.of(METALAKE_NAME, catalogName, schemaName);
+ createAndInsertCatalog(METALAKE_NAME, catalogName);
+ createAndInsertSchema(METALAKE_NAME, catalogName, schemaName);
+ // Create multiple tables
+ TableEntity table1 = createAndInsertTableEntity(namespace, testTableOne);
+ TableEntity table2 = createAndInsertTableEntity(namespace, testTableTwo);
+
+ // Test batch query using MetadataObjectService
+ List<GenericEntity> tableEntities =
+ List.of(
+ GenericEntity.builder()
+ .withId(table1.id())
+ .withName(table1.name())
+ .withNamespace(table1.namespace())
+ .withEntityType(Entity.EntityType.TABLE)
+ .build(),
+ GenericEntity.builder()
+ .withId(table2.id())
+ .withName(table2.name())
+ .withNamespace(table2.namespace())
+ .withEntityType(Entity.EntityType.TABLE)
+ .build());
+
+ List<MetadataObject> metadataObjects =
MetadataObjectService.fromGenericEntities(tableEntities);
+
+ // Verify results
+ assertEquals(2, metadataObjects.size());
+
+ // Table full names include catalog.schema.table format
+ Set<String> tableFullNames =
+
metadataObjects.stream().map(MetadataObject::fullName).collect(Collectors.toSet());
+
+ Set<String> expected =
+ Set.of(
+ catalogName + "." + schemaName + "." + testTableOne,
+ catalogName + "." + schemaName + "." + testTableTwo);
+ assertEquals(expected, tableFullNames);
+
+ // Verify all are TABLE type
+ assertTrue(metadataObjects.stream().allMatch(obj -> obj.type() ==
MetadataObject.Type.TABLE));
+ }
+}