From 10e176362ca44e80d34e242e3d4245b0c6156a31 Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Mon, 26 Jan 2026 13:55:42 -0800 Subject: [PATCH 1/5] Add missing catalog tests --- tests/integration/test_catalog.py | 124 +++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_catalog.py b/tests/integration/test_catalog.py index f9d174dd96..e0b233cce0 100644 --- a/tests/integration/test_catalog.py +++ b/tests/integration/test_catalog.py @@ -42,6 +42,7 @@ from pyiceberg.table.metadata import INITIAL_SPEC_ID from pyiceberg.table.sorting import INITIAL_SORT_ORDER_ID, SortField, SortOrder from pyiceberg.transforms import BucketTransform, DayTransform, IdentityTransform +from pyiceberg.typedef import Identifier from pyiceberg.types import IntegerType, LongType, NestedField, TimestampType, UUIDType from tests.conftest import clean_up @@ -640,7 +641,6 @@ def test_rest_custom_namespace_separator(rest_catalog: RestCatalog, table_schema loaded_table = rest_catalog.load_table(identifier=full_table_identifier_tuple) assert loaded_table.name() == full_table_identifier_tuple - @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) def test_incompatible_partitioned_schema_evolution( @@ -671,6 +671,28 @@ def test_incompatible_partitioned_schema_evolution( assert table.spec() == PartitionSpec(PartitionField(2, 1001, DayTransform(), "tpep_pickup_day"), spec_id=1) assert table.schema() == Schema(NestedField(2, "tpep_pickup_datetime", TimestampType(), False)) +@pytest.mark.integration +@pytest.mark.parametrize("test_catalog", CATALOGS) +def test_namespace_with_slash(test_catalog: Catalog) -> None: + if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + pytest.skip(f"{type(test_catalog).__name__} does not support slash in namespace") + + namespace = ("new/db",) + + if test_catalog.namespace_exists(namespace): + test_catalog.drop_namespace(namespace) + + assert not test_catalog.namespace_exists(namespace) + + test_catalog.create_namespace(namespace) + assert test_catalog.namespace_exists(namespace) + + properties = test_catalog.load_namespace_properties(namespace) + assert properties is not None + + test_catalog.drop_namespace(namespace) + assert not test_catalog.namespace_exists(namespace) + @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) @@ -692,3 +714,103 @@ def test_incompatible_sorted_schema_evolution( assert table.schema() == Schema( NestedField(1, "VendorID", IntegerType(), False), NestedField(2, "tpep_pickup_datetime", TimestampType(), False) ) + +def test_namespace_with_dot(test_catalog: Catalog) -> None: + if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + pytest.skip(f"{type(test_catalog).__name__} does not support dot in namespace") + + namespace = ("new.db",) + + if test_catalog.namespace_exists(namespace): + test_catalog.drop_namespace(namespace) + + assert not test_catalog.namespace_exists(namespace) + + test_catalog.create_namespace(namespace) + assert test_catalog.namespace_exists(namespace) + + # list_namespaces returns a list of tuples + assert namespace in test_catalog.list_namespaces() + + properties = test_catalog.load_namespace_properties(namespace) + assert properties is not None + + test_catalog.drop_namespace(namespace) + assert not test_catalog.namespace_exists(namespace) + + +@pytest.mark.integration +@pytest.mark.parametrize("test_catalog", CATALOGS) +def test_table_name_with_slash(test_catalog: Catalog, table_schema_simple: Schema) -> None: + if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + pytest.skip(f"{type(test_catalog).__name__} does not support slash in table name") + + namespace = ("ns_slash",) + table_ident = ("ns_slash", "tab/le") + + if not test_catalog.namespace_exists(namespace): + test_catalog.create_namespace(namespace) + + if test_catalog.table_exists(table_ident): + test_catalog.drop_table(table_ident) + + assert not test_catalog.table_exists(table_ident) + + test_catalog.create_table(table_ident, table_schema_simple) + assert test_catalog.table_exists(table_ident) + + table = test_catalog.load_table(table_ident) + assert table.schema().as_struct() == table_schema_simple.as_struct() + + test_catalog.drop_table(table_ident) + assert not test_catalog.table_exists(table_ident) + + +@pytest.mark.integration +@pytest.mark.parametrize("test_catalog", CATALOGS) +def test_table_name_with_dot(test_catalog: Catalog, table_schema_simple: Schema) -> None: + if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + pytest.skip(f"{type(test_catalog).__name__} does not support dot in table name") + + namespace = ("ns_dot",) + table_ident = ("ns_dot", "ta.ble") + + if not test_catalog.namespace_exists(namespace): + test_catalog.create_namespace(namespace) + + if test_catalog.table_exists(table_ident): + test_catalog.drop_table(table_ident) + + assert not test_catalog.table_exists(table_ident) + + test_catalog.create_table(table_ident, table_schema_simple) + assert test_catalog.table_exists(table_ident) + + assert table_ident in test_catalog.list_tables(namespace) + + table = test_catalog.load_table(table_ident) + assert table.schema().as_struct() == table_schema_simple.as_struct() + + test_catalog.drop_table(table_ident) + assert not test_catalog.table_exists(table_ident) + + +@pytest.mark.integration +@pytest.mark.parametrize("test_catalog", CATALOGS) +def test_drop_missing_table(test_catalog: Catalog, database_name: str) -> None: + test_catalog.create_namespace_if_not_exists(database_name) + table_ident = (database_name, "missing_table") + assert not test_catalog.table_exists(table_ident) + with pytest.raises(NoSuchTableError): + test_catalog.drop_table(table_ident) + + +@pytest.mark.integration +@pytest.mark.parametrize("test_catalog", CATALOGS) +def test_drop_nonexistent_namespace(test_catalog: Catalog) -> None: + if isinstance(test_catalog, HiveCatalog): + pytest.skip("HiveCatalog raises NoSuchObjectException instead of NoSuchNamespaceError") + + namespace = ("non_existent_namespace",) + with pytest.raises(NoSuchNamespaceError): + test_catalog.drop_namespace(namespace) From 63595616e747629f5ec275194c1eb81e9cce693c Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Mon, 26 Jan 2026 14:31:03 -0800 Subject: [PATCH 2/5] Catalog test fixes --- pyiceberg/catalog/rest/__init__.py | 4 ++-- tests/integration/test_catalog.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pyiceberg/catalog/rest/__init__.py b/pyiceberg/catalog/rest/__init__.py index e3cc09edc0..802be28565 100644 --- a/pyiceberg/catalog/rest/__init__.py +++ b/pyiceberg/catalog/rest/__init__.py @@ -790,7 +790,7 @@ def _create_table( if location: location = location.rstrip("/") request = CreateTableRequest( - name=namespace_and_table["table"], + name=self._identifier_to_validated_tuple(identifier)[-1], location=location, table_schema=fresh_schema, partition_spec=fresh_partition_spec, @@ -869,7 +869,7 @@ def register_table(self, identifier: str | Identifier, metadata_location: str) - self._check_endpoint(Capability.V1_REGISTER_TABLE) namespace_and_table = self._split_identifier_for_path(identifier) request = RegisterTableRequest( - name=namespace_and_table["table"], + name=self._identifier_to_validated_tuple(identifier)[-1], metadata_location=metadata_location, ) serialized_json = request.model_dump_json().encode(UTF8) diff --git a/tests/integration/test_catalog.py b/tests/integration/test_catalog.py index e0b233cce0..6fe4b60cc6 100644 --- a/tests/integration/test_catalog.py +++ b/tests/integration/test_catalog.py @@ -674,7 +674,7 @@ def test_incompatible_partitioned_schema_evolution( @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) def test_namespace_with_slash(test_catalog: Catalog) -> None: - if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + if isinstance(test_catalog, HiveCatalog): pytest.skip(f"{type(test_catalog).__name__} does not support slash in namespace") namespace = ("new/db",) @@ -716,7 +716,7 @@ def test_incompatible_sorted_schema_evolution( ) def test_namespace_with_dot(test_catalog: Catalog) -> None: - if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + if isinstance(test_catalog, (HiveCatalog, SqlCatalog)): pytest.skip(f"{type(test_catalog).__name__} does not support dot in namespace") namespace = ("new.db",) @@ -730,7 +730,11 @@ def test_namespace_with_dot(test_catalog: Catalog) -> None: assert test_catalog.namespace_exists(namespace) # list_namespaces returns a list of tuples - assert namespace in test_catalog.list_namespaces() + if isinstance(test_catalog, RestCatalog): + namespaces = test_catalog.list_namespaces() + assert ("new",) in namespaces or ("new.db",) in namespaces + else: + assert namespace in test_catalog.list_namespaces() properties = test_catalog.load_namespace_properties(namespace) assert properties is not None @@ -742,7 +746,7 @@ def test_namespace_with_dot(test_catalog: Catalog) -> None: @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) def test_table_name_with_slash(test_catalog: Catalog, table_schema_simple: Schema) -> None: - if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + if isinstance(test_catalog, (HiveCatalog, SqlCatalog)): pytest.skip(f"{type(test_catalog).__name__} does not support slash in table name") namespace = ("ns_slash",) @@ -769,7 +773,7 @@ def test_table_name_with_slash(test_catalog: Catalog, table_schema_simple: Schem @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) def test_table_name_with_dot(test_catalog: Catalog, table_schema_simple: Schema) -> None: - if isinstance(test_catalog, (HiveCatalog, SqlCatalog, RestCatalog)): + if isinstance(test_catalog, (HiveCatalog, SqlCatalog)): pytest.skip(f"{type(test_catalog).__name__} does not support dot in table name") namespace = ("ns_dot",) From 4ed9cf8fcb314e890f1a1f670eb2dfebc179e142 Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Mon, 26 Jan 2026 20:30:07 -0800 Subject: [PATCH 3/5] PR comment --- tests/integration/test_catalog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_catalog.py b/tests/integration/test_catalog.py index 6fe4b60cc6..4ff1dda9f4 100644 --- a/tests/integration/test_catalog.py +++ b/tests/integration/test_catalog.py @@ -729,7 +729,8 @@ def test_namespace_with_dot(test_catalog: Catalog) -> None: test_catalog.create_namespace(namespace) assert test_catalog.namespace_exists(namespace) - # list_namespaces returns a list of tuples + # REST Catalog fixture treats this as a hierarchical namespace. + # Calling list namespaces will get `new`, not `new.db`. if isinstance(test_catalog, RestCatalog): namespaces = test_catalog.list_namespaces() assert ("new",) in namespaces or ("new.db",) in namespaces From 9c8b80bac85f46707943074fd6a64ee847a24f19 Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Wed, 28 Jan 2026 13:36:11 -0800 Subject: [PATCH 4/5] lint --- tests/integration/test_catalog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_catalog.py b/tests/integration/test_catalog.py index 4ff1dda9f4..b5bc6afb2a 100644 --- a/tests/integration/test_catalog.py +++ b/tests/integration/test_catalog.py @@ -42,7 +42,6 @@ from pyiceberg.table.metadata import INITIAL_SPEC_ID from pyiceberg.table.sorting import INITIAL_SORT_ORDER_ID, SortField, SortOrder from pyiceberg.transforms import BucketTransform, DayTransform, IdentityTransform -from pyiceberg.typedef import Identifier from pyiceberg.types import IntegerType, LongType, NestedField, TimestampType, UUIDType from tests.conftest import clean_up @@ -641,6 +640,7 @@ def test_rest_custom_namespace_separator(rest_catalog: RestCatalog, table_schema loaded_table = rest_catalog.load_table(identifier=full_table_identifier_tuple) assert loaded_table.name() == full_table_identifier_tuple + @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) def test_incompatible_partitioned_schema_evolution( @@ -671,6 +671,7 @@ def test_incompatible_partitioned_schema_evolution( assert table.spec() == PartitionSpec(PartitionField(2, 1001, DayTransform(), "tpep_pickup_day"), spec_id=1) assert table.schema() == Schema(NestedField(2, "tpep_pickup_datetime", TimestampType(), False)) + @pytest.mark.integration @pytest.mark.parametrize("test_catalog", CATALOGS) def test_namespace_with_slash(test_catalog: Catalog) -> None: @@ -715,6 +716,7 @@ def test_incompatible_sorted_schema_evolution( NestedField(1, "VendorID", IntegerType(), False), NestedField(2, "tpep_pickup_datetime", TimestampType(), False) ) + def test_namespace_with_dot(test_catalog: Catalog) -> None: if isinstance(test_catalog, (HiveCatalog, SqlCatalog)): pytest.skip(f"{type(test_catalog).__name__} does not support dot in namespace") From 4a8e830038c7d56af68193dfdf67d4484afb55ea Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Wed, 28 Jan 2026 13:41:17 -0800 Subject: [PATCH 5/5] rebase issue --- tests/integration/test_catalog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/test_catalog.py b/tests/integration/test_catalog.py index b5bc6afb2a..0e39beb241 100644 --- a/tests/integration/test_catalog.py +++ b/tests/integration/test_catalog.py @@ -717,6 +717,8 @@ def test_incompatible_sorted_schema_evolution( ) +@pytest.mark.integration +@pytest.mark.parametrize("test_catalog", CATALOGS) def test_namespace_with_dot(test_catalog: Catalog) -> None: if isinstance(test_catalog, (HiveCatalog, SqlCatalog)): pytest.skip(f"{type(test_catalog).__name__} does not support dot in namespace")