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();