danhuawang opened a new issue, #11297:
URL: https://github.com/apache/gravitino/issues/11297

   ### Version
   
   main branch
   
   ### Describe what's wrong
   
   After calling `revokePrivilegesFromRole` to remove the last (and only) 
privilege from a role's securable object on a schema, `listBindingRoleNames()` 
on that schema still returns the revoked role. The role should no longer appear 
because the securable object row has been soft-deleted in the database.
   
   This is a **general issue** affecting all schema types (both flat and 
hierarchical). It is not specific to hierarchical/nested namespace schemas.
   
   The root cause appears to be a cache invalidation gap: 
`RelationalEntityStore.update` invalidates the role entity cache 
(`cache.invalidate(roleIdent, ROLE)`) after the revoke, but the 
`METADATA_OBJECT_ROLE_REL` relation cache — keyed by the schema's identifier — 
is not properly invalidated. As a result, `listBindingRoleNames` reads stale 
cached relation data that still includes the revoked role.
   
   ### Error message and/or stacktrace
   
   ```
   org.opentest4j.AssertionFailedError: Flat schema should no longer have role 
after revoke,
   got: [hs_d2286d6c_role_flat] ==> expected: <false> but was: <true>
     at org.junit.jupiter.api.Assertions.assertFalse(Assertions.java:235)
     at org.apache.gravitino.integration.test.schema.TestHierarchicalSchemaIT
         .testRoleBindingRevokeOnFlatSchema(TestHierarchicalSchemaIT.java:1021)
   ```
   
   The same failure occurs for hierarchical schemas:
   
   ```
   org.opentest4j.AssertionFailedError: Level1 schema should no longer have 
role1 after revoke
   ==> expected: <false> but was: <true>
     at org.apache.gravitino.integration.test.schema.TestHierarchicalSchemaIT
         
.testRoleBindingForHierarchicalSchema(TestHierarchicalSchemaIT.java:965)
   ```
   
   ### How to reproduce
   
   1. Use Gravitino main branch with any catalog (Iceberg, Hive, etc.).
   2. Create a schema and a role:
      ```java
      icebergCatalog.asSchemas().createSchema("test_schema", "comment", 
Map.of());
      metalake.createRole("test_role", Map.of(), List.of());
      ```
   3. Grant a privilege to the role on the schema:
      ```java
      MetadataObject schemaObject = MetadataObjects.of(catalogName, 
"test_schema", MetadataObject.Type.SCHEMA);
      metalake.grantPrivilegesToRole("test_role", schemaObject, 
Set.of(Privileges.UseSchema.allow()));
      ```
   4. Verify the role is bound (this succeeds):
      ```java
      Schema schema = catalog.asSchemas().loadSchema("test_schema");
      String[] roles = schema.supportsRoles().listBindingRoleNames();
      // roles contains "test_role" ✓
      ```
   5. Revoke the privilege:
      ```java
      metalake.revokePrivilegesFromRole("test_role", schemaObject, 
Set.of(Privileges.UseSchema.allow()));
      ```
   6. Check binding again:
      ```java
      String[] rolesAfter = schema.supportsRoles().listBindingRoleNames();
      // rolesAfter STILL contains "test_role" ✗
      ```
   7. **Actual result**: `listBindingRoleNames` still returns `["test_role"]`.
   8. **Expected result**: `listBindingRoleNames` should return `[]` because 
the securable object has been removed from the role.
   
   ### Additional context
   
   **Affected code path**:
   - `RelationalEntityStore.update()` (line ~136) — invalidates 
`cache.invalidate(ident, entityType)` where ident is the role, not the schema.
   - `RoleMetaService.updateRole()` — correctly soft-deletes the securable 
object row via `batchSoftDeleteSecurableObjects`.
   - `RoleMetaService.listRolesByMetadataObject()` — queries via SQL JOIN on 
`securable_object` table with `se.deleted_at = 0`, which should exclude 
soft-deleted rows.
   - `CaffeineEntityCache` — the `METADATA_OBJECT_ROLE_REL` relation (keyed by 
schema identifier) is not invalidated when the role entity is updated.
   
   **Confirmed via control test**: A flat (non-hierarchical) schema exhibits 
the exact same behavior, proving this is not related to hierarchical namespace 
support.
   
   **Database layer is correct**: The 
`PermissionManager.updateRevokedSecurableObject` correctly removes the 
securable object when no privileges remain, and the SQL query in 
`listRolesByMetadataObjectIdAndType` filters by `se.deleted_at = 0`. The issue 
is that the cached relation result is served without re-querying the database.
   
   **Suggested fix**: When `RelationalEntityStore.update` is called for a 
`ROLE` entity type, it should also invalidate the `METADATA_OBJECT_ROLE_REL` 
relation cache entries for all metadata objects that were in the role's 
securable objects (both old and new). Alternatively, the 
`CaffeineEntityCache.invalidate(roleIdent, ROLE)` BFS traversal should follow 
the reverse index from role → securable objects → schema relation keys.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to