sdaberdaku commented on code in PR #17862:
URL: https://github.com/apache/iceberg/pull/17862#discussion_r3948034338


##########
spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/actions/DeleteOrphanFilesSparkAction.java:
##########
@@ -222,6 +223,24 @@ public DeleteOrphanFilesSparkAction 
usePrefixListing(boolean newUsePrefixListing
     return this;
   }
 
+  /**
+   * Configures the listing to use the Hadoop configuration of the given 
catalog.
+   *
+   * <p>Listing the table location goes through the Hadoop {@link 
org.apache.hadoop.fs.FileSystem}
+   * API, which is configured from the session and so reaches storage as a 
different principal than
+   * the catalog does. Setting the catalog makes the listing follow the 
catalog's own configuration
+   * instead.
+   *
+   * @param newCatalogName the name of the catalog that holds the table
+   * @return this for method chaining
+   */
+  public DeleteOrphanFilesSparkAction catalogName(String newCatalogName) {

Review Comment:
   @yangshangqing95 fair enough — the provenance argument convinced me. 
`Table.name()` is display output, and a delete action shouldn't pick 
credentials based on what a string happens to look like. I've pushed the 
explicit version: `catalogName(String)` on the action, and 
`RemoveOrphanFilesProcedure` passes `tableCatalog().name()`. Without the call 
the action keeps today's behaviour (session configuration only), so nothing 
changes for existing `SparkActions` users unless they opt in.
   



##########
spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/actions/DeleteOrphanFilesSparkAction.java:
##########
@@ -146,6 +148,28 @@ public class DeleteOrphanFilesSparkAction extends 
BaseSparkAction<DeleteOrphanFi
         "Cannot delete orphan files: GC is disabled (deleting files may 
corrupt other tables)");
   }
 
+  /**
+   * Resolves the Hadoop configuration for listing the table location: the 
session configuration
+   * plus the {@code spark.sql.catalog.<name>.hadoop.*} overrides of the 
catalog that owns the
+   * table.
+   *
+   * <p>Catalog tables are named {@code catalog.namespace.table}, where the 
catalog part is the
+   * Spark catalog name. The overrides are applied only when that part names a 
registered Spark
+   * catalog; path-based tables and unknown names fall back to the session 
configuration.
+   */
+  private static Configuration hadoopConfForTable(SparkSession spark, Table 
table) {
+    String name = table.name();

Review Comment:
   Agreed, and this is gone now — the pushed version takes the catalog name 
explicitly instead of parsing `table.name()`.
   



##########
spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/actions/DeleteOrphanFilesSparkAction.java:
##########
@@ -146,6 +148,28 @@ public class DeleteOrphanFilesSparkAction extends 
BaseSparkAction<DeleteOrphanFi
         "Cannot delete orphan files: GC is disabled (deleting files may 
corrupt other tables)");
   }
 
