diff --git a/pyiceberg/catalog/rest/__init__.py b/pyiceberg/catalog/rest/__init__.py index f7d8eec960..913dc85bea 100644 --- a/pyiceberg/catalog/rest/__init__.py +++ b/pyiceberg/catalog/rest/__init__.py @@ -654,6 +654,16 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U "source": self._split_identifier_for_json(from_identifier), "destination": self._split_identifier_for_json(to_identifier), } + + # Ensure that namespaces exist on source and destination. + source_namespace = self._split_identifier_for_json(from_identifier)["namespace"] + if not self.namespace_exists(source_namespace): + raise NoSuchNamespaceError(f"Source namespace does not exist: {source_namespace}") + + destination_namespace = self._split_identifier_for_json(to_identifier)["namespace"] + if not self.namespace_exists(destination_namespace): + raise NoSuchNamespaceError(f"Destination namespace does not exist: {destination_namespace}") + response = self._session.post(self.url(Endpoints.rename_table), json=payload) try: response.raise_for_status() diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py index 223c6d2f9e..5aee65d8b5 100644 --- a/tests/catalog/test_rest.py +++ b/tests/catalog/test_rest.py @@ -1353,6 +1353,11 @@ def test_rename_table_200(rest_mock: Mocker, example_table_metadata_with_snapsho status_code=200, request_headers=TEST_HEADERS, ) + rest_mock.head( + f"{TEST_URI}v1/namespaces/pdames", + status_code=200, + request_headers=TEST_HEADERS, + ) catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN) from_identifier = ("pdames", "source") to_identifier = ("pdames", "destination") @@ -1396,6 +1401,11 @@ def test_rename_table_from_self_identifier_200( status_code=200, request_headers=TEST_HEADERS, ) + rest_mock.head( + f"{TEST_URI}v1/namespaces/pdames", + status_code=200, + request_headers=TEST_HEADERS, + ) actual = catalog.rename_table(table.name(), to_identifier) expected = Table( identifier=("pdames", "destination"), @@ -1408,6 +1418,48 @@ def test_rename_table_from_self_identifier_200( assert actual == expected +def test_rename_table_source_namespace_does_not_exist(rest_mock: Mocker) -> None: + catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN) + from_identifier = ("invalid", "source") + to_identifier = ("pdames", "destination") + + rest_mock.head( + f"{TEST_URI}v1/namespaces/invalid", + status_code=404, + request_headers=TEST_HEADERS, + ) + rest_mock.head( + f"{TEST_URI}v1/namespaces/pdames", + status_code=200, + request_headers=TEST_HEADERS, + ) + + with pytest.raises(NoSuchNamespaceError) as e: + catalog.rename_table(from_identifier, to_identifier) + assert "Source namespace does not exist" in str(e.value) + + +def test_rename_table_destination_namespace_does_not_exist(rest_mock: Mocker) -> None: + catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN) + from_identifier = ("pdames", "source") + to_identifier = ("invalid", "destination") + + rest_mock.head( + f"{TEST_URI}v1/namespaces/pdames", + status_code=200, + request_headers=TEST_HEADERS, + ) + rest_mock.head( + f"{TEST_URI}v1/namespaces/invalid", + status_code=404, + request_headers=TEST_HEADERS, + ) + + with pytest.raises(NoSuchNamespaceError) as e: + catalog.rename_table(from_identifier, to_identifier) + assert "Destination namespace does not exist" in str(e.value) + + def test_delete_table_404(rest_mock: Mocker) -> None: rest_mock.delete( f"{TEST_URI}v1/namespaces/example/tables/fokko",