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 f9fde2d810 [python] Reject dropping the main branch (#10855)
f9fde2d810 is described below

commit f9fde2d81091393b8cd2922ecf466ea6eaaf71b1
Author: Jiajia Li <[email protected]>
AuthorDate: Thu Oct 29 10:10:27 2026 +0800

    [python] Reject dropping the main branch (#10855)
---
 .../pypaimon/branch/filesystem_branch_manager.py   |  4 +++-
 .../pypaimon/tests/branch_manager_test.py          | 19 ++++++++++++++++
 .../tests/filesystem_catalog_branch_test.py        | 10 +++++++++
 paimon-python/pypaimon/tests/jdbc_catalog_test.py  | 26 ++++++++++++++++++++++
 4 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/paimon-python/pypaimon/branch/filesystem_branch_manager.py 
b/paimon-python/pypaimon/branch/filesystem_branch_manager.py
index 855e30603e..ddd83a0f65 100644
--- a/paimon-python/pypaimon/branch/filesystem_branch_manager.py
+++ b/paimon-python/pypaimon/branch/filesystem_branch_manager.py
@@ -180,8 +180,10 @@ class FileSystemBranchManager(BranchManager):
             branch_name: Name of the branch to drop
 
         Raises:
-            ValueError: If branch doesn't exist
+            ValueError: If branch is main or doesn't exist
         """
+        if BranchManager.is_main_branch(branch_name):
+            raise ValueError("The main branch cannot be deleted.")
         if not self.branch_exists(branch_name):
             raise ValueError(f"Branch name '{branch_name}' doesn't exist.")
 
diff --git a/paimon-python/pypaimon/tests/branch_manager_test.py 
b/paimon-python/pypaimon/tests/branch_manager_test.py
index dc70797b76..40b023e286 100644
--- a/paimon-python/pypaimon/tests/branch_manager_test.py
+++ b/paimon-python/pypaimon/tests/branch_manager_test.py
@@ -184,6 +184,25 @@ class FileSystemBranchManagerTest(unittest.TestCase):
         self.assertTrue(manager.branch_exists("feature"))
         self.assertFalse(manager.branch_exists("develop"))
 
+    def test_main_branch_validation_before_file_access(self):
+        from unittest.mock import Mock
+
+        file_io = Mock()
+        manager = FileSystemBranchManager(file_io, self.table_path, None, 
None, None)
+        for operation, args, message in (
+            (manager.drop_branch, ("main",), "The main branch cannot be 
deleted."),
+            (manager.create_branch, ("main",),
+             "Branch name 'main' is the default branch and cannot be used."),
+            (manager.create_branch, ("main", "absent"),
+             "Branch name 'main' is the default branch and cannot be used."),
+            (manager.fast_forward, ("main",), "Branch name 'main' do not use 
in fast-forward."),
+        ):
+            with self.subTest(operation=operation.__name__, args=args):
+                with self.assertRaises(ValueError) as cm:
+                    operation(*args)
+                self.assertEqual(str(cm.exception), message)
+        self.assertEqual(file_io.mock_calls, [])
+
 
 class SnapshotManagerBranchAwarenessTest(unittest.TestCase):
     """Pin down SnapshotManager's branch-aware path computation.
diff --git a/paimon-python/pypaimon/tests/filesystem_catalog_branch_test.py 
b/paimon-python/pypaimon/tests/filesystem_catalog_branch_test.py
index b1fe5cb681..e8b21523f9 100644
--- a/paimon-python/pypaimon/tests/filesystem_catalog_branch_test.py
+++ b/paimon-python/pypaimon/tests/filesystem_catalog_branch_test.py
@@ -238,6 +238,16 @@ class FileSystemCatalogBranchCRUDTest(unittest.TestCase):
             self.catalog.drop_branch(self.identifier, "absent")
         self.assertEqual(cm.exception.branch, "absent")
 
+    def test_drop_main_branch_preserves_table(self):
+        table = self.catalog.get_table(self.identifier)
+        with self.assertRaisesRegex(ValueError, r"^The main branch cannot be 
deleted\.$"):
+            self.catalog.drop_branch(self.identifier, "main")
+        self.assertTrue(os.path.isdir(table.table_path))
+        self.assertEqual(
+            self._read(self.catalog.get_table(self.identifier)).to_pydict(),
+            {"id": [1, 2, 3], "value": ["a", "b", "c"]},
+        )
+
     # -- fast_forward ---------------------------------------------------------
 
     def test_fast_forward_missing_raises(self):
diff --git a/paimon-python/pypaimon/tests/jdbc_catalog_test.py 
b/paimon-python/pypaimon/tests/jdbc_catalog_test.py
index 04ca55e250..9b017bacff 100644
--- a/paimon-python/pypaimon/tests/jdbc_catalog_test.py
+++ b/paimon-python/pypaimon/tests/jdbc_catalog_test.py
@@ -22,6 +22,8 @@ import sqlite3
 import tempfile
 import unittest
 
+import pyarrow as pa
+
 from pypaimon import CatalogFactory, Schema
 from pypaimon.catalog.catalog_exception import (
     DatabaseAlreadyExistException,
@@ -190,6 +192,30 @@ class JdbcCatalogTest(unittest.TestCase):
         self.assertEqual(table_count, 0)
         self.assertFalse(os.path.exists(os.path.join(self.warehouse, 
"test_db.db", "test_table")))
 
+    def test_drop_main_branch_preserves_table(self):
+        with CatalogFactory.create(self.options) as catalog:
+            catalog.create_database("test_db", False)
+            data = pa.table({"id": [1]})
+            catalog.create_table("test_db.test_table", 
Schema.from_pyarrow_schema(data.schema), False)
+            table = catalog.get_table("test_db.test_table")
+            builder = table.new_batch_write_builder()
+            writer = builder.new_write()
+            try:
+                writer.write_arrow(data)
+                builder.new_commit().commit(writer.prepare_commit())
+            finally:
+                writer.close()
+
+            with self.assertRaisesRegex(ValueError, r"^The main branch cannot 
be deleted\.$"):
+                table.branch_manager().drop_branch("main")
+            self.assertTrue(os.path.isdir(table.table_path))
+            table = catalog.get_table("test_db.test_table")
+            reader = table.new_read_builder()
+            self.assertEqual(
+                
reader.new_read().to_arrow(reader.new_scan().plan().splits()).to_pydict(),
+                {"id": [1]},
+            )
+
     def test_rename_table_keeps_metadata_when_file_move_fails(self):
         fields = [DataField.from_dict({"id": 1, "name": "f0", "type": "INT"})]
         catalog = CatalogFactory.create(self.options)

Reply via email to