+  /**
+   * Resolves the Hadoop configuration for listing the table location: the 
session configuration
+   * plus the {@code spark.sql.catalog.<name>.hadoop.*} overrides of the 
catalog that owns the
+   * table.
+   *
+   * <p>Catalog tables are named {@code catalog.namespace.table}, where the 
catalog part is the
+   * Spark catalog name. The overrides are applied only when that part names a 
registered Spark
+   * catalog; path-based tables and unknown names fall back to the session 
configuration.
+   */
+  private static Configuration hadoopConfForTable(SparkSession spark, Table 
table) {
+    String name = table.name();
+    int dot = name.indexOf('.');
+    if (dot > 0 && !name.contains("/") && !name.contains(":")) {
+      String catalogName = name.substring(0, dot);
+      if 
(spark.sessionState().catalogManager().isCatalogRegistered(catalogName)) {

Review Comment:
   Good catch — I had assumed it was a passive lookup, but it goes through 
`catalog(name)` and can initialize and cache the catalog. One more reason the 
inference was the wrong idea. No catalog resolution happens in the action 
anymore; it only reads the `spark.sql.catalog.<name>.hadoop.*` conf keys for 
the name it's given.
   



##########
spark/v4.1/spark/src/test/java/org/apache/iceberg/spark/actions/TestRemoveOrphanFilesAction3.java:
##########
@@ -190,10 +193,117 @@ public void testSparkSessionCatalogHiveTable() throws 
Exception {
     assertThat(results.orphanFilesCount()).as("trash file should be 
removed").isEqualTo(1L);
   }
 
+  @TestTemplate
+  public void catalogHadoopConfOverridesApplyToListing() throws Exception {

Review Comment:
   Added the `CALL <catalog>.system.remove_orphan_files(...)` regression to 
`TestRemoveOrphanFilesProcedure`; it registers the marker filesystem only in 
the catalog's `hadoop.*` overrides and runs across all four test catalogs 
(testhive, testhadoop, spark_catalog, testrest), so it covers the exact path 
from #17860 including the session catalog. I didn't add the collision test — 
with the inference removed there's no name-based selection left to demonstrate; 
the action tests now pass the catalog explicitly.
   



##########
spark/v4.1/spark/src/test/java/org/apache/iceberg/spark/actions/TestRemoveOrphanFilesAction3.java:
##########
@@ -190,10 +193,117 @@ public void testSparkSessionCatalogHiveTable() throws 
Exception {
     assertThat(results.orphanFilesCount()).as("trash file should be 
removed").isEqualTo(1L);
   }
 
+  @TestTemplate
+  public void catalogHadoopConfOverridesApplyToListing() throws Exception {
+    spark.conf().set("spark.sql.catalog.overridecat", 
"org.apache.iceberg.spark.SparkCatalog");
+    spark.conf().set("spark.sql.catalog.overridecat.type", "hadoop");
+    spark.conf().set("spark.sql.catalog.overridecat.warehouse", tableLocation);
+    // registered for this catalog alone, so the location below resolves only 
when the catalog's
+    // Hadoop overrides reach the listing
+    spark
+        .conf()
+        .set(
+            String.format(
+                "spark.sql.catalog.overridecat.hadoop.fs.%s.impl", 
CatalogScopedFileSystem.SCHEME),
+            CatalogScopedFileSystem.class.getName());
+    SparkCatalog cat = (SparkCatalog) 
spark.sessionState().catalogManager().catalog("overridecat");
+
+    String[] database = {"default"};
+    Identifier id = Identifier.of(database, randomName("table"));
+    Transform[] transforms = {};
+    cat.createTable(id, SparkSchemaUtil.convert(SCHEMA), transforms, 
properties);
+    SparkTable table = (SparkTable) cat.loadTable(id);
+
+    sql("INSERT INTO overridecat.default.%s VALUES (1,1,1)", id.name());
+
+    String location = table.table().location().replaceFirst("file:", "");
+    String trashFile = randomName("/data/trashfile");
+    new File(location + trashFile).createNewFile();
+
+    DeleteOrphanFiles.Result results =
+        SparkActions.get()
+            .deleteOrphanFiles(table.table())
+            .location(CatalogScopedFileSystem.SCHEME + "://" + location)
+            .equalSchemes(ImmutableMap.of(CatalogScopedFileSystem.SCHEME, 
"file"))
+            .deleteWith(file -> {})
+            .olderThan(System.currentTimeMillis() + 1000)
+            .execute();
+
+    
assertThat(StreamSupport.stream(results.orphanFileLocations().spliterator(), 
false))
+        .as("trash file should be found")
+        .anyMatch(file -> file.endsWith(trashFile));
+    assertThat(results.orphanFilesCount()).as("only the trash file is an 
orphan").isEqualTo(1L);
+  }
+
+  @TestTemplate
+  public void sessionCatalogHadoopConfOverridesApplyToListing() throws 
Exception {
+    spark
+        .conf()
+        .set("spark.sql.catalog.spark_catalog", 
"org.apache.iceberg.spark.SparkSessionCatalog");
+    spark.conf().set("spark.sql.catalog.spark_catalog.type", "hadoop");
+    spark.conf().set("spark.sql.catalog.spark_catalog.warehouse", 
tableLocation);
+    spark
+        .conf()
+        .set(
+            String.format(
+                "spark.sql.catalog.spark_catalog.hadoop.fs.%s.impl",
+                CatalogScopedFileSystem.SCHEME),
+            CatalogScopedFileSystem.class.getName());
+    SparkSessionCatalog cat =
+        (SparkSessionCatalog) 
spark.sessionState().catalogManager().v2SessionCatalog();
+
+    String[] database = {"default"};
+    Identifier id = Identifier.of(database, randomName("table"));
+    Transform[] transforms = {};
+    cat.createTable(id, SparkSchemaUtil.convert(SCHEMA), transforms, 
properties);
+    SparkTable table = (SparkTable) cat.loadTable(id);
+
+    sql("INSERT INTO default.%s VALUES (1,1,1)", id.name());
+
+    String location = table.table().location().replaceFirst("file:", "");
+    String trashFile = randomName("/data/trashfile");
+    new File(location + trashFile).createNewFile();
+
+    DeleteOrphanFiles.Result results =
+        SparkActions.get()
+            .deleteOrphanFiles(table.table())
+            .location(CatalogScopedFileSystem.SCHEME + "://" + location)
+            .equalSchemes(ImmutableMap.of(CatalogScopedFileSystem.SCHEME, 
"file"))
+            .deleteWith(file -> {})
+            .olderThan(System.currentTimeMillis() + 1000)
+            .execute();
+
+    
assertThat(StreamSupport.stream(results.orphanFileLocations().spliterator(), 
false))
+        .as("trash file should be found")
+        .anyMatch(file -> file.endsWith(trashFile));
+    assertThat(results.orphanFilesCount()).as("only the trash file is an 
orphan").isEqualTo(1L);
+  }
+
   @AfterEach
   public void resetSparkSessionCatalog() {
     spark.conf().unset("spark.sql.catalog.spark_catalog");
     spark.conf().unset("spark.sql.catalog.spark_catalog.type");
     spark.conf().unset("spark.sql.catalog.spark_catalog.warehouse");
+    spark

Review Comment:
   Done — the `@AfterEach` now unsets the `overridecat` keys as well, and the 
new procedure test cleans up its override key too.
   



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