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]