2323import java .io .BufferedReader ;
2424import java .io .InputStreamReader ;
2525import java .nio .charset .StandardCharsets ;
26+ import java .nio .file .Path ;
2627import java .util .ArrayList ;
2728import java .util .HashMap ;
2829import java .util .HashSet ;
3334import org .apache .iceberg .DataFile ;
3435import org .apache .iceberg .DataFiles ;
3536import org .apache .iceberg .FileFormat ;
37+ import org .apache .iceberg .GenericManifestFile ;
38+ import org .apache .iceberg .GenericPartitionFieldSummary ;
3639import org .apache .iceberg .HasTableOperations ;
40+ import org .apache .iceberg .InternalData ;
41+ import org .apache .iceberg .ManifestFile ;
3742import org .apache .iceberg .PartitionSpec ;
3843import org .apache .iceberg .RewriteTablePathUtil ;
3944import org .apache .iceberg .Schema ;
4449import org .apache .iceberg .TableMetadata .MetadataLogEntry ;
4550import org .apache .iceberg .actions .RewriteTablePath ;
4651import org .apache .iceberg .hadoop .HadoopTables ;
52+ import org .apache .iceberg .io .CloseableIterable ;
4753import org .apache .iceberg .types .Types ;
4854import org .apache .iceberg .util .Pair ;
4955import org .junit .jupiter .api .BeforeEach ;
@@ -68,11 +74,11 @@ class TestRewriteTablePathOzoneAction {
6874 private Table table = null ;
6975
7076 @ TempDir
71- private java . nio . file . Path tableDir ;
77+ private Path tableDir ;
7278 @ TempDir
73- private java . nio . file . Path targetDir ;
79+ private Path targetDir ;
7480 @ TempDir
75- private java . nio . file . Path stagingDir ;
81+ private Path stagingDir ;
7682
7783 @ BeforeEach
7884 public void setupTableLocation () {
@@ -96,7 +102,9 @@ void fullTablePathRewrite() throws Exception {
96102 }
97103
98104 Set <Pair <String , String >> csvPairs = readCsvPairs (table , result .fileListLocation ());
99- assertEquals (expectedTargets , csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ()));
105+ Set <String > actualTargets = csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ());
106+ assertTrue (actualTargets .containsAll (expectedTargets ),
107+ "Copy plan should contain all expected version file targets" );
100108
101109 // Verify all internal paths inside each staged metadata file are rewritten to target.
102110 assertAllInternalPathsRewritten (csvPairs , targetPrefix );
@@ -124,7 +132,9 @@ void tablePathRewriteForStartAndNoEndVersionProvided() throws Exception {
124132 }
125133
126134 Set <Pair <String , String >> csvPairs = readCsvPairs (table , result .fileListLocation ());
127- assertEquals (expectedTargets , csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ()));
135+ Set <String > actualTargets = csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ());
136+ assertTrue (actualTargets .containsAll (expectedTargets ),
137+ "Copy plan should contain all expected version file targets" );
128138
129139 // Verify all internal paths inside each staged metadata file are rewritten to target
130140 assertAllInternalPathsRewritten (csvPairs , targetPrefix );
@@ -152,7 +162,9 @@ void tablePathRewriteForOnlyEndVersionProvided() throws Exception {
152162 }
153163
154164 Set <Pair <String , String >> csvPairs = readCsvPairs (table , result .fileListLocation ());
155- assertEquals (expectedTargets , csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ()));
165+ Set <String > actualTargets = csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ());
166+ assertTrue (actualTargets .containsAll (expectedTargets ),
167+ "Copy plan should contain all expected version file targets" );
156168
157169 // Verify all internal paths inside each staged metadata file are rewritten to target
158170 assertAllInternalPathsRewritten (csvPairs , targetPrefix );
@@ -182,49 +194,119 @@ void tablePathRewriteForStartAndEndVersionProvided() throws Exception {
182194 }
183195
184196 Set <Pair <String , String >> csvPairs = readCsvPairs (table , result .fileListLocation ());
185- assertEquals (expectedTargets , csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ()));
197+ Set <String > actualTargets = csvPairs .stream ().map (Pair ::second ).collect (Collectors .toSet ());
198+ assertTrue (actualTargets .containsAll (expectedTargets ),
199+ "Copy plan should contain all expected version file targets" );
186200
187201 // Verify all internal paths inside each staged metadata file are rewritten to target
188202 assertAllInternalPathsRewritten (csvPairs , targetPrefix );
189203 }
190204
191205 /**
192- * For every staged metadata JSON file in the CSV, parses the file and asserts that:
193- * - The table location starts with target
194- * - Every metadata-log entry path starts with target
195- * - Every snapshot's manifest-list path starts with target
196- * - Every statistics file path starts with target
197- * - None of the above contain the source prefix.
206+ * For every staged file in the CSV copy plan, asserts that internal paths are rewritten
207+ * to the target prefix:
208+ * <ul>
209+ * <li><b>.metadata.json</b>: table location, metadata-log entries, and snapshot
210+ * manifest-list references all start with target.</li>
211+ * <li><b>snap-*.avro (manifest-list)</b>: target path starts with target, and every
212+ * manifest entry path inside the staged file starts with target.</li>
213+ * <li><b>*.avro (manifest)</b>: target path starts with target (content rewrite
214+ * is not yet implemented).</li>
215+ * </ul>
198216 */
199- private void assertAllInternalPathsRewritten (Set <Pair <String , String >> csvPairs , String target ) {
217+ private void assertAllInternalPathsRewritten (Set <Pair <String , String >> csvPairs , String target ) throws Exception {
218+
200219 for (Pair <String , String > pair : csvPairs ) {
201220 String stagingPath = pair .first ();
202221 String targetPath = pair .second ();
203222
204- // Only inspect .metadata.json files, manifest/data files and snapshots are not yet rewritten
205- if (!stagingPath .endsWith (".metadata.json" )) {
206- continue ;
223+ if (stagingPath .endsWith (".metadata.json" )) {
224+ assertMetadataFileRewritten (stagingPath , targetPath , target );
225+ } else if (RewriteTablePathUtil .fileName (stagingPath ).startsWith ("snap-" )) {
226+ assertManifestListRewritten (stagingPath , targetPath , target );
227+ } else if (RewriteTablePathUtil .fileName (stagingPath ).endsWith (".avro" )) {
228+ assertTrue (targetPath .startsWith (target ),
229+ "Manifest file target path should start with target prefix: " + targetPath );
207230 }
231+ }
232+ }
208233
209- assertTrue (targetPath .startsWith (target ),
210- "Target path in CSV should start with target prefix: " + targetPath );
234+ private void assertMetadataFileRewritten (String stagingPath , String targetPath , String target ) {
235+
236+ assertTrue (targetPath .startsWith (target ),
237+ "Target path in CSV should start with target prefix: " + targetPath );
238+ assertEquals (RewriteTablePathUtil .fileName (stagingPath ), RewriteTablePathUtil .fileName (targetPath ),
239+ "original and target metadata file should have the same filename" );
240+
241+ TableMetadata rewritten = new StaticTableOperations (stagingPath , table .io ()).current ();
242+ TableMetadata original = new StaticTableOperations (
243+ targetPath .replace (targetPrefix , sourcePrefix ), table .io ()).current ();
244+ Set <String > expectedMetadata = original .previousFiles ().stream ()
245+ .map (e -> RewriteTablePathUtil .fileName (e .file ()))
246+ .collect (Collectors .toSet ());
247+ Set <String > expectedManifestLists = original .snapshots ().stream ()
248+ .map (s -> RewriteTablePathUtil .fileName (s .manifestListLocation ()))
249+ .collect (Collectors .toSet ());
250+ Set <String > actualMetadata = new HashSet <>();
251+ Set <String > actualManifestLists = new HashSet <>();
252+
253+ assertTrue (rewritten .location ().startsWith (target ),
254+ "Metadata location should start with target: " + rewritten .location ());
255+
256+ for (MetadataLogEntry entry : rewritten .previousFiles ()) {
257+ assertTrue (entry .file ().startsWith (target ),
258+ "Metadata log entry should start with target: " + entry .file ());
259+ actualMetadata .add (RewriteTablePathUtil .fileName (entry .file ()));
260+ }
211261
212- TableMetadata rewritten = new StaticTableOperations (stagingPath , table .io ()).current ();
262+ assertEquals (expectedMetadata , actualMetadata ,
263+ "Rewritten metadata file should reference the same metadata files as the original" );
213264
214- assertTrue (rewritten .location ().startsWith (target ),
215- "Metadata location should start with target: " + rewritten .location ());
265+ for (Snapshot snapshot : rewritten .snapshots ()) {
266+ String manifestList = snapshot .manifestListLocation ();
267+ assertTrue (manifestList .startsWith (target ),
268+ "Snapshot's manifest-list should start with target: " + manifestList );
269+ actualManifestLists .add (RewriteTablePathUtil .fileName (manifestList ));
270+ }
271+ assertEquals (expectedManifestLists , actualManifestLists ,
272+ "Rewritten metadata file should reference the same manifest-lists as the original" );
273+ }
216274
217- for (MetadataLogEntry entry : rewritten .previousFiles ()) {
218- assertTrue (entry .file ().startsWith (target ),
219- "Metadata log entry should start with target: " + entry .file ());
275+ private void assertManifestListRewritten (String stagingPath , String targetPath , String target ) throws Exception {
276+
277+ assertTrue (targetPath .startsWith (target ),
278+ "Manifest list target path should start with target prefix: " + targetPath );
279+ assertEquals (RewriteTablePathUtil .fileName (stagingPath ), RewriteTablePathUtil .fileName (targetPath ),
280+ "original and target manifest list should have the same filename" );
281+
282+ Set <String > expectedManifests = new HashSet <>();
283+ Set <String > actualManifests = new HashSet <>();
284+ for (Snapshot s : table .snapshots ()) {
285+ if (RewriteTablePathUtil .fileName (s .manifestListLocation ()).equals (RewriteTablePathUtil .fileName (stagingPath ))) {
286+ expectedManifests = s .allManifests (table .io ())
287+ .stream ()
288+ .map (m -> RewriteTablePathUtil .fileName (m .path ()))
289+ .collect (Collectors .toSet ());
290+ break ;
220291 }
292+ }
221293
222- for (Snapshot snapshot : rewritten .snapshots ()) {
223- String manifestList = snapshot .manifestListLocation ();
224- assertTrue (manifestList .startsWith (target ),
225- "Snapshot manifest-list should start with target: " + manifestList );
294+ try (CloseableIterable <ManifestFile > manifests =
295+ InternalData .read (FileFormat .AVRO , table .io ().newInputFile (stagingPath ))
296+ .setRootType (GenericManifestFile .class )
297+ .setCustomType (
298+ ManifestFile .PARTITION_SUMMARIES_ELEMENT_ID ,
299+ GenericPartitionFieldSummary .class )
300+ .project (ManifestFile .schema ())
301+ .build ()) {
302+ for (ManifestFile manifest : manifests ) {
303+ assertTrue (manifest .path ().startsWith (target ),
304+ "Manifest path inside staged manifest list should start with target prefix: " + manifest .path ());
305+ actualManifests .add (RewriteTablePathUtil .fileName (manifest .path ()));
226306 }
227307 }
308+ assertEquals (expectedManifests , actualManifests ,
309+ "Rewritten manifest list should reference the same manifest files as the original" );
228310 }
229311
230312 private static List <String > metadataLogEntryPaths (Table tbl ) {
0 commit comments