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 dc02d6ff90 [core] Rebuild the default value row when the write type 
changes (#10208)
dc02d6ff90 is described below

commit dc02d6ff90633aa09aa8415f359fa8813ab94c19
Author: YangJie <[email protected]>
AuthorDate: Mon Oct 5 10:11:49 2026 -0400

    [core] Rebuild the default value row when the write type changes (#10208)
---
 .../org/apache/paimon/casting/DefaultValueRow.java | 33 ++++++++++++++-
 .../apache/paimon/data/DefaultValueRowTest.java    | 23 +++++++++++
 .../apache/paimon/table/sink/TableWriteImpl.java   |  5 ++-
 .../NestedSubfieldDataEvolutionTableTest.java      | 48 ++++++++++++++++++++++
 .../NestedSubfieldMergeIntoActionITCase.java       | 22 ++++++++++
 .../apache/paimon/spark/sql/DefaultValueTest.scala | 12 ++++++
 6 files changed, 140 insertions(+), 3 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/casting/DefaultValueRow.java 
b/paimon-common/src/main/java/org/apache/paimon/casting/DefaultValueRow.java
index 555f065c70..0e73c35074 100644
--- a/paimon-common/src/main/java/org/apache/paimon/casting/DefaultValueRow.java
+++ b/paimon-common/src/main/java/org/apache/paimon/casting/DefaultValueRow.java
@@ -27,10 +27,13 @@ import org.apache.paimon.data.InternalMap;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.data.InternalVector;
 import org.apache.paimon.data.Timestamp;
+import org.apache.paimon.data.serializer.InternalRowSerializer;
 import org.apache.paimon.data.variant.Variant;
 import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
 import org.apache.paimon.types.RowKind;
 import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.NestedProjectedRow;
 
 import javax.annotation.Nullable;
 
@@ -223,6 +226,16 @@ public class DefaultValueRow implements InternalRow {
 
     @Nullable
     public static DefaultValueRow create(RowType rowType) {
+        return create(rowType, rowType);
+    }
+
+    /**
+     * Creates the default value row of {@code rowType}, which may keep only 
some sub-fields of a
+     * ROW field of {@code fullRowType}. The default of such a field is 
converted with the full ROW
+     * type and then projected to the kept sub-fields.
+     */
+    @Nullable
+    public static DefaultValueRow create(RowType rowType, RowType fullRowType) 
{
         List<DataField> fields = rowType.getFields();
         GenericRow row = new GenericRow(fields.size());
         boolean containsDefaultValue = false;
@@ -234,8 +247,7 @@ public class DefaultValueRow implements InternalRow {
             }
 
             containsDefaultValue = true;
-            Object defaultValue = convertDefaultValue(dataField.type(), 
defaultValueStr);
-            row.setField(i, defaultValue);
+            row.setField(i, defaultValueOf(dataField, fullRowType, 
defaultValueStr));
         }
 
         if (!containsDefaultValue) {
@@ -244,4 +256,21 @@ public class DefaultValueRow implements InternalRow {
 
         return DefaultValueRow.from(row);
     }
+
+    private static Object defaultValueOf(
+            DataField field, RowType fullRowType, String defaultValueStr) {
+        DataType type = field.type();
+        if (type instanceof RowType && fullRowType.containsField(field.id())) {
+            DataType fullType = fullRowType.getField(field.id()).type();
+            if (fullType instanceof RowType && !fullType.equals(type)) {
+                InternalRow fullDefault =
+                        (InternalRow) convertDefaultValue(fullType, 
defaultValueStr);
+                NestedProjectedRow projected =
+                        NestedProjectedRow.create((RowType) fullType, 
(RowType) type);
+                return new InternalRowSerializer((RowType) type)
+                        .copy(projected.replaceRow(fullDefault));
+            }
+        }
+        return convertDefaultValue(type, defaultValueStr);
+    }
 }
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/data/DefaultValueRowTest.java 
b/paimon-common/src/test/java/org/apache/paimon/data/DefaultValueRowTest.java
index 43fccf10d1..4531078493 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/data/DefaultValueRowTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/data/DefaultValueRowTest.java
@@ -25,6 +25,8 @@ import org.apache.paimon.types.RowType;
 
 import org.junit.Test;
 
+import java.util.Collections;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -123,6 +125,27 @@ public class DefaultValueRowTest {
         assertThat(propertiesValue).isNull();
     }
 
+    @Test
+    public void testDefaultValueRowProjectsRowDefaultToWriteType() {
+        RowType fullType =
+                RowType.of(
+                        new DataField(
+                                0,
+                                "nest",
+                                RowType.of(
+                                        new DataField(1, "a", DataTypes.INT()),
+                                        new DataField(2, "b", 
DataTypes.STRING())),
+                                null,
+                                "{42, z}"));
+        RowType writeType = 
fullType.projectByPaths(Collections.singletonList("nest.b"));
+
+        DefaultValueRow defaultValueRow = DefaultValueRow.create(writeType, 
fullType);
+
+        InternalRow nest = defaultValueRow.replaceRow(GenericRow.of((Object) 
null)).getRow(0, 1);
+        assertThat(nest.getFieldCount()).isEqualTo(1);
+        assertThat(nest.getString(0).toString()).isEqualTo("z");
+    }
+
     @Test
     public void testDefaultValueRowWithRowType() {
         // Test with Row default value
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java 
b/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java
index 3d728aeed7..7f9add7879 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java
@@ -63,11 +63,12 @@ public class TableWriteImpl<T> implements InnerTableWrite, 
Restorable<List<State
     @Nullable private final RowKindFilter rowKindFilter;
 
     private boolean batchCommitted = false;
+    private final RowType rowType;
     private RowType writeType;
     private int[] notNullFieldIndex;
     private int[] deleteNotNullFieldIndex;
 
-    private final @Nullable DefaultValueRow defaultValueRow;
+    private @Nullable DefaultValueRow defaultValueRow;
     private final @Nullable Set<String> deleteNotNullFieldNames;
 
     public TableWriteImpl(
@@ -95,6 +96,7 @@ public class TableWriteImpl<T> implements InnerTableWrite, 
Restorable<List<State
             @Nullable RowKindGenerator rowKindGenerator,
             @Nullable RowKindFilter rowKindFilter,
             @Nullable List<String> deleteNotNullFieldNames) {
+        this.rowType = rowType;
         this.writeType = rowType;
         this.write = write;
         this.keyAndBucketExtractor = keyAndBucketExtractor;
@@ -134,6 +136,7 @@ public class TableWriteImpl<T> implements InnerTableWrite, 
Restorable<List<State
         write.withWriteType(writeType);
         this.writeType = writeType;
         updateNotNullFieldIndexes();
+        this.defaultValueRow = DefaultValueRow.create(writeType, rowType);
         return this;
     }
 
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/NestedSubfieldDataEvolutionTableTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/NestedSubfieldDataEvolutionTableTest.java
index 3adea522b0..0ff1edbf04 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/table/NestedSubfieldDataEvolutionTableTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/NestedSubfieldDataEvolutionTableTest.java
@@ -150,6 +150,54 @@ public class NestedSubfieldDataEvolutionTableTest extends 
DataEvolutionTestBase
         }
     }
 
+    @Test
+    public void testUpdateSubFieldOfRowWithDefault() throws Exception {
+        Schema schema =
+                Schema.newBuilder()
+                        .column("id", DataTypes.INT())
+                        .column(
+                                "nest",
+                                DataTypes.ROW(
+                                        DataTypes.FIELD(0, "a", 
DataTypes.INT()),
+                                        DataTypes.FIELD(1, "b", 
DataTypes.STRING())),
+                                null,
+                                "{42, z}")
+                        .option(CoreOptions.ROW_TRACKING_ENABLED.key(), "true")
+                        .option(CoreOptions.DATA_EVOLUTION_ENABLED.key(), 
"true")
+                        
.option(CoreOptions.DATA_EVOLUTION_NESTED_FIELD_ENABLED.key(), "true")
+                        .build();
+        catalog.createTable(identifier(), schema, false);
+        FileStoreTable table = getTableDefault();
+        BatchWriteBuilder builder = table.newBatchWriteBuilder();
+        try (BatchTableWrite write = builder.newWrite()) {
+            write.write(GenericRow.of(1, GenericRow.of(10, 
BinaryString.fromString("x"))));
+            write.write(GenericRow.of(2, GenericRow.of(20, 
BinaryString.fromString("y"))));
+            commit(builder, write.prepareCommit());
+        }
+
+        RowType partialType = 
table.rowType().projectByPaths(Collections.singletonList("nest.a"));
+        try (BatchTableWrite write = 
builder.newWrite().withWriteType(partialType)) {
+            write.write(GenericRow.of(GenericRow.of(100)));
+            // a null nest takes the default {42, z} projected to nest.a
+            write.write(GenericRow.of((Object) null));
+            List<CommitMessage> messages = write.prepareCommit();
+            setFirstRowId(messages, 0L);
+            commit(builder, messages);
+        }
+
+        List<InternalRow> rows = read(table);
+        assertThat(rows).hasSize(2);
+        assertThat(rows.get(0).getInt(0)).isEqualTo(1);
+        InternalRow nest = rows.get(0).getRow(1, 2);
+        assertThat(nest.getInt(0)).isEqualTo(100);
+        assertThat(nest.getString(1).toString()).isEqualTo("x");
+        assertThat(rows.get(1).getInt(0)).isEqualTo(2);
+        nest = rows.get(1).getRow(1, 2);
+        assertThat(nest.isNullAt(0)).isFalse();
+        assertThat(nest.getInt(0)).isEqualTo(42);
+        assertThat(nest.getString(1).toString()).isEqualTo("y");
+    }
+
     @Test
     public void testDisabledOptionAllowsTopLevelColumnContainingDot() throws 
Exception {
         Schema disabledSchema =
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/NestedSubfieldMergeIntoActionITCase.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/NestedSubfieldMergeIntoActionITCase.java
index a5722ff6ce..a2a83592f4 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/NestedSubfieldMergeIntoActionITCase.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/NestedSubfieldMergeIntoActionITCase.java
@@ -119,6 +119,28 @@ public class NestedSubfieldMergeIntoActionITCase extends 
ActionITCaseBase {
         
assertThat(deltaWriteCols("T")).contains(Collections.singletonList("nest.a"));
     }
 
+    @Test
+    public void testUpdateSubFieldOfRowWithDefault() throws Exception {
+        prepareNestedTarget(true);
+        sEnv.executeSql("CALL sys.alter_column_default_value('default.T', 
'nest', '{42, z}')")
+                .await();
+        
assertThat(getFileStoreTable("T").rowType().getField("nest").defaultValue())
+                .isEqualTo("{42, z}");
+        prepareSubFieldSource();
+
+        builder(warehouse, database, "T")
+                .withMergeCondition("T.id=S.id")
+                .withMatchedUpdateSet("T.nest.a=S.newa")
+                .withSourceTable("S")
+                .withSinkParallelism(2)
+                .build()
+                .run();
+
+        testBatchRead(
+                "SELECT id, nest.a, nest.b FROM T",
+                Arrays.asList(changelogRow("+I", 1, 100, "x"), 
changelogRow("+I", 2, 20, "y")));
+    }
+
     @Test
     public void testUpdateSubFieldDisabledThrows() throws Exception {
         // data-evolution.nested-field.enabled left at its default (false)
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DefaultValueTest.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DefaultValueTest.scala
index 22edb8f61a..493b1e5f2c 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DefaultValueTest.scala
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DefaultValueTest.scala
@@ -92,6 +92,18 @@ class DefaultValueTest extends PaimonSparkTestBase {
     }
   }
 
+  test("Default Value: update a row-tracking table") {
+    withTable("t") {
+      sql("""CREATE TABLE t (id INT, data INT, c INT DEFAULT 5)
+            |TBLPROPERTIES ('row-tracking.enabled' = 'true')
+            |""".stripMargin)
+      sql("INSERT INTO t VALUES (1, 1, 10), (2, 2, 20)")
+
+      sql("UPDATE t SET data = 22 WHERE id = 2")
+      checkAnswer(sql("SELECT * FROM t ORDER BY id"), Seq(Row(1, 1, 10), 
Row(2, 22, 20)))
+    }
+  }
+
   test("Default Value: unsupported default value") {
     withTimeZone("Asia/Shanghai") {
       withTable("t") {

Reply via email to