Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -750,4 +750,61 @@ public void testRemoveOrphanFilesProcedureWithEqualAuthorities()
// Dropping the table here
sql("DROP TABLE %s", tableName);
}

@TestTemplate
public void testRemoveOrphanFilesFileListViewDoesNotMatchSiblingPaths()
throws NoSuchTableException, ParseException, IOException {
if (catalogName.equals("testhadoop")) {
sql("CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg", tableName);
} else {
sql(
"CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg LOCATION '%s'",
tableName, java.nio.file.Files.createTempDirectory(temp, "junit"));
}
Table table = Spark3Util.loadIcebergTable(spark, tableName);
String tableLocation = table.location();
// The sibling path has the table location as a raw string prefix, e.g. /tmp/abc-sibling when
// table is at /tmp/abc. Without the trailing-slash fix, startsWith(tableLocation) would
// incorrectly match files under this sibling path.
String siblingPath = tableLocation + "-sibling";

Timestamp lastModifiedTimestamp = new Timestamp(10000);
String orphanFile = tableLocation + "/orphan.parquet";
String siblingFile = siblingPath + "/data.parquet";

List<FilePathLastModifiedRecord> allFiles = Lists.newArrayList();
allFiles.add(new FilePathLastModifiedRecord(orphanFile, lastModifiedTimestamp));
allFiles.add(new FilePathLastModifiedRecord(siblingFile, lastModifiedTimestamp));
allFiles.add(
new FilePathLastModifiedRecord(
ReachableFileUtil.versionHintLocation(table), lastModifiedTimestamp));
for (String metaFile : ReachableFileUtil.metadataFileLocations(table, true)) {
allFiles.add(new FilePathLastModifiedRecord(metaFile, lastModifiedTimestamp));
}

Dataset<Row> compareToFileList =
spark
.createDataFrame(allFiles, FilePathLastModifiedRecord.class)
.withColumnRenamed("filePath", "file_path")
.withColumnRenamed("lastModified", "last_modified");
String fileListViewName = "files_view";
compareToFileList.createOrReplaceTempView(fileListViewName);

List<Object[]> orphanFiles =
sql(
"CALL %s.system.remove_orphan_files("
+ "table => '%s',"
+ "dry_run => true,"
+ "file_list_view => '%s')",
catalogName, tableIdent, fileListViewName);

assertThat(orphanFiles)
.as("Files under sibling path must not be identified as orphans for this table")
.extracting(row -> row[0])
.doesNotContain(siblingFile);
assertThat(orphanFiles)
.as("Orphan file under the table location must be identified")
.extracting(row -> row[0])
.contains(orphanFile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ public DeleteOrphanFilesSparkAction usePrefixListing(boolean newUsePrefixListing
private Dataset<String> filteredCompareToFileList() {
Dataset<Row> files = compareToFileList;
if (location != null) {
files = files.filter(files.col(FILE_PATH).startsWith(location));
String locationPrefix = location.endsWith("/") ? location : location + "/";
files = files.filter(files.col(FILE_PATH).startsWith(locationPrefix));
}
return files
.filter(files.col(LAST_MODIFIED).lt(new Timestamp(olderThanTimestamp)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,61 @@ public void testRemoveOrphanFilesProcedureWithEqualAuthorities()
// Dropping the table here
sql("DROP TABLE %s", tableName);
}

@TestTemplate
public void testRemoveOrphanFilesFileListViewDoesNotMatchSiblingPaths()
throws NoSuchTableException, ParseException, IOException {
if (catalogName.equals("testhadoop")) {
sql("CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg", tableName);
} else {
sql(
"CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg LOCATION '%s'",
tableName, java.nio.file.Files.createTempDirectory(temp, "junit"));
}
Table table = Spark3Util.loadIcebergTable(spark, tableName);
String tableLocation = table.location();
// The sibling path has the table location as a raw string prefix, e.g. /tmp/abc-sibling when
// table is at /tmp/abc. Without the trailing-slash fix, startsWith(tableLocation) would
// incorrectly match files under this sibling path.
String siblingPath = tableLocation + "-sibling";

Timestamp lastModifiedTimestamp = new Timestamp(10000);
String orphanFile = tableLocation + "/orphan.parquet";
String siblingFile = siblingPath + "/data.parquet";

List<FilePathLastModifiedRecord> allFiles = Lists.newArrayList();
allFiles.add(new FilePathLastModifiedRecord(orphanFile, lastModifiedTimestamp));
allFiles.add(new FilePathLastModifiedRecord(siblingFile, lastModifiedTimestamp));
allFiles.add(
new FilePathLastModifiedRecord(
ReachableFileUtil.versionHintLocation(table), lastModifiedTimestamp));
for (String metaFile : ReachableFileUtil.metadataFileLocations(table, true)) {
allFiles.add(new FilePathLastModifiedRecord(metaFile, lastModifiedTimestamp));
}

Dataset<Row> compareToFileList =
spark
.createDataFrame(allFiles, FilePathLastModifiedRecord.class)
.withColumnRenamed("filePath", "file_path")
.withColumnRenamed("lastModified", "last_modified");
String fileListViewName = "files_view";
compareToFileList.createOrReplaceTempView(fileListViewName);

List<Object[]> orphanFiles =
sql(
"CALL %s.system.remove_orphan_files("
+ "table => '%s',"
+ "dry_run => true,"
+ "file_list_view => '%s')",
catalogName, tableIdent, fileListViewName);

assertThat(orphanFiles)
.as("Files under sibling path must not be identified as orphans for this table")
.extracting(row -> row[0])
.doesNotContain(siblingFile);
assertThat(orphanFiles)
.as("Orphan file under the table location must be identified")
.extracting(row -> row[0])
.contains(orphanFile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ public DeleteOrphanFilesSparkAction usePrefixListing(boolean newUsePrefixListing
private Dataset<String> filteredCompareToFileList() {
Dataset<Row> files = compareToFileList;
if (location != null) {
files = files.filter(files.col(FILE_PATH).startsWith(location));
String locationPrefix = location.endsWith("/") ? location : location + "/";
files = files.filter(files.col(FILE_PATH).startsWith(locationPrefix));
}
return files
.filter(files.col(LAST_MODIFIED).lt(new Timestamp(olderThanTimestamp)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,61 @@ public void testRemoveOrphanFilesProcedureWithEqualAuthorities()
// Dropping the table here
sql("DROP TABLE %s", tableName);
}

@TestTemplate
public void testRemoveOrphanFilesFileListViewDoesNotMatchSiblingPaths()
throws NoSuchTableException, ParseException, IOException {
if (catalogName.equals("testhadoop")) {
sql("CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg", tableName);
} else {
sql(
"CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg LOCATION '%s'",
tableName, java.nio.file.Files.createTempDirectory(temp, "junit"));
}
Table table = Spark3Util.loadIcebergTable(spark, tableName);
String tableLocation = table.location();
// The sibling path has the table location as a raw string prefix, e.g. /tmp/abc-sibling when
// table is at /tmp/abc. Without the trailing-slash fix, startsWith(tableLocation) would
// incorrectly match files under this sibling path.
String siblingPath = tableLocation + "-sibling";

Timestamp lastModifiedTimestamp = new Timestamp(10000);
String orphanFile = tableLocation + "/orphan.parquet";
String siblingFile = siblingPath + "/data.parquet";

List<FilePathLastModifiedRecord> allFiles = Lists.newArrayList();
allFiles.add(new FilePathLastModifiedRecord(orphanFile, lastModifiedTimestamp));
allFiles.add(new FilePathLastModifiedRecord(siblingFile, lastModifiedTimestamp));
allFiles.add(
new FilePathLastModifiedRecord(
ReachableFileUtil.versionHintLocation(table), lastModifiedTimestamp));
for (String metaFile : ReachableFileUtil.metadataFileLocations(table, true)) {
allFiles.add(new FilePathLastModifiedRecord(metaFile, lastModifiedTimestamp));
}

Dataset<Row> compareToFileList =
spark
.createDataFrame(allFiles, FilePathLastModifiedRecord.class)
.withColumnRenamed("filePath", "file_path")
.withColumnRenamed("lastModified", "last_modified");
String fileListViewName = "files_view";
compareToFileList.createOrReplaceTempView(fileListViewName);

List<Object[]> orphanFiles =
sql(
"CALL %s.system.remove_orphan_files("
+ "table => '%s',"
+ "dry_run => true,"
+ "file_list_view => '%s')",
catalogName, tableIdent, fileListViewName);

assertThat(orphanFiles)
.as("Files under sibling path must not be identified as orphans for this table")
.extracting(row -> row[0])
.doesNotContain(siblingFile);
assertThat(orphanFiles)
.as("Orphan file under the table location must be identified")
.extracting(row -> row[0])
.contains(orphanFile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ public DeleteOrphanFilesSparkAction usePrefixListing(boolean newUsePrefixListing
private Dataset<String> filteredCompareToFileList() {
Dataset<Row> files = compareToFileList;
if (location != null) {
files = files.filter(files.col(FILE_PATH).startsWith(location));
String locationPrefix = location.endsWith("/") ? location : location + "/";
files = files.filter(files.col(FILE_PATH).startsWith(locationPrefix));
}
return files
.filter(files.col(LAST_MODIFIED).lt(new Timestamp(olderThanTimestamp)))
Expand Down