This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git


The following commit(s) were added to refs/heads/main by this push:
     new 2ecd87b22 Glue: Prune removed table properties from Glue Parameters 
(#4133)
2ecd87b22 is described below

commit 2ecd87b222f5c7fe020c93816f8c3830412b7034
Author: Ankit Raj <[email protected]>
AuthorDate: Sat Oct 24 07:21:29 2026 +0000

    Glue: Prune removed table properties from Glue Parameters (#4133)
    
    * Glue: Prune removed table properties from Glue Parameters
    
    Closes #3977
    
    * Use keyword arguments for _construct_parameters
    
    * Prevent table properties from overwriting reserved Glue parameters
    
    A table property named table_type, metadata_location, or
    previous_metadata_location overwrote the catalog-managed Glue parameter
    of the same name, which could point Glue at a bogus metadata file and
    break load_table. Skip reserved keys when copying table properties,
    matching the Hive catalog.
    
    Co-authored-by: Copilot App <[email protected]>
    
    ---------
    
    Co-authored-by: Kevin Liu <[email protected]>
    Co-authored-by: Copilot App <[email protected]>
---
 pyiceberg/catalog/glue.py  | 19 ++++++++++++--
 tests/catalog/test_glue.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/pyiceberg/catalog/glue.py b/pyiceberg/catalog/glue.py
index 977876918..a7d0683a0 100644
--- a/pyiceberg/catalog/glue.py
+++ b/pyiceberg/catalog/glue.py
@@ -147,15 +147,22 @@ def _construct_parameters(
     glue_table: Optional["TableTypeDef"] = None,
     prev_metadata_location: str | None = None,
     metadata_properties: Properties | None = None,
+    prev_metadata_properties: Properties | None = None,
 ) -> Properties:
     new_parameters = glue_table.get("Parameters", {}) if glue_table else {}
+    if prev_metadata_properties:
+        for key in prev_metadata_properties:
+            if not metadata_properties or key not in metadata_properties:
+                new_parameters.pop(key, None)
     new_parameters.update({TABLE_TYPE: ICEBERG.upper(), METADATA_LOCATION: 
metadata_location})
     if prev_metadata_location:
         new_parameters[PREVIOUS_METADATA_LOCATION] = prev_metadata_location
 
     if metadata_properties:
         for key, value in metadata_properties.items():
-            new_parameters[key] = str(value)
+            # Reserved parameters are managed by the catalog and must not be 
overwritten by table properties
+            if key not in (TABLE_TYPE, METADATA_LOCATION, 
PREVIOUS_METADATA_LOCATION):
+                new_parameters[key] = str(value)
 
     return new_parameters
 
@@ -243,11 +250,18 @@ def _construct_table_input(
     metadata: TableMetadata,
     glue_table: Optional["TableTypeDef"] = None,
     prev_metadata_location: str | None = None,
+    prev_metadata_properties: Properties | None = None,
 ) -> "TableInputTypeDef":
     table_input: TableInputTypeDef = {
         "Name": table_name,
         "TableType": EXTERNAL_TABLE,
-        "Parameters": _construct_parameters(metadata_location, glue_table, 
prev_metadata_location, properties),
+        "Parameters": _construct_parameters(
+            metadata_location=metadata_location,
+            glue_table=glue_table,
+            prev_metadata_location=prev_metadata_location,
+            metadata_properties=properties,
+            prev_metadata_properties=prev_metadata_properties,
+        ),
         "StorageDescriptor": {
             "Columns": _to_columns(metadata),
             "Location": metadata.location,
@@ -693,6 +707,7 @@ class GlueCatalog(MetastoreCatalog):
                 metadata=updated_staged_table.metadata,
                 glue_table=current_glue_table,
                 prev_metadata_location=current_table.metadata_location,
+                prev_metadata_properties=current_table.properties,
             )
             self._update_glue_table(
                 database_name=database_name,
diff --git a/tests/catalog/test_glue.py b/tests/catalog/test_glue.py
index fd476c978..8943858b8 100644
--- a/tests/catalog/test_glue.py
+++ b/tests/catalog/test_glue.py
@@ -859,6 +859,68 @@ def test_commit_table_properties(
     assert table_info["Table"]["Parameters"]["test_c"] == "test_c"
 
 
+@mock_aws
+def test_commit_table_prunes_removed_properties(
+    _glue: boto3.client,
+    _bucket_initialize: None,
+    moto_endpoint_url: str,
+    table_schema_nested: Schema,
+    database_name: str,
+    table_name: str,
+) -> None:
+    catalog_name = "glue"
+    identifier = (database_name, table_name)
+    test_catalog = GlueCatalog(
+        catalog_name, client=None, **{"s3.endpoint": moto_endpoint_url, 
"warehouse": f"s3://{BUCKET_NAME}"}
+    )
+    test_catalog.create_namespace(namespace=database_name)
+    table = test_catalog.create_table(
+        identifier=identifier, schema=table_schema_nested, 
properties={"test_a": "test_a", "test_b": "test_b"}
+    )
+
+    transaction = table.transaction()
+    transaction.remove_properties("test_b")
+    transaction.commit_transaction()
+
+    assert table.metadata.properties == {"test_a": "test_a"}
+
+    table_info = _glue.get_table(DatabaseName=database_name, Name=table_name)
+    assert table_info["Table"]["Parameters"]["test_a"] == "test_a"
+    assert "test_b" not in table_info["Table"]["Parameters"]
+
+
+@mock_aws
+def test_commit_table_properties_do_not_overwrite_reserved_parameters(
+    _glue: boto3.client,
+    _bucket_initialize: None,
+    moto_endpoint_url: str,
+    table_schema_nested: Schema,
+    database_name: str,
+    table_name: str,
+) -> None:
+    identifier = (database_name, table_name)
+    test_catalog = GlueCatalog("glue", client=None, **{"s3.endpoint": 
moto_endpoint_url, "warehouse": f"s3://{BUCKET_NAME}"})
+    test_catalog.create_namespace(namespace=database_name)
+    table = test_catalog.create_table(identifier=identifier, 
schema=table_schema_nested)
+    previous_metadata_location = table.metadata_location
+
+    table.transaction().set_properties(
+        table_type="bogus",
+        metadata_location="s3://bogus/metadata.json",
+        previous_metadata_location="s3://bogus/previous.metadata.json",
+    ).commit_transaction()
+
+    parameters = _glue.get_table(DatabaseName=database_name, 
Name=table_name)["Table"]["Parameters"]
+    assert parameters["table_type"] == "ICEBERG"
+    assert parameters["metadata_location"] == table.metadata_location
+    assert parameters["previous_metadata_location"] == 
previous_metadata_location
+    assert test_catalog.load_table(identifier).metadata_location == 
table.metadata_location
+
+    # Removing the properties must not prune the reserved parameters either
+    table.transaction().remove_properties("table_type", "metadata_location", 
"previous_metadata_location").commit_transaction()
+    assert test_catalog.load_table(identifier).metadata_location == 
table.metadata_location
+
+
 @mock_aws
 def test_commit_append_table_snapshot_properties(
     _bucket_initialize: None, moto_endpoint_url: str, table_schema_simple: 
Schema, database_name: str, table_name: str

Reply via email to