yuqi1129 commented on code in PR #12781:
URL: https://github.com/apache/gravitino/pull/12781#discussion_r3904183010


##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TagMetaService.java:
##########
@@ -156,27 +174,47 @@ public <E extends Entity & HasIdentifier> TagEntity 
updateTag(
 
   @Monitored(metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME, 
baseMetricName = "deleteTag")
   public boolean deleteTag(NameIdentifier identifier) {
-    String metalakeName = identifier.namespace().level(0);
-    int[] tagDeletedCount = new int[] {0};
-    int[] tagMetadataObjectRelDeletedCount = new int[] {0};
+    TagPO tagPO;
+    try {
+      tagPO = getTagPOByMetalakeAndName(identifier.namespace().level(0), 
identifier.name());
+    } catch (NoSuchEntityException e) {
+      return false;
+    }
+    return deleteTag(identifier, tagPO);
+  }
+
+  boolean deleteTag(NameIdentifier identifier, TagPO tagPO) {
+    long tagId = tagPO.getTagId();
 
     SessionUtils.doMultipleWithCommit(

Review Comment:
   Documented rather than restructured. `deleteTag` now carries a comment 
stating that the version-checked delete must stay first, because that is what 
decides whether the delete wins and a losing delete rolls the transaction back 
before any dependent row is touched.
   
   `OccWriteSupport.deleteChildrenWithVersions` does not fit these five calls: 
it asserts that the affected row count equals the size of a known child list, 
while these are blanket soft deletes for one tag ID where zero rows is a 
legitimate outcome (a tag with no assignments, no policies, no owner and no 
securable object). The comment says that too, so the next reader does not reach 
for the helper by reflex.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TagMetaService.java:
##########
@@ -156,27 +174,47 @@ public <E extends Entity & HasIdentifier> TagEntity 
updateTag(
 
   @Monitored(metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME, 
baseMetricName = "deleteTag")
   public boolean deleteTag(NameIdentifier identifier) {
-    String metalakeName = identifier.namespace().level(0);
-    int[] tagDeletedCount = new int[] {0};
-    int[] tagMetadataObjectRelDeletedCount = new int[] {0};
+    TagPO tagPO;
+    try {
+      tagPO = getTagPOByMetalakeAndName(identifier.namespace().level(0), 
identifier.name());
+    } catch (NoSuchEntityException e) {
+      return false;
+    }
+    return deleteTag(identifier, tagPO);
+  }
+
+  boolean deleteTag(NameIdentifier identifier, TagPO tagPO) {
+    long tagId = tagPO.getTagId();
 
     SessionUtils.doMultipleWithCommit(
+        () -> deleteTagWithVersion(identifier, tagPO),
         () ->

Review Comment:
   Added `testDeleteTagCleansEveryDependentRelation`: it sets up an assignment 
on a catalog, a policy-tag relation, a policy applied to the tag itself, an 
owner relation and a role whose securable object names the tag, asserts one 
active row in each of the five tables, deletes the tag and asserts all five are 
gone. A wrong mapper method or a wrong id/type argument in any of the four new 
cascade calls now fails CI.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TagMetaService.java:
##########
@@ -625,6 +678,66 @@ private static void validateAllowedValue(TagPO tagPO, 
TagValue tagValue)
         Arrays.toString(allowedValues));
   }
 
+  void lockMetalakeForTagCreate(MetalakePO observedMetalakePO) {
+    OccWriteSupport.lockParentForChildWrite(
+        observedMetalakePO.getMetalakeName(),
+        Entity.EntityType.METALAKE,
+        () ->
+            SessionUtils.getWithoutCommit(
+                MetalakeMetaMapper.class,
+                mapper ->
+                    
mapper.selectMetalakeMetaByIdForShare(observedMetalakePO.getMetalakeId())),
+        null,
+        current -> Objects.equals(current.getMetalakeName(), 
observedMetalakePO.getMetalakeName()));
+  }
+
+  private void deleteTagWithVersion(NameIdentifier identifier, TagPO 
observedTagPO) {
+    OccWriteSupport.deleteWithVersion(
+        () ->
+            SessionUtils.getWithoutCommit(
+                TagMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteTagMetaByIdAndVersion(
+                        observedTagPO.getTagId(), 
observedTagPO.getCurrentVersion())),
+        () -> tagWriteFailure(identifier, observedTagPO));
+  }
+
+  private RuntimeException tagWriteFailure(NameIdentifier identifier, TagPO 
observedTagPO) {
+    return OccWriteSupport.writeFailure(
+        identifier,
+        Entity.EntityType.TAG,
+        () ->
+            SessionUtils.getWithoutCommit(
+                TagMetaMapper.class,
+                mapper -> 
mapper.selectTagByTagIdForUpdate(observedTagPO.getTagId())),
+        null,
+        current ->
+            Objects.equals(current.getTagName(), observedTagPO.getTagName())
+                && Objects.equals(current.getMetalakeId(), 
observedTagPO.getMetalakeId()));
+  }
+
+  private List<TagPO> lockTagsForAssignment(List<TagPO> observedTagPOs) {

Review Comment:
   All three points addressed.
   
   reuse/altitude: `lockTagsForAssignment` and `PolicyTagRelService.lockTag` 
both route through a new `TagMetaService.lockTags`, which classifies through 
`OccWriteSupport.lockParentForChildWrite`. The three hand-rolled copies are now 
one call site of the shared helper.
   
   efficiency: `lockTags` issues a single `listTagPOsByTagIdsForUpdate` (`WHERE 
tag_id IN (...) ORDER BY tag_id FOR UPDATE`) and matches the locked rows 
against the observed ones, so a metadata object with 10 tags costs one round 
trip instead of ten, with the same tag-ID lock order.
   
   test-coverage: the missing-row classification is covered by 
`testDeleteOfAlreadyDeletedTagReportsMissingTagNotConflict` and by 
`TestOccWriteSupport`. A tag renamed by another transaction in the middle of 
this one still is not covered: it needs two connections interleaved inside one 
transaction, which this suite has no harness for.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TagMetaService.java:
##########
@@ -156,27 +174,47 @@ public <E extends Entity & HasIdentifier> TagEntity 
updateTag(
 
   @Monitored(metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME, 
baseMetricName = "deleteTag")
   public boolean deleteTag(NameIdentifier identifier) {
-    String metalakeName = identifier.namespace().level(0);
-    int[] tagDeletedCount = new int[] {0};
-    int[] tagMetadataObjectRelDeletedCount = new int[] {0};
+    TagPO tagPO;
+    try {
+      tagPO = getTagPOByMetalakeAndName(identifier.namespace().level(0), 
identifier.name());
+    } catch (NoSuchEntityException e) {
+      return false;
+    }
+    return deleteTag(identifier, tagPO);
+  }
+
+  boolean deleteTag(NameIdentifier identifier, TagPO tagPO) {

Review Comment:
   Fixed. The package-private overload now sits below the public methods, 
before the private helpers.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TagMetaService.java:
##########
@@ -625,6 +678,66 @@ private static void validateAllowedValue(TagPO tagPO, 
TagValue tagValue)
         Arrays.toString(allowedValues));
   }
 
+  void lockMetalakeForTagCreate(MetalakePO observedMetalakePO) {

Review Comment:
   Added `testTagCreateIsFencedByParentMetalake`, which asserts both 
`insertTag(tag, false)` and `insertTag(tag, true)` fail with 
`NoSuchEntityException` when the parent metalake is not there.
   
   The narrower branch where the metalake is renamed or dropped by another 
transaction between the lookup and the locking read needs two interleaved 
connections, which this suite cannot express; the classification itself is 
covered by `TestOccWriteSupport.testLockParentForChildWriteNotFoundThrows` and 
`...IdentityMismatchThrows`.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TagMetaService.java:
##########
@@ -625,6 +678,66 @@ private static void validateAllowedValue(TagPO tagPO, 
TagValue tagValue)
         Arrays.toString(allowedValues));
   }
 
+  void lockMetalakeForTagCreate(MetalakePO observedMetalakePO) {
+    OccWriteSupport.lockParentForChildWrite(
+        observedMetalakePO.getMetalakeName(),
+        Entity.EntityType.METALAKE,
+        () ->
+            SessionUtils.getWithoutCommit(
+                MetalakeMetaMapper.class,
+                mapper ->
+                    
mapper.selectMetalakeMetaByIdForShare(observedMetalakePO.getMetalakeId())),
+        null,
+        current -> Objects.equals(current.getMetalakeName(), 
observedMetalakePO.getMetalakeName()));
+  }
+
+  private void deleteTagWithVersion(NameIdentifier identifier, TagPO 
observedTagPO) {
+    OccWriteSupport.deleteWithVersion(
+        () ->
+            SessionUtils.getWithoutCommit(
+                TagMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteTagMetaByIdAndVersion(
+                        observedTagPO.getTagId(), 
observedTagPO.getCurrentVersion())),
+        () -> tagWriteFailure(identifier, observedTagPO));
+  }
+
+  private RuntimeException tagWriteFailure(NameIdentifier identifier, TagPO 
observedTagPO) {

Review Comment:
   Added `testDeleteOfAlreadyDeletedTagReportsMissingTagNotConflict`: it 
captures the observed row, deletes the tag, then replays the delete with the 
stale row and asserts `NoSuchEntityException` rather than 
`OptimisticLockException`, which is exactly the mis-classification you describe.



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