This is an automated email from the ASF dual-hosted git repository.
yuqi1129 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 93edbe5abe [#14282] fix(core): Clean securable objects, tag relations
and policies when dropping a metalake (#14285)
93edbe5abe is described below
commit 93edbe5abe1fbef1f4853f7a676d82df4ed49cf9
Author: Qi Yu <[email protected]>
AuthorDate: Wed Oct 28 08:36:17 2026 +0800
[#14282] fix(core): Clean securable objects, tag relations and policies
when dropping a metalake (#14285)
### What changes were proposed in this pull request?
`MetalakeMetaService.deleteMetalake` now cleans securable objects and
tag relations before the roles and tags they hang off, on both the
cascade and the non-cascade path. The non-cascade path also soft-deletes
policies and policy versions.
### Why are the changes needed?
Both cleanup statements find their rows through a live role or tag, so
running them after the roles and tags were soft-deleted updated nothing.
The rows of a dropped metalake stayed live and were never collected, and
so did its policies after a non-cascade drop.
Fixes #14282
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
- New `TestMetalakeMetaService.testDeleteMetalakeRemovesGovernanceRows`
(cascade and non-cascade), which fails without the fix.
- `TestRoleMetaService.testDeleteRoleMetasByLegacyTimeline` now also
counts the securable object of each role among the rows hard-deleted
after the metalake drop. Before the fix the cascade drop left these rows
live, so the test didn't count them.
- `TestMetalakeMetaService`, `TestMetalakeManager`,
`TestRoleMetaService`, `TestUserMetaService`, `TestGroupMetaService`,
`TestTagMetaService` and `TestPolicyMetaService` pass on H2; MySQL and
PostgreSQL run in CI.
---
.../relational/service/MetalakeMetaService.java | 34 ++++++---
.../service/TestMetalakeMetaService.java | 82 ++++++++++++++++++++++
.../relational/service/TestRoleMetaService.java | 5 +-
3 files changed, 108 insertions(+), 13 deletions(-)
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
index 02f94121ae..1cdbc5b854 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
@@ -277,22 +277,24 @@ public class MetalakeMetaService {
SessionUtils.doWithoutCommit(
GroupMetaMapper.class,
mapper ->
mapper.softDeleteGroupMetasByMetalakeId(metalakeId)),
- () ->
- SessionUtils.doWithoutCommit(
- RoleMetaMapper.class,
- mapper ->
mapper.softDeleteRoleMetasByMetalakeId(metalakeId)),
+ // Securable objects and tag relations find their rows through a
live role or tag, so
+ // they are removed before the roles and tags themselves.
() ->
SessionUtils.doWithoutCommit(
SecurableObjectMapper.class,
mapper ->
mapper.softDeleteSecurableObjectsByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
- TagMetaMapper.class,
- mapper ->
mapper.softDeleteTagMetasByMetalakeId(metalakeId)),
+ RoleMetaMapper.class,
+ mapper ->
mapper.softDeleteRoleMetasByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
TagMetadataObjectRelMapper.class,
mapper ->
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ TagMetaMapper.class,
+ mapper ->
mapper.softDeleteTagMetasByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
PolicyTagRelMapper.class, mapper ->
mapper.softDeleteByMetalakeId(metalakeId)),
@@ -387,25 +389,35 @@ public class MetalakeMetaService {
SessionUtils.doWithoutCommit(
GroupMetaMapper.class,
mapper ->
mapper.softDeleteGroupMetasByMetalakeId(metalakeId)),
+ // Securable objects and tag relations find their rows through a
live role or tag, so
+ // they are removed before the roles and tags themselves.
+ () ->
+ SessionUtils.doWithoutCommit(
+ SecurableObjectMapper.class,
+ mapper ->
mapper.softDeleteSecurableObjectsByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
RoleMetaMapper.class,
mapper ->
mapper.softDeleteRoleMetasByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
- SecurableObjectMapper.class,
- mapper ->
mapper.softDeleteSecurableObjectsByMetalakeId(metalakeId)),
+ TagMetadataObjectRelMapper.class,
+ mapper ->
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
TagMetaMapper.class,
mapper ->
mapper.softDeleteTagMetasByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
- TagMetadataObjectRelMapper.class,
- mapper ->
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)),
+ PolicyTagRelMapper.class, mapper ->
mapper.softDeleteByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
- PolicyTagRelMapper.class, mapper ->
mapper.softDeleteByMetalakeId(metalakeId)),
+ PolicyMetaMapper.class,
+ mapper ->
mapper.softDeletePolicyMetasByMetalakeId(metalakeId)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ PolicyVersionMapper.class,
+ mapper ->
mapper.softDeletePolicyVersionsByMetalakeId(metalakeId)),
() ->
SessionUtils.doWithoutCommit(
OwnerMetaMapper.class,
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetalakeMetaService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetalakeMetaService.java
index ed560ee704..28d1693b1a 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetalakeMetaService.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestMetalakeMetaService.java
@@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
@@ -38,16 +40,24 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityAlreadyExistsException;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.Privileges;
+import org.apache.gravitino.authorization.SecurableObjects;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.exceptions.NonEmptyEntityException;
import org.apache.gravitino.exceptions.OptimisticLockException;
import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.meta.ColumnEntity;
+import org.apache.gravitino.meta.PolicyEntity;
+import org.apache.gravitino.meta.RoleEntity;
import org.apache.gravitino.meta.SchemaEntity;
import org.apache.gravitino.meta.SchemaVersion;
import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.TagEntity;
+import org.apache.gravitino.policy.PolicyContents;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.storage.RandomIdGenerator;
import org.apache.gravitino.storage.relational.TestJDBCBackend;
@@ -58,6 +68,7 @@ import org.apache.gravitino.storage.relational.po.SchemaPO;
import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
import org.apache.gravitino.storage.relational.utils.POConverters;
import org.apache.gravitino.storage.relational.utils.SessionUtils;
+import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.NamespaceUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Assertions;
@@ -469,6 +480,61 @@ public class TestMetalakeMetaService extends
TestJDBCBackend {
assertEquals(0, countActiveTableVersionRows(table.id()));
}
+ @TestTemplate
+ public void testDeleteMetalakeRemovesGovernanceRows() throws IOException {
+ for (boolean cascade : new boolean[] {true, false}) {
+ String metalakeName = METALAKE_NAME + (cascade ? "_cascade" :
"_non_cascade");
+ BaseMetalake metalake = createAndInsertMakeLake(metalakeName);
+
+ RoleEntity role =
+ createRoleEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ NamespaceUtil.ofRole(metalakeName),
+ "role",
+ AUDIT_INFO,
+ List.of(
+ SecurableObjects.ofMetalake(
+ metalakeName,
List.of(Privileges.CreateCatalog.allow()))),
+ null);
+ backend.insert(role, false);
+
+ // Tags attach to catalogs and below, and a non-cascade drop needs a
metalake without
+ // catalogs, so only the cascade drop can still have live tag relations.
+ TagEntity tag = createAndInsertTagEntity("tag", "tag", metalakeName);
+ if (cascade) {
+ CatalogEntity catalog = createAndInsertCatalog(metalakeName,
"catalog");
+ TagMetaService.getInstance()
+ .associateTagsWithMetadataObject(
+ catalog.nameIdentifier(),
+ Entity.EntityType.CATALOG,
+ new NameIdentifier[] {NameIdentifierUtil.ofTag(metalakeName,
tag.name())},
+ new NameIdentifier[0]);
+ }
+
+ PolicyEntity policy =
+ createAndInsertPolicyEntity(
+ "policy",
+ "policy",
+ PolicyContents.custom(
+ ImmutableMap.of("field", 1),
ImmutableSet.of(MetadataObject.Type.TABLE), null),
+ metalakeName);
+
+ assertEquals(1, countActiveRows("role_meta_securable_object", "role_id",
role.id()));
+ assertEquals(cascade ? 1 : 0, countActiveRows("tag_relation_meta",
"tag_id", tag.id()));
+ assertEquals(1, countActiveRows("policy_meta", "policy_id",
policy.id()));
+ assertEquals(1, countActiveRows("policy_version_info", "policy_id",
policy.id()));
+
+ assertTrue(
+
MetalakeMetaService.getInstance().deleteMetalake(metalake.nameIdentifier(),
cascade));
+
+ // Rows left with deleted_at = 0 are never collected by the legacy-data
cleanup.
+ assertEquals(0, countActiveRows("role_meta_securable_object", "role_id",
role.id()));
+ assertEquals(0, countActiveRows("tag_relation_meta", "tag_id",
tag.id()));
+ assertEquals(0, countActiveRows("policy_meta", "policy_id",
policy.id()));
+ assertEquals(0, countActiveRows("policy_version_info", "policy_id",
policy.id()));
+ }
+ }
+
@TestTemplate
public void testMetaLifeCycleFromCreationToDeletion() throws IOException {
// meta data creation
@@ -497,6 +563,22 @@ public class TestMetalakeMetaService extends
TestJDBCBackend {
assertFalse(legacyRecordExistsInDB(metalake.id(),
Entity.EntityType.METALAKE));
}
+ private int countActiveRows(String table, String idColumn, Long id) {
+ try (SqlSession sqlSession =
+
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ Connection connection = sqlSession.getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet rs =
+ statement.executeQuery(
+ String.format(
+ "SELECT count(*) FROM %s WHERE %s = %d AND deleted_at = 0",
+ table, idColumn, id))) {
+ return rs.next() ? rs.getInt(1) : 0;
+ } catch (SQLException e) {
+ throw new RuntimeException("SQL execution failed", e);
+ }
+ }
+
private int countActiveTableVersionRows(Long tableId) {
try (SqlSession sqlSession =
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
index a7fbd048bd..a203c371e4 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
@@ -1629,14 +1629,15 @@ class TestRoleMetaService extends TestJDBCBackend {
// hard delete after soft delete
deletedCount =
roleMetaService.deleteRoleMetasByLegacyTimeline(Instant.now().toEpochMilli() +
1000, 1);
- assertEquals(3, deletedCount); // delete 1 role + 1 userRoleRel + 1
groupRoleRel
+ // delete 1 role + 1 userRoleRel + 1 groupRoleRel + 1 securableObject
+ assertEquals(4, deletedCount);
assertEquals(1, countRoles(metalake.id())); // 2 - 1
assertEquals(1, countUserRoleRels()); // 2 - 1
assertEquals(1, countGroupRoleRels()); // 2 - 1
deletedCount =
roleMetaService.deleteRoleMetasByLegacyTimeline(Instant.now().toEpochMilli() +
1000, 1);
- assertEquals(3, deletedCount);
+ assertEquals(4, deletedCount);
assertEquals(0, countRoles(metalake.id()));
assertEquals(0, countUserRoleRels());
assertEquals(0, countGroupRoleRels());