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 =