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


##########
core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/OwnerMetaBaseSQLProvider.java:
##########
@@ -124,6 +125,35 @@ public String insertOwnerRel(@Param("ownerRelPO") 
OwnerRelPO ownerRelPO) {
         + ")";
   }
 
+  public String batchInsertOwnerRels(@Param("ownerRelPOs") List<OwnerRelPO> 
ownerRelPOs) {
+    return "<script>"
+        + "INSERT INTO "
+        + OWNER_TABLE_NAME
+        + " (metalake_id, metadata_object_id, metadata_object_type, owner_id, 
owner_type,"
+        + " audit_info, current_version, last_version, deleted_at, updated_at) 
VALUES "
+        + "<foreach collection='ownerRelPOs' item='po' separator=','>"
+        + "(#{po.metalakeId}, #{po.metadataObjectId}, 
#{po.metadataObjectType},"
+        + " #{po.ownerId}, #{po.ownerType}, #{po.auditInfo},"
+        + " #{po.currentVersion}, #{po.lastVersion}, #{po.deletedAt}, 
#{po.updatedAt})"
+        + "</foreach>"
+        + "</script>";
+  }
+
+  public String batchSoftDeleteOwnerRelByMetadataObjects(
+      @Param("deletions") List<OwnerRelForDeletion> deletions) {
+    return "<script>"
+        + "UPDATE "
+        + OWNER_TABLE_NAME
+        + " SET deleted_at = (UNIX_TIMESTAMP() * 1000.0)"

Review Comment:
   **MySQL soft-delete timestamp produces a `DOUBLE`, not `BIGINT`**
   
   `UNIX_TIMESTAMP()` (no argument) returns a plain integer — no sub-second 
precision. Adding `EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000` makes 
the expression a `DOUBLE`, which is stored into the `BIGINT` `deleted_at` 
column via an implicit cast. MySQL silently truncates, but this is fragile and 
inconsistent with the PostgreSQL override which uses `CAST(... AS BIGINT)` 
explicitly.
   
   Consider wrapping with `FLOOR()` to make the truncation explicit:
   ```sql
   SET deleted_at = FLOOR((UNIX_TIMESTAMP() * 1000.0)
       + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000)
   ```



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java:
##########
@@ -162,18 +169,69 @@ public List<SchemaEntity> 
listSchemasByNamespace(Namespace namespace) {
   public void insertSchema(SchemaEntity schemaEntity, boolean overwrite) 
throws IOException {
     try {
       NameIdentifierUtil.checkSchema(schemaEntity.nameIdentifier());
-
-      SchemaPO.Builder builder = SchemaPO.builder();
-      fillSchemaPOBuilderParentEntityId(builder, schemaEntity.namespace());
+      // Callers above this service (e.g. JDBCBackend + naming bridge) pass 
storage-form schema
+      // names: nested paths use the internal physical separator, not the 
external logical one.
+      String physicalSep = HierarchicalSchemaUtil.physicalSeparator();
+      String schemaName = schemaEntity.name();
+      List<SchemaEntity> rowsToInsert = new ArrayList<>();
+      if (schemaName == null || !schemaName.contains(physicalSep)) {
+        rowsToInsert.add(schemaEntity);
+      } else {
+        String[] parts = schemaName.split(Pattern.quote(physicalSep), -1);
+        for (int nSeg = 1; nSeg < parts.length; nSeg++) {
+          String ancestorPhysical = String.join(physicalSep, 
Arrays.copyOf(parts, nSeg));
+          SchemaEntity ancestor =
+              SchemaEntity.builder()
+                  .withId(nextIdForNestedAncestor())
+                  .withName(ancestorPhysical)
+                  .withNamespace(schemaEntity.namespace())
+                  .withComment(null)
+                  .withProperties(Collections.emptyMap())
+                  .withAuditInfo(schemaEntity.auditInfo())
+                  .build();
+          rowsToInsert.add(ancestor);
+        }
+        rowsToInsert.add(schemaEntity);
+      }
 
       SessionUtils.doWithCommit(
           SchemaMetaMapper.class,
           mapper -> {
-            SchemaPO po = 
POConverters.initializeSchemaPOWithVersion(schemaEntity, builder);
+            int n = rowsToInsert.size();
+            List<SchemaPO> missingAncestorPOs = new ArrayList<>();
+            if (n > 1) {
+              SchemaEntity firstAncestor = rowsToInsert.get(0);
+              Namespace ancestorNs = firstAncestor.namespace();
+              List<String> ancestorPhysicalNames =
+                  rowsToInsert.subList(0, n - 1).stream()
+                      .map(SchemaEntity::name)
+                      .collect(Collectors.toList());
+              Set<String> existingAncestorNames =
+                  mapper
+                      .batchSelectSchemaByIdentifier(
+                          ancestorNs.level(0), ancestorNs.level(1), 
ancestorPhysicalNames)
+                      .stream()
+                      .map(SchemaPO::getSchemaName)
+                      .collect(Collectors.toSet());
+              for (SchemaEntity row : rowsToInsert.subList(0, n - 1)) {
+                if (existingAncestorNames.contains(row.name())) {
+                  continue;
+                }
+                SchemaPO.Builder builder = SchemaPO.builder();
+                fillSchemaPOBuilderParentEntityId(builder, row.namespace());
+                
missingAncestorPOs.add(POConverters.initializeSchemaPOWithVersion(row, 
builder));
+              }
+            }
+            SchemaEntity leafRow = rowsToInsert.get(n - 1);
+            SchemaPO.Builder leafBuilder = SchemaPO.builder();
+            fillSchemaPOBuilderParentEntityId(leafBuilder, 
leafRow.namespace());
+            SchemaPO leafPO = 
POConverters.initializeSchemaPOWithVersion(leafRow, leafBuilder);
+            List<SchemaPO> schemaPosToInsert = new 
ArrayList<>(missingAncestorPOs);
+            schemaPosToInsert.add(leafPO);
             if (overwrite) {
-              mapper.insertSchemaMetaOnDuplicateKeyUpdate(po);
+              
mapper.batchInsertSchemaMetaOnDuplicateKeyUpdate(schemaPosToInsert);
             } else {
-              mapper.insertSchemaMeta(po);
+              mapper.batchInsertSchemaMeta(schemaPosToInsert);

Review Comment:
   **Ancestor insert race condition when `overwrite=false`**
   
   Two concurrent inserts of `A:B:C` and `A:B:D` will both call 
`batchSelectSchemaByIdentifier`, both find ancestor `A` missing, and both 
attempt `batchInsertSchemaMeta`. The second caller gets a duplicate-key 
exception. The `overwrite=true` path handles this correctly via upsert, but the 
`false` path does not.
   
   Catalog bootstrap scenarios (where many hierarchical schemas are imported in 
parallel) are particularly vulnerable. At minimum, document the known 
limitation. Ideally, catch the duplicate-key exception on ancestor rows 
specifically and treat it as a benign race.



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