mbutrovich commented on code in PR #15470:
URL: https://github.com/apache/iceberg/pull/15470#discussion_r3453869904


##########
spark/v4.1/spark/src/test/java/org/apache/iceberg/spark/actions/TestRewriteTablePathsAction.java:
##########
@@ -629,26 +632,222 @@ public void testPositionDeletesDeduplication() throws 
Exception {
     // in a new manifest, which will cause duplicate DeleteFile objects when 
processing
     tableWithPosDeletes.newRowDelta().addDeletes(positionDeletes).commit();
 
-    // This should NOT throw AlreadyExistsException - the fix uses 
DeleteFileSet to deduplicate
-    // Without the fix (using Collectors.toSet()), this would fail because:
-    // 1. Both manifests contain entries for the same delete file
-    // 2. Processing returns two different DeleteFile objects for the same file
-    // 3. HashSet doesn't deduplicate them (DeleteFile doesn't override 
equals())
-    // 4. rewritePositionDeletes tries to write the same file twice -> 
AlreadyExistsException
+    // This should NOT throw AlreadyExistsException
     RewriteTablePath.Result result =
         actions()
             .rewriteTablePath(tableWithPosDeletes)
             .stagingLocation(stagingLocation())
             .rewriteLocationPrefix(tableWithPosDeletes.location(), 
targetTableLocation())
             .execute();
 
-    // Verify the rewrite completed successfully - should have rewritten 
exactly 1 delete file
-    // (the duplicate should be deduplicated by DeleteFileSet)
     assertThat(result.rewrittenDeleteFilePathsCount())
         .as("Should have rewritten exactly 1 delete file after deduplication")
         .isEqualTo(1);
   }
 
+  // Regression test: when the same position delete file is referenced from 
manifests in different

Review Comment:
   Done. Ported the new tests to v3.5 and v4.0; the three modules are identical.



##########
spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/actions/RewriteTablePathSparkAction.java:
##########
@@ -682,27 +704,120 @@ private static RewriteResult<DeleteFile> 
writeDeleteManifest(
           specsById,
           sourcePrefix,
           targetPrefix,
-          stagingLocation);
+          stagingLocation,
+          rewrittenDeleteFileSizes.value());
     } catch (IOException e) {
       throw new RuntimeIOException(e);
     }
   }
 
-  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+  /**
+   * Enumerate the distinct position delete files referenced by the delete 
manifests being
+   * rewritten. Equality delete files are excluded because they hold no 
absolute paths and are not
+   * rewritten.
+   */
+  private Set<DeleteFile> positionDeletesToRewrite(Set<ManifestFile> 
metaFiles) {
+    Set<ManifestFile> deleteManifests =
+        metaFiles.stream()
+            .filter(manifest -> manifest.content() == ManifestContent.DELETES)
+            .collect(Collectors.toSet());
+    if (deleteManifests.isEmpty()) {
+      return Collections.emptySet();
+    }
+
+    Encoder<ManifestFile> manifestFileEncoder = 
Encoders.javaSerialization(ManifestFile.class);
+    Dataset<ManifestFile> manifestDS =
+        spark().createDataset(Lists.newArrayList(deleteManifests), 
manifestFileEncoder);
+    Encoder<DeleteFile> deleteFileEncoder = 
Encoders.javaSerialization(DeleteFile.class);
+
+    List<DeleteFile> referencedDeleteFiles =
+        manifestDS
+            .repartition(deleteManifests.size())
+            .flatMap(positionDeletesInManifest(tableBroadcast()), 
deleteFileEncoder)
+            .collectAsList();
+
+    // DeleteFile does not override equals(); DeleteFileSet dedupes by path so 
a delete file shared
+    // across manifests is rewritten only once.
+    DeleteFileSet distinct = DeleteFileSet.create();
+    distinct.addAll(referencedDeleteFiles);
+    return distinct;
+  }
+
+  private static FlatMapFunction<ManifestFile, DeleteFile> 
positionDeletesInManifest(
+      Broadcast<Table> tableArg) {
+    return manifestFile -> {
+      Table table = tableArg.getValue();
+      List<DeleteFile> deleteFiles = Lists.newArrayList();
+      try (ManifestReader<DeleteFile> reader =
+          ManifestFiles.readDeleteManifest(manifestFile, table.io(), 
table.specs())) {
+        for (DeleteFile deleteFile : reader) {
+          if (deleteFile.content() == FileContent.POSITION_DELETES) {
+            deleteFiles.add(deleteFile.copy());
+          }
+        }
+      }
+      return deleteFiles.iterator();
+    };
+  }
+
+  /**
+   * Rewrite the given position delete files in parallel, returning a map from 
each source delete
+   * file path to the size of its rewritten file.
+   */
+  private Map<String, Long> rewritePositionDeletes(Set<DeleteFile> toRewrite) {
     if (toRewrite.isEmpty()) {
-      return;
+      return Collections.emptyMap();
     }
 
     Encoder<DeleteFile> deleteFileEncoder = 
Encoders.javaSerialization(DeleteFile.class);
-    Dataset<DeleteFile> deleteFileDs =
+    Dataset<DeleteFile> deleteFileDS =
         spark().createDataset(Lists.newArrayList(toRewrite), 
deleteFileEncoder);
 
     PositionDeleteReaderWriter posDeleteReaderWriter = new 
SparkPositionDeleteReaderWriter();
-    deleteFileDs
-        .repartition(toRewrite.size())
-        .foreach(
-            rewritePositionDelete(
-                tableBroadcast(), sourcePrefix, targetPrefix, stagingDir, 
posDeleteReaderWriter));
+    List<Tuple2<String, Long>> rewrittenSizes =
+        deleteFileDS
+            .repartition(toRewrite.size())
+            .map(
+                rewritePositionDelete(
+                    tableBroadcast(),
+                    sourcePrefix,
+                    targetPrefix,
+                    stagingDir,
+                    posDeleteReaderWriter),
+                Encoders.tuple(Encoders.STRING(), Encoders.LONG()))
+            .collectAsList();
+
+    Map<String, Long> sizesBySourcePath = Maps.newHashMap();

Review Comment:
   Done. `Maps.newHashMapWithExpectedSize(...)`.



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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to