diff --git a/paimon-core/src/main/java/org/apache/paimon/operation/OrphanFilesClean.java b/paimon-core/src/main/java/org/apache/paimon/operation/OrphanFilesClean.java index 31d80bf519e8..b7bf5f975065 100644 --- a/paimon-core/src/main/java/org/apache/paimon/operation/OrphanFilesClean.java +++ b/paimon-core/src/main/java/org/apache/paimon/operation/OrphanFilesClean.java @@ -109,7 +109,10 @@ public OrphanFilesClean(FileStoreTable table, long olderThanMillis, boolean dryR } protected List validBranches() { - List branches = table.branchManager().branches(); + // The branch manager may return an immutable list (the REST branch manager returns + // Collections.emptyList() for a branchless table), so copy it before adding the main + // branch. + List branches = new ArrayList<>(table.branchManager().branches()); List abnormalBranches = new ArrayList<>(); for (String branch : branches) { diff --git a/paimon-core/src/test/java/org/apache/paimon/operation/OrphanFilesCleanTest.java b/paimon-core/src/test/java/org/apache/paimon/operation/OrphanFilesCleanTest.java index 75648f519bc3..6322f89724d2 100644 --- a/paimon-core/src/test/java/org/apache/paimon/operation/OrphanFilesCleanTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/operation/OrphanFilesCleanTest.java @@ -37,6 +37,7 @@ import org.apache.paimon.types.DataTypes; import org.apache.paimon.types.RowKind; import org.apache.paimon.types.RowType; +import org.apache.paimon.utils.BranchManager; import org.junit.Rule; import org.junit.Test; @@ -44,6 +45,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -68,6 +70,36 @@ public void testOlderThanMillis() { "The arg olderThan must be less than now, because dataFiles that are currently being written and not referenced by snapshots will be mistakenly cleaned up."); } + @Test + public void testValidBranchesDoesNotMutateManagerList() throws Exception { + Path tablePath = new Path(tempDir.newFolder().toURI()); + FileIO fileIO = LocalFileIO.create(); + RowType rowType = RowType.of(DataTypes.INT(), DataTypes.STRING()); + TableSchema tableSchema = + SchemaUtils.forceCommit( + new FileSystemSchemaManager(fileIO, tablePath), + new Schema( + rowType.getFields(), + Collections.emptyList(), + Collections.emptyList(), + new Options().toMap(), + "")); + FileStoreTable real = FileStoreTableFactory.create(fileIO, tablePath, tableSchema); + + // REST catalogs hand out an immutable empty list for branch-less tables; the + // cleaner must not try to add the main branch to that shared list + BranchManager branchManager = org.mockito.Mockito.mock(BranchManager.class); + org.mockito.Mockito.when(branchManager.branches()).thenReturn(Collections.emptyList()); + FileStoreTable table = + org.mockito.Mockito.mock( + FileStoreTable.class, org.mockito.AdditionalAnswers.delegatesTo(real)); + org.mockito.Mockito.doReturn(branchManager).when(table).branchManager(); + + List valid = new LocalOrphanFilesClean(table).validBranches(); + + assertThat(valid).containsExactly("main"); + } + @Test public void testListPaimonFileDirsWithEmptyPartition() throws Exception { Path tablePath = new Path(tempDir.newFolder().toURI());