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 5371a4f22f [iceberg] Allow publishing VARIANT columns with Iceberg 
format version 3 (#9246)
5371a4f22f is described below

commit 5371a4f22fcc7875abbcacfffc913ac4a8e3964a
Author: Victor Babenko <[email protected]>
AuthorDate: Sat Aug 29 19:04:33 2026 -0700

    [iceberg] Allow publishing VARIANT columns with Iceberg format version 3 
(#9246)
---
 .../paimon/iceberg/IcebergCommitCallback.java      | 14 ++++---
 .../core/IcebergRowLineageCompatibilityTest.java   | 48 ++++++++++++++++++++++
 2 files changed, 56 insertions(+), 6 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
 
b/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
index d4c25a734e..5b0a78b828 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
@@ -735,7 +735,7 @@ public class IcebergCommitCallback implements 
CommitCallback, TagCallback {
         return result;
     }
 
-    /** VARIANT needs Iceberg row lineage, which Paimon Iceberg compatibility 
cannot publish. */
+    /** VARIANT is an Iceberg format-version-3 type; reject publishing it into 
v2 metadata. */
     static void checkVariantNotPublishable(RowType rowType) {
         Collection<String> variantFields = new LinkedHashSet<>();
         for (DataField field : rowType.getFields()) {
@@ -743,9 +743,8 @@ public class IcebergCommitCallback implements 
CommitCallback, TagCallback {
         }
         Preconditions.checkArgument(
                 variantFields.isEmpty(),
-                "Columns %s use the VARIANT type, which Paimon Iceberg 
compatibility cannot "
-                        + "publish: it is an Iceberg format-version-3 type 
that requires row "
-                        + "lineage.",
+                "Columns %s use the VARIANT type, which requires Iceberg 
format version 3. "
+                        + "Set 'metadata.iceberg.format-version' = '3' to 
publish this table.",
                 variantFields);
     }
 
@@ -2145,8 +2144,11 @@ public class IcebergCommitCallback implements 
CommitCallback, TagCallback {
                     schemaId,
                     id -> {
                         TableSchema schema = schemaManager.schema(id);
-                        // backstop: reject variant on each schema as it is 
emitted
-                        checkVariantNotPublishable(schema.logicalRowType());
+                        if (formatVersion < IcebergMetadata.FORMAT_VERSION_V3) 
{
+                            // VARIANT is an Iceberg format-version-3 type; v2 
metadata cannot
+                            // represent it
+                            
checkVariantNotPublishable(schema.logicalRowType());
+                        }
                         SchemaValidation.validateIcebergGeospatialTypes(
                                 schema.logicalRowType(), table.coreOptions());
                         SchemaValidation.validateIcebergTimestampPrecisions(
diff --git 
a/paimon-iceberg/src/test/java/org/apache/paimon/core/IcebergRowLineageCompatibilityTest.java
 
b/paimon-iceberg/src/test/java/org/apache/paimon/core/IcebergRowLineageCompatibilityTest.java
index 55a1eacb66..b10cb345d1 100644
--- 
a/paimon-iceberg/src/test/java/org/apache/paimon/core/IcebergRowLineageCompatibilityTest.java
+++ 
b/paimon-iceberg/src/test/java/org/apache/paimon/core/IcebergRowLineageCompatibilityTest.java
@@ -23,6 +23,7 @@ import org.apache.paimon.catalog.FileSystemCatalog;
 import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.data.variant.GenericVariant;
 import org.apache.paimon.disk.IOManagerImpl;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.fs.SeekableInputStream;
@@ -76,6 +77,7 @@ import java.util.Map;
 import java.util.UUID;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /** Tests for Iceberg format-version 3 row-lineage metadata fields. */
 public class IcebergRowLineageCompatibilityTest {
@@ -234,6 +236,52 @@ public class IcebergRowLineageCompatibilityTest {
         assertThat(metadata.nextRowId()).isEqualTo(3L);
     }
 
+    @Test
+    public void testVariantPublishableWithFormatVersion3() throws Exception {
+        RowType rowType =
+                RowType.of(
+                        new DataType[] {DataTypes.INT(), DataTypes.VARIANT()},
+                        new String[] {"k", "payload"});
+        FileStoreTable table = createPaimonTable(rowType, 
formatVersionOptions(3), "parquet");
+        String commitUser = UUID.randomUUID().toString();
+        TableWriteImpl<?> write =
+                table.newWrite(commitUser)
+                        .withIOManager(new IOManagerImpl(tempDir.toString() + 
"/tmp"));
+        TableCommitImpl commit = table.newCommit(commitUser);
+
+        write.write(GenericRow.of(1, GenericVariant.fromJson("{\"a\": 1}")));
+        commit.commit(1, write.prepareCommit(false, 1));
+        write.close();
+        commit.close();
+
+        IcebergMetadata metadata = readIcebergMetadata(table, 1);
+        assertThat(metadata.nextRowId()).isEqualTo(1L);
+        
assertThat(metadata.schemas().get(metadata.currentSchemaId()).fields().get(1).type())
+                .isEqualTo("variant");
+    }
+
+    @Test
+    public void testVariantRejectedWithFormatVersion2() throws Exception {
+        RowType rowType =
+                RowType.of(
+                        new DataType[] {DataTypes.INT(), DataTypes.VARIANT()},
+                        new String[] {"k", "payload"});
+        FileStoreTable table = createPaimonTable(rowType, 
formatVersionOptions(2), "parquet");
+        String commitUser = UUID.randomUUID().toString();
+        TableWriteImpl<?> write =
+                table.newWrite(commitUser)
+                        .withIOManager(new IOManagerImpl(tempDir.toString() + 
"/tmp"));
+        TableCommitImpl commit = table.newCommit(commitUser);
+
+        write.write(GenericRow.of(1, GenericVariant.fromJson("{\"a\": 1}")));
+        // hasStackTraceContaining: robust whether or not the commit path 
wraps the
+        // IllegalArgumentException from the guard
+        assertThatThrownBy(() -> commit.commit(1, write.prepareCommit(false, 
1)))
+                .hasStackTraceContaining("VARIANT");
+        write.close();
+        commit.close();
+    }
+
     @Test
     public void testManifestListCarriesFirstRowIdColumn() throws Exception {
         FileStoreTable table = createPaimonTable(defaultRowType(), 
formatVersionOptions(3), "avro");

Reply via email to