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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 3bf8da507b [core] Qualify local Format Table partition paths on 
replacement
3bf8da507b is described below

commit 3bf8da507bc58651a36d3b15a0da23e51fe537f3
Author: JingsongLi <[email protected]>
AuthorDate: Fri Sep 11 11:25:21 2026 +0800

    [core] Qualify local Format Table partition paths on replacement
---
 .../paimon/table/format/FormatTableCommit.java     | 18 ++++----
 .../apache/paimon/rest/MockRESTCatalogTest.java    | 54 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 8 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableCommit.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableCommit.java
index b823fc462e..b0b0f5de45 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableCommit.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableCommit.java
@@ -48,6 +48,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nullable;
 
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
@@ -599,15 +600,16 @@ public class FormatTableCommit implements 
BatchTableCommit {
             partitionOptions = new ArrayList<>(specs.size());
             for (Map<String, String> spec : specs) {
                 statisticsByPartition.putIfAbsent(spec, emptyStatistics(spec, 
commitTime));
+                Path partitionPath =
+                        buildPartitionPath(
+                                location, spec, 
formatTablePartitionOnlyValueInPath, partitionKeys);
+                if (partitionPath.toUri().getScheme() == null) {
+                    // Scheme-less table paths use local storage, but catalog 
partition locations
+                    // require a fully qualified URI. Keep escaped partition 
values intact.
+                    partitionPath = new Path(new 
File(partitionPath.toString()).toURI());
+                }
                 partitionOptions.add(
-                        Collections.singletonMap(
-                                CoreOptions.PATH.key(),
-                                buildPartitionPath(
-                                                location,
-                                                spec,
-                                                
formatTablePartitionOnlyValueInPath,
-                                                partitionKeys)
-                                        .toString()));
+                        Collections.singletonMap(CoreOptions.PATH.key(), 
partitionPath.toString()));
             }
         }
         partitionManager.createPartitions(
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java 
b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
index 492b509dfd..0054af3d16 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
@@ -64,6 +64,7 @@ import org.apache.paimon.table.BlobDescriptorReaderFactory;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.FormatTable;
 import org.apache.paimon.table.format.FormatTablePartitionManager;
+import org.apache.paimon.table.sink.BatchTableCommit;
 import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
@@ -76,6 +77,8 @@ import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGet
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import java.io.IOException;
 import java.net.URI;
@@ -823,6 +826,57 @@ class MockRESTCatalogTest extends RESTCatalogTest {
         
assertThat(partition.options()).containsExactlyInAnyOrderEntriesOf(originalOptions);
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = {"truncate-table", "truncate-partition", 
"overwrite"})
+    void testPartitionReplacementWithSchemeLessTableLocation(String operation) 
throws Exception {
+        restCatalogServer.shutdown();
+        dataPath = new Path(dataPath).toUri().getPath();
+        restCatalog = initCatalog(false);
+        Identifier identifier = 
createFormatTableWithCatalogManagedPartitions();
+        FormatTable table = (FormatTable) restCatalog.getTable(identifier);
+        assertThat(new Path(table.location()).toUri().getScheme()).isNull();
+
+        Map<String, String> spec = Collections.singletonMap("dt", 
"2026/07%17");
+        Map<String, String> partitionOptions = 
Collections.singletonMap("owner", "data-platform");
+        restCatalog.createPartitions(
+                identifier,
+                Collections.singletonList(spec),
+                true,
+                Collections.singletonList(partitionStatistics(spec, 9L)),
+                true,
+                Collections.singletonList(partitionOptions));
+        Path directory = new Path(new Path(table.location()), 
"dt=2026%2F07%2517");
+        Path dataFile = new Path(directory, "data.parquet");
+        table.fileIO().mkdirs(directory);
+        table.fileIO().overwriteFileUtf8(dataFile, "old data");
+
+        try (BatchTableCommit commit =
+                table.newBatchWriteBuilder().withOverwrite(spec).newCommit()) {
+            switch (operation) {
+                case "truncate-table":
+                    commit.truncateTable();
+                    break;
+                case "truncate-partition":
+                    commit.truncatePartitions(Collections.singletonList(spec));
+                    break;
+                case "overwrite":
+                    commit.commit(Collections.emptyList());
+                    break;
+                default:
+                    throw new IllegalArgumentException(operation);
+            }
+        }
+
+        assertThat(table.fileIO().exists(dataFile)).isFalse();
+        assertThat(table.fileIO().exists(directory)).isTrue();
+        Partition partition = onlyPartition(identifier);
+        assertThat(partition.spec()).isEqualTo(spec);
+        assertThat(partition.recordCount()).isZero();
+        assertThat(partition.fileSizeInBytes()).isZero();
+        assertThat(partition.fileCount()).isZero();
+        assertThat(partition.options()).isEqualTo(partitionOptions);
+    }
+
     @Test
     void 
testReturningToTheDefaultDirectoryRemovesOnlyLocationAndIsReplaySafe() throws 
Exception {
         Identifier identifier = 
createFormatTableWithCatalogManagedPartitions();

Reply via email to