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());
+ }
}