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

bharos 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 3ca87d839e [MINOR] fix(core): Handle NoSuchEntityException in 
JDBCBackend.batchGet for USER/GROUP/ROLE/VIEW (#10933)
3ca87d839e is described below

commit 3ca87d839eb5eeee6571b9ec95977e51ee3c78db
Author: Bharath Krishna <[email protected]>
AuthorDate: Thu Apr 30 20:15:48 2026 -0700

    [MINOR] fix(core): Handle NoSuchEntityException in JDBCBackend.batchGet for 
USER/GROUP/ROLE/VIEW (#10933)
    
    ### What changes were proposed in this pull request?
    
    Handle partial failures gracefully in `JDBCBackend.batchGet()` for USER,
    GROUP, ROLE, and VIEW entity types by catching `NoSuchEntityException`
    per entity instead of letting it abort the entire batch.
    
    ### Why are the changes needed?
    
    `batchGet()` for USER/GROUP/ROLE/VIEW iterates entities individually
    (with a TODO for real batch SQL). If any entity does not exist,
    `NoSuchEntityException` is thrown immediately, aborting the batch. This
    is inconsistent with other entity types (TABLE, SCHEMA, METALAKE, etc.)
    that use `WHERE name IN (...)` SQL and simply return fewer rows for
    missing entities. Callers expecting partial results (e.g., resolving
    group-inherited roles) would fail on the first missing entity.
    
    ### Does this PR introduce any user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Added `testBatchGetGroupsPartialResults`,
    `testBatchGetUsersPartialResults`, and `testBatchGetRolesPartialResults`
    tests in `TestJDBCBackendBatchGet` that insert entities, then batch-get
    a mix of existing and nonexistent identifiers, verifying only existing
    entities are returned.
---
 .../gravitino/storage/relational/JDBCBackend.java  |  26 ++++-
 .../relational/TestJDBCBackendBatchGet.java        | 117 +++++++++++++++++++++
 2 files changed, 138 insertions(+), 5 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index 43c7fe2335..b0f11c150e 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -351,28 +351,44 @@ public class JDBCBackend implements RelationalBackend {
         return (List<E>)
             
JobTemplateMetaService.getInstance().batchGetJobTemplateByIdentifier(identifiers);
       case USER:
-        // TODO: I will add batch operations for users, groups and views
+        // TODO: Add true batch SQL operations for users, groups, roles, and 
views
         List<E> users = Lists.newArrayList();
         for (NameIdentifier identifier : identifiers) {
-          users.add((E) 
UserMetaService.getInstance().getUserByIdentifier(identifier));
+          try {
+            users.add((E) 
UserMetaService.getInstance().getUserByIdentifier(identifier));
+          } catch (NoSuchEntityException e) {
+            LOG.debug("Skipping missing user during batch get: {}", 
identifier.name());
+          }
         }
         return users;
       case GROUP:
         List<E> groups = Lists.newArrayList();
         for (NameIdentifier identifier : identifiers) {
-          groups.add((E) 
GroupMetaService.getInstance().getGroupByIdentifier(identifier));
+          try {
+            groups.add((E) 
GroupMetaService.getInstance().getGroupByIdentifier(identifier));
+          } catch (NoSuchEntityException e) {
+            LOG.debug("Skipping missing group during batch get: {}", 
identifier.name());
+          }
         }
         return groups;
       case ROLE:
         List<E> roles = Lists.newArrayList();
         for (NameIdentifier identifier : identifiers) {
-          roles.add((E) 
RoleMetaService.getInstance().getRoleByIdentifier(identifier));
+          try {
+            roles.add((E) 
RoleMetaService.getInstance().getRoleByIdentifier(identifier));
+          } catch (NoSuchEntityException e) {
+            LOG.debug("Skipping missing role during batch get: {}", 
identifier.name());
+          }
         }
         return roles;
       case VIEW:
         List<E> views = Lists.newArrayList();
         for (NameIdentifier identifier : identifiers) {
-          views.add((E) 
ViewMetaService.getInstance().getViewByIdentifier(identifier));
+          try {
+            views.add((E) 
ViewMetaService.getInstance().getViewByIdentifier(identifier));
+          } catch (NoSuchEntityException e) {
+            LOG.debug("Skipping missing view during batch get: {}", 
identifier.name());
+          }
         }
         return views;
       default:
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackendBatchGet.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackendBatchGet.java
index d80f433f7b..327ba0fcb4 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackendBatchGet.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackendBatchGet.java
@@ -33,14 +33,18 @@ import org.apache.gravitino.job.JobTemplate;
 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.JobEntity;
 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.TableEntity;
 import org.apache.gravitino.meta.TagEntity;
 import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.meta.ViewEntity;
 import org.apache.gravitino.storage.RandomIdGenerator;
 import org.apache.gravitino.utils.NamespaceUtil;
 import org.junit.jupiter.api.Assertions;
@@ -847,4 +851,117 @@ public class TestJDBCBackendBatchGet extends 
TestJDBCBackend {
     Assertions.assertEquals(1, result.size());
     Assertions.assertEquals("catalog_single", result.get(0).name());
   }
+
+  @TestTemplate
+  public void testBatchGetGroupsPartialResults() throws IOException {
+    String metalakeName = "metalake_for_group_partial";
+    createAndInsertMakeLake(metalakeName);
+
+    GroupEntity group1 =
+        createGroupEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            NamespaceUtil.ofGroup(metalakeName),
+            "group1",
+            AUDIT_INFO,
+            Lists.newArrayList(),
+            Lists.newArrayList());
+    backend.insert(group1, false);
+
+    // Non-existing first to verify the loop continues past the failure
+    List<NameIdentifier> identifiers =
+        Lists.newArrayList(
+            NameIdentifier.of(NamespaceUtil.ofGroup(metalakeName), 
"nonexistent_group"),
+            group1.nameIdentifier());
+
+    List<GroupEntity> result = backend.batchGet(identifiers, 
Entity.EntityType.GROUP);
+
+    Assertions.assertEquals(1, result.size());
+    Assertions.assertEquals("group1", result.get(0).name());
+  }
+
+  @TestTemplate
+  public void testBatchGetUsersPartialResults() throws IOException {
+    String metalakeName = "metalake_for_user_partial";
+    createAndInsertMakeLake(metalakeName);
+
+    UserEntity user1 =
+        createUserEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            NamespaceUtil.ofUser(metalakeName),
+            "user1",
+            AUDIT_INFO);
+    backend.insert(user1, false);
+
+    // Non-existing first to verify the loop continues past the failure
+    List<NameIdentifier> identifiers =
+        Lists.newArrayList(
+            NameIdentifier.of(NamespaceUtil.ofUser(metalakeName), 
"nonexistent_user"),
+            user1.nameIdentifier());
+
+    List<UserEntity> result = backend.batchGet(identifiers, 
Entity.EntityType.USER);
+
+    Assertions.assertEquals(1, result.size());
+    Assertions.assertEquals("user1", result.get(0).name());
+  }
+
+  @TestTemplate
+  public void testBatchGetRolesPartialResults() throws IOException {
+    String metalakeName = "metalake_for_role_partial";
+    createAndInsertMakeLake(metalakeName);
+
+    CatalogEntity catalog =
+        createCatalog(
+            RandomIdGenerator.INSTANCE.nextId(),
+            NamespaceUtil.ofCatalog(metalakeName),
+            "catalog_for_roles",
+            AUDIT_INFO);
+    backend.insert(catalog, false);
+
+    RoleEntity role1 =
+        createRoleEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            NamespaceUtil.ofRole(metalakeName),
+            "role1",
+            AUDIT_INFO,
+            "catalog_for_roles");
+    backend.insert(role1, false);
+
+    // Non-existing first to verify the loop continues past the failure
+    List<NameIdentifier> identifiers =
+        Lists.newArrayList(
+            NameIdentifier.of(NamespaceUtil.ofRole(metalakeName), 
"nonexistent_role"),
+            role1.nameIdentifier());
+
+    List<RoleEntity> result = backend.batchGet(identifiers, 
Entity.EntityType.ROLE);
+
+    Assertions.assertEquals(1, result.size());
+    Assertions.assertEquals("role1", result.get(0).name());
+  }
+
+  @TestTemplate
+  public void testBatchGetViewsPartialResults() throws IOException {
+    String metalakeName = "metalake_for_view_partial";
+    String catalogName = "catalog_for_view_partial";
+    String schemaName = "schema_for_view_partial";
+    createParentEntities(metalakeName, catalogName, schemaName, AUDIT_INFO);
+
+    ViewEntity view1 =
+        createViewEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            NamespaceUtil.ofView(metalakeName, catalogName, schemaName),
+            "view1");
+    backend.insert(view1, false);
+
+    // Non-existing first to verify the loop continues past the failure
+    List<NameIdentifier> identifiers =
+        Lists.newArrayList(
+            NameIdentifier.of(
+                NamespaceUtil.ofView(metalakeName, catalogName, schemaName), 
"nonexistent_view"),
+            view1.nameIdentifier());
+
+    List<ViewEntity> result = backend.batchGet(identifiers, 
Entity.EntityType.VIEW);
+
+    Assertions.assertEquals(1, result.size());
+    Assertions.assertEquals("view1", result.get(0).name());
+  }
 }

Reply via email to