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

roryqi 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 3b4d1d9dab [#12532] improvement(core): support group owners in 
batchGetOwner (#12533)
3b4d1d9dab is described below

commit 3b4d1d9dab6a0296d02e293c6a13e1c4d2e384d5
Author: roryqi <[email protected]>
AuthorDate: Thu Aug 20 21:13:56 2026 +0800

    [#12532] improvement(core): support group owners in batchGetOwner (#12533)
    
    ### What changes were proposed in this pull request?
    
    - Add `GroupOwnerRelPO` for mapping group owners with their metadata
    object IDs.
    - Add batch group-owner queries to the owner mapper and SQL provider.
    - Return both user and group owners from
    `OwnerMetaService#batchGetOwner`.
    - Add tests for group-only and mixed user/group ownership.
    
    ### Why are the changes needed?
    
    The batch owner lookup currently omits group-owned metadata objects,
    while the single-object owner lookup supports both user and group
    owners. This causes incomplete owner results for batch callers.
    
    Fix: #12532
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Batch owner lookup now returns group owners in addition to user
    owners. No public API signature is changed.
    
    ### How was this patch tested?
    
    ```shell
    ./gradlew :core:spotlessApply :core:test \
      --tests 
org.apache.gravitino.storage.relational.service.TestOwnerMetaService \
      -PskipITs -PskipDockerTests=false
    ```
    
    All 39 tests passed on H2, MySQL, and PostgreSQL.
---
 .../storage/relational/mapper/OwnerMetaMapper.java |  15 +++
 .../mapper/OwnerMetaSQLProviderFactory.java        |  14 +++
 .../provider/base/OwnerMetaBaseSQLProvider.java    |  35 +++++++
 .../storage/relational/po/GroupOwnerRelPO.java     |  33 +++++++
 .../relational/service/OwnerMetaService.java       |  26 ++++-
 .../relational/service/TestOwnerMetaService.java   | 107 +++++++++++++++++++++
 6 files changed, 228 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
index e6453f2b66..e4ef4b6096 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
@@ -19,6 +19,7 @@
 package org.apache.gravitino.storage.relational.mapper;
 
 import java.util.List;
+import org.apache.gravitino.storage.relational.po.GroupOwnerRelPO;
 import org.apache.gravitino.storage.relational.po.GroupPO;
 import org.apache.gravitino.storage.relational.po.OwnerRelForDeletion;
 import org.apache.gravitino.storage.relational.po.OwnerRelPO;
@@ -64,6 +65,20 @@ public interface OwnerMetaMapper {
       @Param("metadataObjectId") Long metadataObjectId,
       @Param("metadataObjectType") String metadataObjectType);
 
+  /**
+   * Selects group owners for the specified metadata objects.
+   *
+   * @param metadataObjectIds IDs of the metadata objects
+   * @param metadataObjectType type of the metadata objects
+   * @return group owners with their owned metadata object IDs
+   */
+  @SelectProvider(
+      type = OwnerMetaSQLProviderFactory.class,
+      method = "batchSelectGroupOwnerMetaByMetadataObjectIdAndType")
+  List<GroupOwnerRelPO> batchSelectGroupOwnerMetaByMetadataObjectIdAndType(
+      @Param("metadataObjectIds") List<Long> metadataObjectIds,
+      @Param("metadataObjectType") String metadataObjectType);
+
   @InsertProvider(type = OwnerMetaSQLProviderFactory.class, method = 
"insertOwnerRel")
   void insertOwnerRel(@Param("ownerRelPO") OwnerRelPO ownerRelPO);
 
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaSQLProviderFactory.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaSQLProviderFactory.java
index f73f93726f..5957c6d91b 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaSQLProviderFactory.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaSQLProviderFactory.java
@@ -115,6 +115,20 @@ public class OwnerMetaSQLProviderFactory {
         .batchSelectUserOwnerMetaByMetadataObjectIdAndType(metadataObjectIds, 
metadataObjectType);
   }
 
+  /**
+   * Builds SQL to select group owners for the specified metadata objects.
+   *
+   * @param metadataObjectIds IDs of the metadata objects
+   * @param metadataObjectType type of the metadata objects
+   * @return SQL for selecting group owners
+   */
+  public static String batchSelectGroupOwnerMetaByMetadataObjectIdAndType(
+      @Param("metadataObjectIds") List<Long> metadataObjectIds,
+      @Param("metadataObjectType") String metadataObjectType) {
+    return getProvider()
+        .batchSelectGroupOwnerMetaByMetadataObjectIdAndType(metadataObjectIds, 
metadataObjectType);
+  }
+
   public static String selectOwnerByMetadataObjectIdAndType(
       @Param("metadataObjectId") long metadataObjectId,
       @Param("metadataObjectType") String metadataObjectType) {
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/OwnerMetaBaseSQLProvider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/OwnerMetaBaseSQLProvider.java
index bee7d6014f..6e45922296 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/OwnerMetaBaseSQLProvider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/OwnerMetaBaseSQLProvider.java
@@ -115,6 +115,41 @@ public class OwnerMetaBaseSQLProvider {
         + " ot.deleted_at = 0 AND gt.deleted_at = 0";
   }
 
+  /**
+   * Builds SQL to select group owners for the specified metadata objects.
+   *
+   * @param metadataObjectIds IDs of the metadata objects
+   * @param metadataObjectType type of the metadata objects
+   * @return SQL for selecting group owners
+   */
+  public String batchSelectGroupOwnerMetaByMetadataObjectIdAndType(
+      @Param("metadataObjectIds") List<Long> metadataObjectIds,
+      @Param("metadataObjectType") String metadataObjectType) {
+    return "<script>"
+        + "SELECT ot.metadata_object_id as metadataObjectId,"
+        + "gt.group_id as groupId, "
+        + "gt.group_name as groupName, "
+        + "gt.metalake_id as metalakeId, "
+        + "gt.audit_info as auditInfo, "
+        + "gt.current_version as currentVersion, "
+        + "gt.last_version as lastVersion, "
+        + "gt.deleted_at as deletedAt "
+        + "FROM "
+        + OWNER_TABLE_NAME
+        + " ot LEFT JOIN "
+        + GroupMetaMapper.GROUP_TABLE_NAME
+        + " gt ON gt.group_id = ot.owner_id "
+        + "WHERE "
+        + "ot.metadata_object_type = #{metadataObjectType} "
+        + "AND ot.owner_type = 'GROUP' "
+        + "AND ot.metadata_object_id IN "
+        + "<foreach collection='metadataObjectIds' item='itemId' open='(' 
separator=',' close=')'>"
+        + "#{itemId}"
+        + "</foreach> "
+        + "AND ot.deleted_at = 0 AND gt.deleted_at = 0 "
+        + "</script>";
+  }
+
   public String insertOwnerRel(@Param("ownerRelPO") OwnerRelPO ownerRelPO) {
     return "INSERT INTO "
         + OWNER_TABLE_NAME
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/po/GroupOwnerRelPO.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/GroupOwnerRelPO.java
new file mode 100644
index 0000000000..714a12f28f
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/GroupOwnerRelPO.java
@@ -0,0 +1,33 @@
+/*
+ * 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.po;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/** The persistence object that combines a group owner with its owned metadata 
object ID. */
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class GroupOwnerRelPO extends GroupPO {
+
+  private Long metadataObjectId;
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
index 012d00abc4..2b261e35b3 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
@@ -35,9 +35,11 @@ import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.RelationalEntity;
 import org.apache.gravitino.SupportsRelationOperations;
 import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.meta.GroupEntity;
 import org.apache.gravitino.meta.UserEntity;
 import org.apache.gravitino.metrics.Monitored;
 import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
+import org.apache.gravitino.storage.relational.po.GroupOwnerRelPO;
 import org.apache.gravitino.storage.relational.po.GroupPO;
 import org.apache.gravitino.storage.relational.po.OwnerRelForDeletion;
 import org.apache.gravitino.storage.relational.po.OwnerRelPO;
@@ -139,8 +141,28 @@ public class OwnerMetaService {
           });
     }
 
-    // TODO: Add batch support for group owners when GroupOwnerRelPO and batch 
method are available
-    // For now, we only handle user owners in batch mode
+    // Get group owners
+    List<GroupOwnerRelPO> groupPOList =
+        SessionUtils.getWithoutCommit(
+            OwnerMetaMapper.class,
+            mapper ->
+                
mapper.batchSelectGroupOwnerMetaByMetadataObjectIdAndType(entityIds, 
type.name()));
+    if (CollectionUtils.isNotEmpty(groupPOList)) {
+      groupPOList.forEach(
+          groupPO -> {
+            GroupEntity groupEntity =
+                POConverters.fromGroupPO(
+                    groupPO,
+                    Collections.emptyList(),
+                    AuthorizationUtils.ofGroupNamespace(metalake));
+            result.add(
+                new RelationalEntity<>(
+                    SupportsRelationOperations.Type.OWNER_REL,
+                    nameIdentifierMap.get(groupPO.getMetadataObjectId()),
+                    type,
+                    groupEntity));
+          });
+    }
 
     return result;
   }
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
index fc57a734ee..675797c4bb 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
@@ -727,6 +727,113 @@ class TestOwnerMetaService extends TestJDBCBackend {
     }
   }
 
+  @TestTemplate
+  void testBatchGetOwnerWithGroupOwners() throws IOException {
+    createAndInsertMakeLake(METALAKE_NAME);
+    createAndInsertCatalog(METALAKE_NAME, CATALOG_NAME);
+    SchemaEntity schema1 = createAndInsertSchema(METALAKE_NAME, CATALOG_NAME, 
SCHEMA_NAME);
+    SchemaEntity schema2 = createAndInsertSchema(METALAKE_NAME, CATALOG_NAME, 
SCHEMA_NAME + "_2");
+    SchemaEntity schema3 = createAndInsertSchema(METALAKE_NAME, CATALOG_NAME, 
SCHEMA_NAME + "_3");
+
+    GroupEntity group1 =
+        createGroupEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofGroupNamespace(METALAKE_NAME),
+            "group1",
+            AUDIT_INFO);
+    GroupEntity group2 =
+        createGroupEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofGroupNamespace(METALAKE_NAME),
+            "group2",
+            AUDIT_INFO);
+    backend.insert(group1, false);
+    backend.insert(group2, false);
+
+    OwnerMetaService.getInstance()
+        .setOwner(schema1.nameIdentifier(), schema1.type(), 
group1.nameIdentifier(), group1.type());
+    OwnerMetaService.getInstance()
+        .setOwner(schema2.nameIdentifier(), schema2.type(), 
group2.nameIdentifier(), group2.type());
+
+    List<RelationalEntity<?>> relations =
+        OwnerMetaService.getInstance()
+            .batchGetOwner(
+                List.of(
+                    schema1.nameIdentifier(), schema2.nameIdentifier(), 
schema3.nameIdentifier()),
+                Entity.EntityType.SCHEMA);
+
+    Assertions.assertEquals(2, relations.size());
+    Map<NameIdentifier, NameIdentifier> sourceToTarget =
+        relations.stream()
+            .collect(
+                Collectors.toMap(
+                    RelationalEntity::source,
+                    relation -> relation.targetEntity().nameIdentifier()));
+    Assertions.assertEquals(group1.nameIdentifier(), 
sourceToTarget.get(schema1.nameIdentifier()));
+    Assertions.assertEquals(group2.nameIdentifier(), 
sourceToTarget.get(schema2.nameIdentifier()));
+    
Assertions.assertFalse(sourceToTarget.containsKey(schema3.nameIdentifier()));
+    relations.forEach(
+        relation -> {
+          Assertions.assertEquals(SupportsRelationOperations.Type.OWNER_REL, 
relation.type());
+          Assertions.assertEquals(Entity.EntityType.SCHEMA, 
relation.sourceType());
+          Assertions.assertEquals(Entity.EntityType.GROUP, 
relation.targetEntity().type());
+        });
+  }
+
+  @TestTemplate
+  void testBatchGetOwnerWithMixedOwnerTypes() throws IOException {
+    createAndInsertMakeLake(METALAKE_NAME);
+    createAndInsertCatalog(METALAKE_NAME, CATALOG_NAME);
+    SchemaEntity userOwnedSchema = createAndInsertSchema(METALAKE_NAME, 
CATALOG_NAME, SCHEMA_NAME);
+    SchemaEntity groupOwnedSchema =
+        createAndInsertSchema(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME + "_2");
+
+    UserEntity user =
+        createUserEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofUserNamespace(METALAKE_NAME),
+            "user",
+            AUDIT_INFO);
+    GroupEntity group =
+        createGroupEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofGroupNamespace(METALAKE_NAME),
+            "group",
+            AUDIT_INFO);
+    backend.insert(user, false);
+    backend.insert(group, false);
+
+    OwnerMetaService.getInstance()
+        .setOwner(
+            userOwnedSchema.nameIdentifier(),
+            userOwnedSchema.type(),
+            user.nameIdentifier(),
+            user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(
+            groupOwnedSchema.nameIdentifier(),
+            groupOwnedSchema.type(),
+            group.nameIdentifier(),
+            group.type());
+
+    List<RelationalEntity<?>> relations =
+        OwnerMetaService.getInstance()
+            .batchGetOwner(
+                List.of(userOwnedSchema.nameIdentifier(), 
groupOwnedSchema.nameIdentifier()),
+                Entity.EntityType.SCHEMA);
+
+    Assertions.assertEquals(2, relations.size());
+    Map<NameIdentifier, Entity.EntityType> ownerTypes =
+        relations.stream()
+            .collect(
+                Collectors.toMap(
+                    RelationalEntity::source, relation -> 
relation.targetEntity().type()));
+    Assertions.assertEquals(
+        Entity.EntityType.USER, 
ownerTypes.get(userOwnedSchema.nameIdentifier()));
+    Assertions.assertEquals(
+        Entity.EntityType.GROUP, 
ownerTypes.get(groupOwnedSchema.nameIdentifier()));
+  }
+
   @TestTemplate
   void testBatchGetOwnerWithEmptyInput() {
     List<RelationalEntity<?>> relations =

Reply via email to