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 9a302e7d08 [python][ray] Fix zero-block overwrite (#8632)
9a302e7d08 is described below

commit 9a302e7d082fdc2d849dc70723f9054be0db9c89
Author: QuakeWang <[email protected]>
AuthorDate: Wed Jul 15 18:14:32 2026 +0800

    [python][ray] Fix zero-block overwrite (#8632)
    
    Ray 2.53+ defers `Datasink.on_write_start()` until the first input
    bundle. A zero-block dataset has no bundle, but `on_write_complete()`
    still runs, leaving Paimon's writer builder uninitialized. Whole-table
    and static-partition overwrites therefore fail before `commit([])`.
    
    Initialize the writer builder on demand when completion needs to commit.
    Zero-block append writes remain no-ops.
---
 .../pypaimon/tests/ray_integration_test.py         | 25 ++++++++++++++++
 paimon-python/pypaimon/tests/ray_sink_test.py      | 35 ++++++++++++++++++++++
 paimon-python/pypaimon/write/ray_datasink.py       |  4 +++
 3 files changed, 64 insertions(+)

diff --git a/paimon-python/pypaimon/tests/ray_integration_test.py 
b/paimon-python/pypaimon/tests/ray_integration_test.py
index c79b62e9fb..875a9a3c9e 100644
--- a/paimon-python/pypaimon/tests/ray_integration_test.py
+++ b/paimon-python/pypaimon/tests/ray_integration_test.py
@@ -396,6 +396,31 @@ class RayIntegrationTest(unittest.TestCase):
         result = read_paimon(identifier, self.catalog_options)
         self.assertEqual(result.count(), 0)
 
+    def test_write_paimon_zero_block_overwrite_unpartitioned(self):
+        """An overwrite with no Ray blocks clears an unpartitioned table."""
+        from pypaimon.ray import read_paimon, write_paimon
+
+        pa_schema = pa.schema([('id', pa.int64())])
+        identifier = 'default.test_write_zero_block_overwrite_unpartitioned'
+        catalog = CatalogFactory.create(self.catalog_options)
+        schema = Schema.from_pyarrow_schema(pa_schema)
+        catalog.create_table(identifier, schema, False)
+
+        initial = ray.data.from_arrow(
+            pa.Table.from_pydict({'id': [1, 2]}, schema=pa_schema)
+        )
+        write_paimon(initial, identifier, self.catalog_options)
+
+        write_paimon(
+            ray.data.range(0),
+            identifier,
+            self.catalog_options,
+            overwrite=True,
+        )
+
+        result = read_paimon(identifier, self.catalog_options)
+        self.assertEqual(result.count(), 0)
+
     def test_table_write_ray_builder_partition_overwrite(self):
         """Builder-level partition overwrite is honored by write_ray()."""
         from pypaimon.ray import read_paimon
diff --git a/paimon-python/pypaimon/tests/ray_sink_test.py 
b/paimon-python/pypaimon/tests/ray_sink_test.py
index afc86b571e..5dc462304a 100644
--- a/paimon-python/pypaimon/tests/ray_sink_test.py
+++ b/paimon-python/pypaimon/tests/ray_sink_test.py
@@ -453,6 +453,41 @@ class RaySinkTest(unittest.TestCase):
             datasink.on_write_complete(write_result)
         self.assertEqual(len(datasink._pending_commit_messages), 1)
 
+    def test_on_write_complete_without_on_write_start(self):
+        from ray.data.datasource.datasink import WriteResult
+
+        write_result = WriteResult(
+            num_rows=0,
+            size_bytes=0,
+            write_returns=[],
+        )
+
+        for overwrite, static_partition in [
+            (True, None),
+            (False, {'dt': '2024-01-01'}),
+        ]:
+            with self.subTest(
+                overwrite=overwrite,
+                static_partition=static_partition,
+            ):
+                table = Mock()
+                table.identifier.get_full_name.return_value = 
'test_db.test_table'
+                writer_builder = table.new_batch_write_builder.return_value
+                writer_builder.overwrite.return_value = writer_builder
+                table_commit = writer_builder.new_commit.return_value
+
+                datasink = PaimonDatasink(
+                    table,
+                    overwrite=overwrite,
+                    static_partition=static_partition,
+                )
+                datasink.on_write_complete(write_result)
+
+                table.new_batch_write_builder.assert_called_once_with()
+                
writer_builder.overwrite.assert_called_once_with(static_partition)
+                table_commit.commit.assert_called_once_with([])
+                table_commit.close.assert_called_once_with()
+
     def test_table_write_ray_forwards_static_partition(self):
         dataset = Mock()
         table_write = TableWrite.__new__(TableWrite)
diff --git a/paimon-python/pypaimon/write/ray_datasink.py 
b/paimon-python/pypaimon/write/ray_datasink.py
index 6f7a336190..37c753b4ca 100644
--- a/paimon-python/pypaimon/write/ray_datasink.py
+++ b/paimon-python/pypaimon/write/ray_datasink.py
@@ -188,6 +188,10 @@ class PaimonDatasink(_DatasinkBase):
                 self._pending_commit_messages = []
                 return
 
+            # Ray does not call on_write_start when the input has no blocks.
+            if self._writer_builder is None:
+                self.on_write_start()
+
             logger.info(
                 f"Committing {len(non_empty_messages)} commit messages "
                 f"for table {self._table_name}"

Reply via email to