jerryshao commented on code in PR #14326:
URL: https://github.com/apache/gravitino/pull/14326#discussion_r4476465470


##########
core/src/main/java/org/apache/gravitino/storage/relational/service/PolicyMetaService.java:
##########
@@ -499,6 +560,35 @@ private void updatePolicyRootWithVersion(
         () -> policyWriteFailure(identifier, oldPolicyPO));
   }
 
+  private void deleteHiddenTagWithoutCommit(PolicyPO policy, String metalake) {
+    TagPO tag =
+        SessionUtils.getWithoutCommit(
+            TagMetaMapper.class,
+            mapper ->
+                mapper.selectTagMetaByMetalakeIdAndNameForUpdate(
+                    policy.getMetalakeId(), 
HiddenPolicyTag.name(policy.getPolicyId())));
+    if (tag == null || !tag.isHidden()) {
+      return;
+    }
+    int deleted =
+        SessionUtils.getWithoutCommit(
+            TagMetaMapper.class,
+            mapper ->
+                mapper.softDeleteTagMetaByIdAndVersion(tag.getTagId(), 
tag.getCurrentVersion()));
+    Preconditions.checkState(
+        deleted == 1,
+        "Failed to delete hidden tag %s for policy %s",
+        tag.getTagName(),
+        policy.getPolicyId());
+    SessionUtils.doWithoutCommit(

Review Comment:
   [Minor] `deleteHiddenTagWithoutCommit` soft-deletes the tag and its 
`tag_relation_meta` rows, but not the owner or securable-object rows that 
`TagMetaService.deleteTag` clears (`TagMetaService.java:704-715`). Also, 
`OwnerManager.setOwner` and `PermissionManager.grantPrivilegesToRole` never 
check `hidden`. So a grant on `reserved_{policyId}` is stored on the role, 
shows up in privilege listings, and survives policy deletion. Could we reject 
hidden tags on those writes and soft-delete the same rows here?



##########
core/src/main/java/org/apache/gravitino/tag/TagManager.java:
##########
@@ -592,10 +619,21 @@ private TagEntity updateTagEntity(TagEntity tagEntity, 
TagChange... changes) {
         .build();
   }
 
+  private void checkVisibleTagIfExists(String metalake, String name) {
+    try {
+      loadTagEntity(metalake, name);
+    } catch (NoSuchTagException e) {
+      // Missing names retain the existing idempotent delete and association 
behavior.
+    }
+  }
+
   private TagEntity loadTagEntity(String metalake, String name) {
     try {
-      return entityStore.get(
-          NameIdentifierUtil.ofTag(metalake, name), Entity.EntityType.TAG, 
TagEntity.class);
+      TagEntity tag =
+          entityStore.get(
+              NameIdentifierUtil.ofTag(metalake, name), Entity.EntityType.TAG, 
TagEntity.class);
+      HiddenPolicyTag.checkVisible(tag);

Review Comment:
   [Nit] A hidden tag throws `IllegalArgumentException` here (400), while a 
missing tag is `NoSuchTagException` (404). That lets callers probe whether 
`reserved_{id}` exists through get, alter, delete, or associate. Since listing 
already filters hidden tags, consider returning not-found for hidden tags too.



##########
core/src/main/java/org/apache/gravitino/policy/PolicyManager.java:
##########
@@ -73,6 +78,114 @@ public PolicyManager(IdGenerator idGenerator, EntityStore 
entityStore) {
     this.objectPolicyResolver = new ObjectPolicyResolver(entityStore);
   }
 
+  /**
+   * Applies a policy to an object through its hidden tag using the entity 
store.
+   *
+   * <p>This internal operation is idempotent. Migration callers can group it 
with legacy cleanup in
+   * their storage transaction. It is not exposed through the policy 
dispatcher or client APIs.
+   *
+   * @param metalake The policy and object's metalake.
+   * @param policyName The policy name.
+   * @param object The metadata object to associate.
+   * @param auditInfo The audit information to preserve on newly created 
relations.
+   * @return The policy's hidden tag ID.
+   * @throws IOException If an entity or relation cannot be persisted.
+   */
+  public long addPolicyToObject(
+      String metalake, String policyName, MetadataObject object, Audit 
auditInfo)
+      throws IOException {
+    Preconditions.checkNotNull(auditInfo, "Policy binding audit information is 
required");
+    Preconditions.checkArgument(
+        SUPPORTED_METADATA_OBJECT_TYPES_FOR_TAGS.contains(object.type()),
+        "Unsupported policy binding object type: %s",
+        object.type());
+    checkMetalake(NameIdentifier.of(metalake), entityStore);
+    NameIdentifier policyIdent = NameIdentifierUtil.ofPolicy(metalake, 
policyName);
+    PolicyEntity policy =
+        entityStore.get(policyIdent, Entity.EntityType.POLICY, 
PolicyEntity.class);
+    NameIdentifier objectIdent = MetadataObjectUtil.toEntityIdent(metalake, 
object);
+    Entity.EntityType objectType = MetadataObjectUtil.toEntityType(object);
+    // Relation lookup validates the endpoint, including columns stored as 
part of a table.
+    entityStore
+        .relationOperations()
+        .listEntitiesByRelation(
+            SupportsRelationOperations.Type.TAG_METADATA_OBJECT_REL, 
objectIdent, objectType);
+    NameIdentifier tagIdent = NameIdentifierUtil.ofTag(metalake, 
HiddenPolicyTag.name(policy.id()));
+    TagEntity tag;
+    try {
+      tag = entityStore.get(tagIdent, Entity.EntityType.TAG, TagEntity.class);
+    } catch (NoSuchEntityException e) {
+      tag = HiddenPolicyTag.create(policy, idGenerator.nextId());
+      try {
+        entityStore.put(tag, false);
+      } catch (EntityAlreadyExistsException concurrentCreate) {
+        tag = entityStore.get(tagIdent, Entity.EntityType.TAG, 
TagEntity.class);
+      }
+    }
+    Preconditions.checkArgument(
+        tag.hidden()
+            && tag.valueConstraint().allowedValues() != null
+            && tag.valueConstraint().allowedValues().length == 0
+            && HiddenPolicyTag.create(policy, 
tag.id()).properties().equals(tag.properties()),
+        "Reserved tag name collision for policy %s",
+        policy.id());
+    List<RelationalEntity<?>> policies =
+        entityStore
+            .relationOperations()
+            .batchListEntitiesByRelation(
+                SupportsRelationOperations.Type.POLICY_TAG_REL,
+                Collections.singletonList(tagIdent),
+                Entity.EntityType.TAG);
+    if (policies.isEmpty()) {
+      try {
+        entityStore
+            .relationOperations()
+            .updateEntityRelations(
+                RelationUpdate.of(
+                        SupportsRelationOperations.Type.POLICY_TAG_REL,
+                        tagIdent,
+                        Entity.EntityType.TAG,
+                        new RelationEdgeTarget[] {
+                          RelationEdgeTarget.of(policyIdent, 
Entity.EntityType.POLICY, null)
+                        },
+                        null)
+                    .withAuditInfo(auditInfo));
+      } catch (EntityAlreadyExistsException concurrentCreate) {

Review Comment:
   [Minor] `PolicyTagRelService.insertIfAbsent` throws 
`OptimisticLockException` (not `EntityAlreadyExistsException`) when the insert 
affects zero rows and the follow-up read doesn't see the competing row yet 
(`PolicyTagRelService.java:308-316`). This catch misses that case, so the call 
fails before associating the object. A retry succeeds, and the new concurrency 
test doesn't cover this branch. Could we handle `OptimisticLockException` the 
same way: reload, accept a single selector-free link to this policy, then 
associate the object?



-- 
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