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 27dc9b24f5 [python] Preserve fields from later Python MERGE source 
rows (#10093)
27dc9b24f5 is described below

commit 27dc9b24f5f2e083f6d4477346abd2f8010f8e40
Author: chaoyang <[email protected]>
AuthorDate: Thu Sep 24 14:39:09 2026 +0800

    [python] Preserve fields from later Python MERGE source rows (#10093)
---
 paimon-python/pypaimon/multimodal/table.py         |  6 ++-
 .../pypaimon/tests/multimodal_table_test.py        | 46 ++++++++++++++++++++++
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/paimon-python/pypaimon/multimodal/table.py 
b/paimon-python/pypaimon/multimodal/table.py
index 81f302c499..d988b91e07 100644
--- a/paimon-python/pypaimon/multimodal/table.py
+++ b/paimon-python/pypaimon/multimodal/table.py
@@ -599,7 +599,11 @@ def _to_arrow_table(data, target_schema=None):
     elif isinstance(data, pa.RecordBatch):
         table = pa.Table.from_batches([data])
     elif isinstance(data, list):
-        table = pa.Table.from_pylist(data)
+        # Inference from the first row alone drops fields in later source rows.
+        names = dict.fromkeys(name for row in data for name in row)
+        table = pa.Table.from_pydict({
+            name: [row.get(name) for row in data] for name in names
+        })
     elif isinstance(data, dict):
         table = pa.Table.from_pydict(data)
     elif hasattr(data, "__dataframe__") or 
data.__class__.__module__.startswith("pandas"):
diff --git a/paimon-python/pypaimon/tests/multimodal_table_test.py 
b/paimon-python/pypaimon/tests/multimodal_table_test.py
index a61477f100..6551e743e0 100644
--- a/paimon-python/pypaimon/tests/multimodal_table_test.py
+++ b/paimon-python/pypaimon/tests/multimodal_table_test.py
@@ -2395,6 +2395,52 @@ class MultimodalTableTest(unittest.TestCase):
             rows,
         )
 
+    def test_merge_preserves_fields_first_present_in_later_source_rows(self):
+        users = self.conn.create_table(
+            "sparse_merge",
+            data=[
+                {"id": 1, "name": "Alice", "age": 30},
+                {"id": 2, "name": "Bob", "age": 25},
+            ],
+            schema=_schema({
+                "id": pa.int32(), "name": pa.string(), "age": pa.int32(),
+            }),
+            options=_PARQUET_OPTIONS,
+        )
+        source = [{"id": 1}, {"id": 2, "name": "Bobby"},
+                  {"id": 3, "name": "Carol"}]
+
+        users.merge("id").when_matched_update() \
+            .when_not_matched_insert().execute(source)
+
+        self.assertEqual([
+            {"id": 1, "name": None, "age": 30},
+            {"id": 2, "name": "Bobby", "age": 25},
+            {"id": 3, "name": "Carol", "age": None},
+        ], sorted(users.scan().to_list(), key=lambda row: row["id"]))
+        self.assertEqual({"id": 1}, source[0])
+
+    def test_merge_preserves_later_source_only_fields_with_mapped_keys(self):
+        users = self.conn.create_table(
+            "sparse_mapped_merge",
+            data=[{"id": 1, "name": "Alice"}],
+            schema=_schema({"id": pa.int32(), "name": pa.string()}),
+            options=_PARQUET_OPTIONS,
+        )
+        values = {"name": source_col("new_name")}
+
+        users.merge({"id": "source_id"}).when_matched_update(values) \
+            .when_not_matched_insert({
+                "id": source_col("source_id"), **values,
+            }).execute([
+                {"source_id": 1},
+                {"source_id": 2, "new_name": "Bob"},
+            ])
+
+        self.assertEqual([
+            {"id": 1, "name": None}, {"id": 2, "name": "Bob"},
+        ], sorted(users.scan().to_list(), key=lambda row: row["id"]))
+
     def test_merge_supports_source_key_mapping(self):
         users = self.conn.create_table(
             "users",

Reply via email to