This is an automated email from the ASF dual-hosted git repository.

vinishjail97 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-xtable.git


The following commit(s) were added to refs/heads/main by this push:
     new b73a1185 fix: Keep absolute paths for data files outside the table 
base path in Delta targets (#957)
b73a1185 is described below

commit b73a118522fd5b3496a3859bbd6aeccdee2e8064
Author: Vinish Reddy <[email protected]>
AuthorDate: Wed Oct 7 06:15:46 2026 +0530

    fix: Keep absolute paths for data files outside the table base path in 
Delta targets (#957)
    
    * fix: Keep absolute paths for data files outside the table base path in 
Delta targets
    
    Both Delta targets passed every data file to PathUtils.getRelativePath,
    which assumes the file is under the table base path. For a file outside
    the base path it removed characters by length, so the Delta log got a
    broken path such as nch.db/spans/data/file.parquet while the sync still
    reported success. This happens when the target base path differs from
    the location of the source data files, for example an Iceberg table
    with write.data.path set, or with files added by add_files or snapshot.
    
    Add DeltaPathUtils.getPathForDeltaLog, the write-side counterpart of
    getFullPathToFile. It records a file under the base path relative to
    it, comparing the paths without the scheme to handle s3 against s3a,
    and records any other file with its absolute path.
    
    * Skip Path parsing for Delta paths without a scheme
---
 .../delta/DeltaDataFileUpdatesExtractor.java       |  5 +--
 .../org/apache/xtable/delta/DeltaPathUtils.java    | 37 ++++++++++++++++++++--
 .../DeltaKernelDataFileUpdatesExtractor.java       |  6 ++--
 .../apache/xtable/delta/TestDeltaPathUtils.java    | 32 +++++++++++++++++++
 4 files changed, 70 insertions(+), 10 deletions(-)

diff --git 
a/xtable-core/src/main/java/org/apache/xtable/delta/DeltaDataFileUpdatesExtractor.java
 
b/xtable-core/src/main/java/org/apache/xtable/delta/DeltaDataFileUpdatesExtractor.java
index 71cebf9a..a338ec34 100644
--- 
a/xtable-core/src/main/java/org/apache/xtable/delta/DeltaDataFileUpdatesExtractor.java
+++ 
b/xtable-core/src/main/java/org/apache/xtable/delta/DeltaDataFileUpdatesExtractor.java
@@ -47,7 +47,6 @@ import org.apache.xtable.model.storage.InternalDataFile;
 import org.apache.xtable.model.storage.InternalFile;
 import org.apache.xtable.model.storage.InternalFilesDiff;
 import org.apache.xtable.model.storage.PartitionFileGroup;
-import org.apache.xtable.paths.PathUtils;
 
 @Builder
 public class DeltaDataFileUpdatesExtractor {
@@ -116,9 +115,7 @@ public class DeltaDataFileUpdatesExtractor {
       InternalDataFile dataFile, InternalSchema schema, String tableBasePath) {
     return Stream.of(
         new AddFile(
-            // Delta Lake supports relative and absolute paths in theory but 
relative paths seem
-            // more commonly supported by query engines in our testing
-            PathUtils.getRelativePath(dataFile.getPhysicalPath(), 
tableBasePath),
+            DeltaPathUtils.getPathForDeltaLog(tableBasePath, 
dataFile.getPhysicalPath()),
             
convertJavaMapToScala(deltaPartitionExtractor.partitionValueSerialization(dataFile)),
             dataFile.getFileSizeBytes(),
             dataFile.getLastModified(),
diff --git 
a/xtable-core/src/main/java/org/apache/xtable/delta/DeltaPathUtils.java 
b/xtable-core/src/main/java/org/apache/xtable/delta/DeltaPathUtils.java
index 141aac33..f690c379 100644
--- a/xtable-core/src/main/java/org/apache/xtable/delta/DeltaPathUtils.java
+++ b/xtable-core/src/main/java/org/apache/xtable/delta/DeltaPathUtils.java
@@ -18,11 +18,16 @@
  
 package org.apache.xtable.delta;
 
+import java.net.URI;
+import java.util.Objects;
+
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
 import org.apache.hadoop.fs.Path;
 
+import org.apache.xtable.paths.PathUtils;
+
 /**
  * Shared path resolution for Delta log actions. Used by both Delta Standalone 
and Delta Kernel
  * implementations.
@@ -51,8 +56,36 @@ public class DeltaPathUtils {
         : tableBasePath + Path.SEPARATOR + dataFilePath;
   }
 
+  /**
+   * Returns the path to record in an AddFile or RemoveFile action for a data 
file.
+   *
+   * <p>A file under the table base path is recorded relative to it, since 
relative paths seem more
+   * commonly supported by query engines. A file outside the base path, for 
example one written to a
+   * separate data location of an Iceberg source, is recorded with its 
absolute path, since a
+   * relative path cannot point to it.
+   *
+   * @param tableBasePath the table base path
+   * @param dataFilePath the path to the data file, relative or absolute
+   * @return the path to record in the Delta log
+   */
+  public static String getPathForDeltaLog(String tableBasePath, String 
dataFilePath) {
+    return isUnderBasePath(tableBasePath, dataFilePath)
+        ? PathUtils.getRelativePath(dataFilePath, tableBasePath)
+        : dataFilePath;
+  }
+
+  private static boolean isUnderBasePath(String tableBasePath, String 
dataFilePath) {
+    if (!dataFilePath.contains(":")) {
+      return true;
+    }
+    URI file = new Path(dataFilePath).toUri();
+    // compare without the scheme to handle differences like s3 vs s3a
+    URI base = new Path(tableBasePath).toUri();
+    return Objects.equals(base.getAuthority(), file.getAuthority())
+        && file.getPath().startsWith(base.getPath() + Path.SEPARATOR);
+  }
+
   private static boolean isAbsolutePath(String dataFilePath) {
-    return dataFilePath.startsWith(Path.SEPARATOR)
-        || new Path(dataFilePath).toUri().getScheme() != null;
+    return dataFilePath.startsWith(Path.SEPARATOR) || 
dataFilePath.contains(":");
   }
 }
diff --git 
a/xtable-core/src/main/java/org/apache/xtable/kernel/DeltaKernelDataFileUpdatesExtractor.java
 
b/xtable-core/src/main/java/org/apache/xtable/kernel/DeltaKernelDataFileUpdatesExtractor.java
index e9412128..f1bf9f63 100644
--- 
a/xtable-core/src/main/java/org/apache/xtable/kernel/DeltaKernelDataFileUpdatesExtractor.java
+++ 
b/xtable-core/src/main/java/org/apache/xtable/kernel/DeltaKernelDataFileUpdatesExtractor.java
@@ -46,6 +46,7 @@ import io.delta.kernel.statistics.DataFileStatistics;
 import io.delta.kernel.types.StructType;
 
 import org.apache.xtable.collectors.CustomCollectors;
+import org.apache.xtable.delta.DeltaPathUtils;
 import org.apache.xtable.exception.ReadException;
 import org.apache.xtable.model.schema.InternalField;
 import org.apache.xtable.model.schema.InternalSchema;
@@ -57,7 +58,6 @@ import org.apache.xtable.model.storage.InternalDataFile;
 import org.apache.xtable.model.storage.InternalFile;
 import org.apache.xtable.model.storage.InternalFilesDiff;
 import org.apache.xtable.model.storage.PartitionFileGroup;
-import org.apache.xtable.paths.PathUtils;
 import org.apache.xtable.spi.extractor.DataFileIterator;
 
 @Builder
@@ -205,9 +205,7 @@ public class DeltaKernelDataFileUpdatesExtractor {
     Row addFileRow =
         AddFile.createAddFileRow(
             physicalSchema,
-            // Delta Lake supports relative and absolute paths in theory but 
relative paths seem
-            // more commonly supported by query engines in our testing
-            PathUtils.getRelativePath(dataFile.getPhysicalPath(), 
tableBasePath),
+            DeltaPathUtils.getPathForDeltaLog(tableBasePath, 
dataFile.getPhysicalPath()),
             partitionValues,
             dataFile.getFileSizeBytes(),
             dataFile.getLastModified(),
diff --git 
a/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaPathUtils.java 
b/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaPathUtils.java
index 3defd993..b1223f98 100644
--- a/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaPathUtils.java
+++ b/xtable-core/src/test/java/org/apache/xtable/delta/TestDeltaPathUtils.java
@@ -58,4 +58,36 @@ class TestDeltaPathUtils {
   void getFullPathToFile(String tableBasePath, String dataFilePath, String 
expected) {
     assertEquals(expected, DeltaPathUtils.getFullPathToFile(tableBasePath, 
dataFilePath));
   }
+
+  private static Stream<Arguments> deltaLogPathCases() {
+    return Stream.of(
+        // files under the base path are recorded relative to it
+        Arguments.of("s3a://bucket/tab", "s3a://bucket/tab/part-0.parquet", 
"part-0.parquet"),
+        Arguments.of(
+            "s3a://bucket/tab",
+            "s3://bucket/tab/year=2024/part-0.parquet",
+            "year=2024/part-0.parquet"),
+        Arguments.of("s3://bucket/tab", "s3a://bucket/tab/part-0.parquet", 
"part-0.parquet"),
+        Arguments.of("file:///tmp/tab", "file:/tmp/tab/part-0.parquet", 
"part-0.parquet"),
+        Arguments.of("s3a://bucket/tab", "part-0.parquet", "part-0.parquet"),
+        // files outside the base path keep their absolute path
+        Arguments.of(
+            "s3a://bucket/target/tab",
+            "s3://bucket/source/tab/data/part-0.parquet",
+            "s3://bucket/source/tab/data/part-0.parquet"),
+        Arguments.of(
+            "s3a://bucket/tab",
+            "s3a://other-bucket/tab/part-0.parquet",
+            "s3a://other-bucket/tab/part-0.parquet"),
+        Arguments.of(
+            "s3a://bucket/tab",
+            "s3a://bucket/tab2/part-0.parquet",
+            "s3a://bucket/tab2/part-0.parquet"));
+  }
+
+  @ParameterizedTest
+  @MethodSource("deltaLogPathCases")
+  void getPathForDeltaLog(String tableBasePath, String dataFilePath, String 
expected) {
+    assertEquals(expected, DeltaPathUtils.getPathForDeltaLog(tableBasePath, 
dataFilePath));
+  }
 }

Reply via email to