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 18acb9ba14 [python] Preserve table UUID in REST rollback requests 
(#10528)
18acb9ba14 is described below

commit 18acb9ba14774c07682cf9b6fdeffd0fadec5e0c
Author: QuakeWang <[email protected]>
AuthorDate: Wed Oct 14 09:57:53 2026 +0800

    [python] Preserve table UUID in REST rollback requests (#10528)
    
    Signed-off-by: QuakeWang <[email protected]>
---
 paimon-python/pypaimon/api/api_request.py          |   4 +-
 paimon-python/pypaimon/api/rest_api.py             |   6 +-
 paimon-python/pypaimon/catalog/catalog.py          |   6 +-
 .../pypaimon/catalog/catalog_environment.py        |   4 +-
 .../pypaimon/catalog/rest/rest_catalog.py          |   7 +-
 paimon-python/pypaimon/catalog/table_rollback.py   |   9 +-
 .../pypaimon/tests/catalog_environment_test.py     |  68 +++++
 .../pypaimon/tests/rest/rollback_protocol_test.py  | 276 +++++++++++++++++++++
 8 files changed, 370 insertions(+), 10 deletions(-)

diff --git a/paimon-python/pypaimon/api/api_request.py 
b/paimon-python/pypaimon/api/api_request.py
index 04482e9247..7f06c789a3 100644
--- a/paimon-python/pypaimon/api/api_request.py
+++ b/paimon-python/pypaimon/api/api_request.py
@@ -20,7 +20,7 @@ from dataclasses import dataclass
 from typing import Dict, List, Optional
 
 from pypaimon.common.identifier import Identifier
-from pypaimon.common.json_util import json_field
+from pypaimon.common.json_util import json_field, optional_json_field
 from pypaimon.function.function_change import FunctionChange
 from pypaimon.function.function_definition import FunctionDefinition
 from pypaimon.management.column_mask import ColumnMask
@@ -106,9 +106,11 @@ class AlterTableRequest(RESTRequest):
 class RollbackTableRequest(RESTRequest):
     FIELD_INSTANT = "instant"
     FIELD_FROM_SNAPSHOT = "fromSnapshot"
+    FIELD_TABLE_ID = "tableId"
 
     instant: Instant = json_field(FIELD_INSTANT)
     from_snapshot: Optional[int] = json_field(FIELD_FROM_SNAPSHOT)
+    table_id: Optional[str] = optional_json_field(FIELD_TABLE_ID, "non_null")
 
 
 @dataclass
diff --git a/paimon-python/pypaimon/api/rest_api.py 
b/paimon-python/pypaimon/api/rest_api.py
index d69f49d0dc..d441ea5cbd 100755
--- a/paimon-python/pypaimon/api/rest_api.py
+++ b/paimon-python/pypaimon/api/rest_api.py
@@ -405,7 +405,7 @@ class RESTApi:
         )
         return response.is_success()
 
-    def rollback_to(self, identifier, instant, from_snapshot=None):
+    def rollback_to(self, identifier, instant, from_snapshot=None, *, 
table_uuid=None):
         """Rollback table to the given instant.
 
         Args:
@@ -413,13 +413,15 @@ class RESTApi:
             instant: The Instant (SnapshotInstant or TagInstant) to rollback 
to.
             from_snapshot: Optional snapshot ID. Success only occurs when the
                 latest snapshot is this snapshot.
+            table_uuid: Expected table or branch UUID from the loaded table 
view.
 
         Raises:
             NoSuchResourceException: If the table, snapshot or tag does not 
exist.
             ForbiddenException: If no permission to access this table.
         """
         database_name, table_name = self.__validate_identifier(identifier)
-        request = RollbackTableRequest(instant=instant, 
from_snapshot=from_snapshot)
+        request = RollbackTableRequest(
+            instant=instant, from_snapshot=from_snapshot, table_id=table_uuid)
         self.client.post(
             self.resource_paths.rollback_table(database_name, table_name),
             request,
diff --git a/paimon-python/pypaimon/catalog/catalog.py 
b/paimon-python/pypaimon/catalog/catalog.py
index 300d9c7227..1e161abfe8 100644
--- a/paimon-python/pypaimon/catalog/catalog.py
+++ b/paimon-python/pypaimon/catalog/catalog.py
@@ -186,7 +186,8 @@ class Catalog(ABC):
         """
 
     def rollback_to(self, identifier: Union[str, Identifier], instant: Instant,
-                    from_snapshot: Optional[int] = None) -> None:
+                    from_snapshot: Optional[int] = None, *,
+                    table_uuid: Optional[str] = None) -> None:
         """Rollback table by the given identifier and instant.
 
         Args:
@@ -194,6 +195,9 @@ class Catalog(ABC):
             instant: The Instant (SnapshotInstant or TagInstant) to rollback 
to.
             from_snapshot: Optional snapshot ID. Success only occurs when the
                 latest snapshot is this snapshot.
+            table_uuid: Expected table or branch UUID from the loaded table 
view.
+                Omit only when its UUID is unavailable. Implementations must 
not
+                discard a supplied UUID or reload it by name before rollback.
 
         Raises:
             TableNotExistException: If the table does not exist.
diff --git a/paimon-python/pypaimon/catalog/catalog_environment.py 
b/paimon-python/pypaimon/catalog/catalog_environment.py
index 0f15594cad..039cc41e98 100644
--- a/paimon-python/pypaimon/catalog/catalog_environment.py
+++ b/paimon-python/pypaimon/catalog/catalog_environment.py
@@ -82,7 +82,7 @@ class CatalogEnvironment:
         if self.catalog_loader is not None and 
self.supports_version_management:
             from pypaimon.catalog.table_rollback import TableRollback
             catalog = self.catalog_loader.load()
-            return TableRollback(catalog, self.identifier)
+            return TableRollback(catalog, self.identifier, self.uuid)
         return None
 
     def snapshot_loader(self) -> Optional[SnapshotLoader]:
@@ -140,7 +140,7 @@ class CatalogEnvironment:
         """
         return CatalogEnvironment(
             identifier=identifier,
-            uuid=self.uuid,
+            uuid=self.uuid if identifier == self.identifier else None,
             catalog_loader=self.catalog_loader,
             supports_version_management=self.supports_version_management,
             rest_table_response=getattr(self, 'rest_table_response', None)
diff --git a/paimon-python/pypaimon/catalog/rest/rest_catalog.py 
b/paimon-python/pypaimon/catalog/rest/rest_catalog.py
index 3425de48b0..2e0d5a046e 100644
--- a/paimon-python/pypaimon/catalog/rest/rest_catalog.py
+++ b/paimon-python/pypaimon/catalog/rest/rest_catalog.py
@@ -412,7 +412,8 @@ class RESTCatalog(Catalog):
             raise TableNoPermissionException(identifier) from e
 
     def rollback_to(self, identifier: Union[str, Identifier], instant: Instant,
-                    from_snapshot: Optional[int] = None) -> None:
+                    from_snapshot: Optional[int] = None, *,
+                    table_uuid: Optional[str] = None) -> None:
         """Rollback table by the given identifier and instant.
 
         Args:
@@ -420,6 +421,7 @@ class RESTCatalog(Catalog):
             instant: The Instant (SnapshotInstant or TagInstant) to rollback 
to.
             from_snapshot: Optional snapshot ID. Success only occurs when the
                 latest snapshot is this snapshot.
+            table_uuid: Expected table or branch UUID from the loaded table 
view.
 
         Raises:
             TableNotExistException: If the table does not exist.
@@ -428,7 +430,8 @@ class RESTCatalog(Catalog):
         if not isinstance(identifier, Identifier):
             identifier = Identifier.from_string(identifier)
         try:
-            self.rest_api.rollback_to(identifier, instant, from_snapshot)
+            self.rest_api.rollback_to(
+                identifier, instant, from_snapshot, table_uuid=table_uuid)
         except NoSuchResourceException as e:
             if e.resource_type == ErrorResponse.RESOURCE_TYPE_SNAPSHOT:
                 raise ValueError(
diff --git a/paimon-python/pypaimon/catalog/table_rollback.py 
b/paimon-python/pypaimon/catalog/table_rollback.py
index b609f717b1..50870ab6d8 100644
--- a/paimon-python/pypaimon/catalog/table_rollback.py
+++ b/paimon-python/pypaimon/catalog/table_rollback.py
@@ -18,9 +18,10 @@
 class TableRollback:
     """Rollback table to instant by delegating to catalog.rollback_to."""
 
-    def __init__(self, catalog, identifier):
+    def __init__(self, catalog, identifier, table_uuid=None):
         self._catalog = catalog
         self._identifier = identifier
+        self._table_uuid = table_uuid
 
     def rollback_to(self, instant, from_snapshot=None):
         """Rollback table to the given instant via catalog.
@@ -34,7 +35,11 @@ class TableRollback:
             RuntimeError: If the rollback fails.
         """
         try:
-            self._catalog.rollback_to(self._identifier, instant, from_snapshot)
+            if self._table_uuid is None:
+                self._catalog.rollback_to(self._identifier, instant, 
from_snapshot)
+            else:
+                self._catalog.rollback_to(
+                    self._identifier, instant, from_snapshot, 
table_uuid=self._table_uuid)
         except Exception as e:
             raise RuntimeError(
                 "Failed to rollback table {}: {}".format(
diff --git a/paimon-python/pypaimon/tests/catalog_environment_test.py 
b/paimon-python/pypaimon/tests/catalog_environment_test.py
index 9ac2e56bf9..4cfa1c56fb 100644
--- a/paimon-python/pypaimon/tests/catalog_environment_test.py
+++ b/paimon-python/pypaimon/tests/catalog_environment_test.py
@@ -36,6 +36,74 @@ class CatalogEnvironmentTest(unittest.TestCase):
 
     _READ_VIA_OPTION = RESTApi.HEADER_PREFIX + RESTApi.READ_VIA_HEADER
 
+    def test_copy_preserves_uuid_only_for_same_identifier(self):
+        main = Identifier.create("db", "table")
+        explicit_main = Identifier.create("db", "table", branch="main")
+        dev = Identifier.create("db", "table", branch="dev")
+        cases = [
+            (main, main, "source-uuid"),
+            (main, explicit_main, "source-uuid"),
+            (explicit_main, main, "source-uuid"),
+            (dev, dev, "source-uuid"),
+            (main, dev, None),
+            (dev, main, None),
+            (dev, Identifier.create("db", "table", branch="release"), None),
+            (main, Identifier.create("db", "other"), None),
+            (main, Identifier.create("other_db", "table"), None),
+        ]
+        loader = mock.Mock()
+        for source, target, expected_uuid in cases:
+            with self.subTest(source=source, target=target):
+                environment = CatalogEnvironment(
+                    identifier=source, uuid="source-uuid", 
catalog_loader=loader,
+                    supports_version_management=True)
+
+                copied = environment.copy(target)
+
+                self.assertIsNot(copied, environment)
+                self.assertEqual(copied.identifier, target)
+                self.assertEqual(copied.uuid, expected_uuid)
+                self.assertIs(copied.catalog_loader, loader)
+                self.assertTrue(copied.supports_version_management)
+                self.assertEqual(environment.identifier, source)
+                self.assertEqual(environment.uuid, "source-uuid")
+
+    def test_copy_does_not_restore_unknown_uuid(self):
+        main = Identifier.create("db", "table")
+        dev = Identifier.create("db", "table", branch="dev")
+        environment = CatalogEnvironment(identifier=main, uuid="source-uuid")
+
+        self.assertIsNone(environment.copy(dev).copy(main).uuid)
+        unknown = CatalogEnvironment(identifier=main)
+        self.assertIsNone(unknown.copy(main).uuid)
+        self.assertIsNone(unknown.copy(dev).uuid)
+
+    def test_catalog_table_rollback_captures_view_uuid(self):
+        catalog = mock.Mock()
+        identifier = Identifier.create("db", "table", branch="dev")
+        environment = CatalogEnvironment(
+            identifier=identifier, uuid="loaded-uuid",
+            catalog_loader=mock.Mock(load=mock.Mock(return_value=catalog)),
+            supports_version_management=True)
+        rollback = environment.catalog_table_rollback()
+        environment.uuid = "replacement-uuid"
+        instant = mock.sentinel.instant
+
+        rollback.rollback_to(instant, 2)
+
+        catalog.rollback_to.assert_called_once_with(
+            identifier, instant, 2, table_uuid="loaded-uuid")
+
+    def test_snapshot_commit_uses_copied_identity(self):
+        main = Identifier.create("db", "table")
+        environment = CatalogEnvironment(
+            identifier=main, uuid="loaded-uuid", catalog_loader=mock.Mock(),
+            supports_version_management=True)
+        dev = Identifier.create("db", "table", branch="dev")
+
+        self.assertEqual(environment.copy(main).snapshot_commit(None).uuid, 
"loaded-uuid")
+        self.assertIsNone(environment.copy(dev).snapshot_commit(None).uuid)
+
     def test_dependency_read_context_for_rest_catalog(self):
         root = Identifier.create("db", "root", branch="dev")
         options = Options({"other-option": "value"})
diff --git a/paimon-python/pypaimon/tests/rest/rollback_protocol_test.py 
b/paimon-python/pypaimon/tests/rest/rollback_protocol_test.py
new file mode 100644
index 0000000000..085d251515
--- /dev/null
+++ b/paimon-python/pypaimon/tests/rest/rollback_protocol_test.py
@@ -0,0 +1,276 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import json
+from types import SimpleNamespace
+from urllib.parse import unquote_plus, urlsplit
+
+import pyarrow as pa
+import pytest
+import requests
+
+from pypaimon import Schema
+from pypaimon.api.api_request import RollbackTableRequest
+from pypaimon.api.api_response import GetTableResponse
+from pypaimon.catalog.catalog_context import CatalogContext
+from pypaimon.catalog.catalog_exception import TableNotExistException, 
TableNoPermissionException
+from pypaimon.catalog.rest.rest_catalog import RESTCatalog
+from pypaimon.catalog.table_rollback import TableRollback
+from pypaimon.common.identifier import Identifier
+from pypaimon.common.json_util import JSON
+from pypaimon.common.options import Options
+from pypaimon.read.native_plan import _resolved_rest_table_response
+from pypaimon.table.instant import Instant
+from pypaimon.write.commit.commit_rollback import CommitRollback
+from pypaimon.write.native_commit import _rest_catalog_supported
+
+
[email protected]
+def transport(monkeypatch, tmp_path):
+    state = SimpleNamespace(requests=[], uuid='loaded-table-uuid', status=200)
+    schema = Schema.from_pyarrow_schema(pa.schema([('id', pa.int64())]))
+
+    def send(session, request, **kwargs):
+        state.requests.append(request)
+        response = requests.Response()
+        response.request = request
+        response.url = request.url
+        if request.method == 'GET':
+            metadata = GetTableResponse(
+                id=state.uuid,
+                name=unquote_plus(urlsplit(request.url).path.rsplit('/', 
1)[-1]),
+                path=tmp_path.as_uri(), is_external=False, schema_id=0, 
schema=schema)
+            response.status_code = 200
+            response._content = JSON.to_json(metadata).encode('utf-8')
+        else:
+            response.status_code = state.status
+            response._content = (b'' if state.status == 200 else json.dumps({
+                'code': state.status, 'message': 'table UUID mismatch',
+            }).encode('utf-8'))
+        return response
+
+    monkeypatch.setattr(requests.Session, 'send', send)
+    return state
+
+
[email protected]
+def catalog(transport):
+    return RESTCatalog(CatalogContext.create_from_options(Options({
+        'metastore': 'rest', 'uri': 'http://127.0.0.1:1', 'warehouse': 
'warehouse',
+        'prefix': 'rollback-test', 'token.provider': 'bear', 'token': 
'test-token',
+        'data-token.enabled': 'false',
+    })), config_required=False)
+
+
[email protected]
+def table(catalog, transport):
+    loaded = catalog.get_table(Identifier('db', 'table'))
+    transport.requests.clear()
+    return loaded
+
+
+def request_body(request):
+    return json.loads(request.body)
+
+
[email protected]('table_uuid', [None, 'loaded-table-uuid'])
+def test_request_serialization(table_uuid):
+    request = RollbackTableRequest(Instant.snapshot(1), 2, table_id=table_uuid)
+    expected = {'instant': {'type': 'snapshot', 'snapshotId': 1}, 
'fromSnapshot': 2}
+    if table_uuid is not None:
+        expected['tableId'] = table_uuid
+
+    assert json.loads(JSON.to_json(request)) == expected
+
+
+def test_legacy_request_constructor():
+    assert json.loads(JSON.to_json(RollbackTableRequest(Instant.snapshot(1), 
None))) == {
+        'instant': {'type': 'snapshot', 'snapshotId': 1}, 'fromSnapshot': None,
+    }
+
+
[email protected]('target,instant', [
+    (1, {'type': 'snapshot', 'snapshotId': 1}),
+    ('v1', {'type': 'tag', 'tagName': 'v1'}),
+])
+def test_table_rollback_transmits_loaded_uuid(table, transport, target, 
instant):
+    table.rollback_to(target)
+
+    assert len(transport.requests) == 1
+    assert transport.requests[0].method == 'POST'
+    assert request_body(transport.requests[0]) == {
+        'instant': instant, 'fromSnapshot': None, 'tableId': 
'loaded-table-uuid',
+    }
+
+
+def test_compact_rollback_transmits_uuid_and_expected_head(table, transport):
+    rollback = 
CommitRollback(table.catalog_environment.catalog_table_rollback())
+
+    assert rollback.try_to_rollback(SimpleNamespace(commit_kind='COMPACT', 
id=3))
+
+    assert len(transport.requests) == 1
+    assert request_body(transport.requests[0]) == {
+        'instant': {'type': 'snapshot', 'snapshotId': 2},
+        'fromSnapshot': 3, 'tableId': 'loaded-table-uuid',
+    }
+
+
[email protected]('entrypoint', ['catalog', 'api'])
+def test_direct_rollback_accepts_optional_uuid(catalog, transport, entrypoint):
+    delegate = catalog if entrypoint == 'catalog' else catalog.rest_api
+
+    delegate.rollback_to(Identifier('db', 'table'), Instant.snapshot(1), 2, 
table_uuid='expected-uuid')
+
+    assert len(transport.requests) == 1
+    assert request_body(transport.requests[0]) == {
+        'instant': {'type': 'snapshot', 'snapshotId': 1},
+        'fromSnapshot': 2, 'tableId': 'expected-uuid',
+    }
+
+
+def test_existing_calls_and_encoded_branch_url(catalog, transport):
+    identifier = Identifier('db space', 'table name', branch='dev / blue')
+
+    catalog.rollback_to(identifier, Instant.snapshot(1))
+    catalog.rollback_to(identifier, Instant.snapshot(1), 2)
+    catalog.rollback_to('db.table', Instant.tag('v1'))
+
+    assert [request_body(request)['fromSnapshot'] for request in 
transport.requests] == [None, 2, None]
+    assert all(set(request_body(request)) == {'instant', 'fromSnapshot'} for 
request in transport.requests)
+    path = urlsplit(transport.requests[0].url).path.split('/')
+    assert len(path) == 8
+    assert unquote_plus(path[4]) == identifier.get_database_name()
+    assert unquote_plus(path[6]) == identifier.get_object_name()
+    assert path[7] == 'rollback'
+
+
+def test_table_rollback_without_uuid(catalog, transport):
+    transport.uuid = None
+    table = catalog.get_table(Identifier('db', 'table'))
+    transport.requests.clear()
+
+    table.rollback_to(1)
+
+    assert len(transport.requests) == 1
+    assert request_body(transport.requests[0]) == {
+        'instant': {'type': 'snapshot', 'snapshotId': 1}, 'fromSnapshot': None,
+    }
+
+
[email protected]('status,exception', [(403, 
TableNoPermissionException), (404, TableNotExistException)])
+def test_existing_error_mapping(catalog, transport, status, exception):
+    transport.status = status
+
+    with pytest.raises(exception):
+        catalog.rollback_to(Identifier('db', 'table'), Instant.snapshot(1), 
table_uuid='expected-uuid')
+
+    assert len(transport.requests) == 1
+    assert request_body(transport.requests[0])['tableId'] == 'expected-uuid'
+
+
[email protected]('target', [1, 'v1'])
+def test_replaced_table_does_not_refresh_retry_or_clean_locally(table, 
transport, monkeypatch, target):
+    transport.uuid = 'replacement-table-uuid'
+    transport.status = 400
+
+    def unexpected_cleanup():
+        pytest.fail('Catalog rejection must not trigger local cleanup')
+
+    monkeypatch.setattr(table, 'rollback_helper', unexpected_cleanup)
+
+    with pytest.raises(RuntimeError, match='table UUID mismatch'):
+        table.rollback_to(target)
+
+    assert len(transport.requests) == 1
+    assert transport.requests[0].method == 'POST'
+    assert request_body(transport.requests[0])['tableId'] == 
'loaded-table-uuid'
+
+
+def test_compact_rollback_rejection_preserves_identity(table, transport):
+    transport.status = 400
+    rollback = 
CommitRollback(table.catalog_environment.catalog_table_rollback())
+
+    assert not rollback.try_to_rollback(SimpleNamespace(commit_kind='COMPACT', 
id=3))
+
+    assert len(transport.requests) == 1
+    assert request_body(transport.requests[0])['tableId'] == 
'loaded-table-uuid'
+    assert request_body(transport.requests[0])['fromSnapshot'] == 3
+
+
[email protected]('table_uuid', [None, 'known-uuid'])
+def test_legacy_catalog_override_never_discards_known_uuid(table_uuid):
+    calls = []
+
+    class LegacyCatalog:
+        def rollback_to(self, identifier, instant, from_snapshot=None):
+            calls.append((identifier, instant, from_snapshot))
+
+    identifier = Identifier('db', 'table')
+    instant = Instant.snapshot(1)
+    if table_uuid is None:
+        TableRollback(LegacyCatalog(), identifier).rollback_to(instant, 2)
+        assert calls == [(identifier, instant, 2)]
+    else:
+        with pytest.raises(RuntimeError) as failure:
+            TableRollback(LegacyCatalog(), identifier, 
table_uuid).rollback_to(instant, 2)
+        assert isinstance(failure.value.__cause__, TypeError)
+        assert calls == []
+
+
[email protected]('source,target', [(None, 'dev'), ('dev', None), 
('dev', 'release')])
+def test_copied_branch_rollback_omits_source_identity(catalog, transport, 
source, target):
+    table = catalog.get_table(Identifier('db', 'table', branch=source))
+    copied = table.copy({'branch': target})
+    transport.requests.clear()
+
+    copied.rollback_to(1)
+
+    assert table.catalog_environment.uuid == 'loaded-table-uuid'
+    assert copied.catalog_environment.uuid is None
+    assert len(transport.requests) == 1
+    assert 'tableId' not in request_body(transport.requests[0])
+    object_name = 
unquote_plus(urlsplit(transport.requests[0].url).path.split('/')[-2])
+    assert object_name == Identifier('db', 'table', 
branch=target).get_object_name()
+
+
+def test_same_branch_copy_and_independent_load_preserve_identity(table, 
catalog, transport):
+    assert table.copy({'scan.parallelism': '2'}).catalog_environment.uuid == 
'loaded-table-uuid'
+    assert table.copy({'branch': 'main'}).catalog_environment.uuid == 
'loaded-table-uuid'
+    assert _rest_catalog_supported(table)
+    assert not _rest_catalog_supported(table.copy({'branch': 'dev'}))
+
+    transport.uuid = 'loaded-dev-uuid'
+    branch = catalog.get_table(Identifier('db', 'table', branch='dev'))
+    assert branch.current_branch() == 'dev'
+    assert branch.catalog_environment.uuid == 'loaded-dev-uuid'
+    assert branch.copy({'branch': 'dev'}).catalog_environment.uuid == 
'loaded-dev-uuid'
+    assert branch.catalog_environment.snapshot_commit(None).uuid == 
'loaded-dev-uuid'
+    assert _rest_catalog_supported(branch)
+    transport.requests.clear()
+
+    branch.rollback_to(1)
+
+    assert len(transport.requests) == 1
+    assert request_body(transport.requests[0])['tableId'] == 'loaded-dev-uuid'
+
+
+def test_copied_branch_rejects_source_read_metadata(table, monkeypatch):
+    monkeypatch.setattr('pypaimon.read.native_plan.native_method_available', 
lambda *args: True)
+
+    assert _resolved_rest_table_response(table) == 
table.catalog_environment.rest_table_response
+    assert _resolved_rest_table_response(table.copy({'branch': 'main'})) is 
not None
+    assert _resolved_rest_table_response(table.copy({'branch': 'dev'})) is None

Reply via email to