From 3573a13075b751ea15934fa49fe8c08cfa4f752f Mon Sep 17 00:00:00 2001 From: Noel Gomez Date: Wed, 23 Sep 2026 10:50:51 -0700 Subject: [PATCH] fix(tag_masking_policy_reference): treat a missing database as not-created-yet Querying .INFORMATION_SCHEMA.POLICY_REFERENCES for a tag's own database fails with INVALID_IDENTIFIER (2004), not DOES_NOT_EXIST_ERR (2003), when that database doesn't exist yet -- e.g. a fresh account where the same config creates the database and declares the masking policy reference in one plan. This mirrors the existing DOES_NOT_EXIST_ERR handling used for roles. --- snowcap/data_provider.py | 9 ++++-- tests/test_data_provider.py | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/snowcap/data_provider.py b/snowcap/data_provider.py index 9640c8c..61b9e94 100644 --- a/snowcap/data_provider.py +++ b/snowcap/data_provider.py @@ -5238,8 +5238,9 @@ def list_tag_masking_policy_references(session: SnowflakeConnection) -> list[FQN logger.debug(f" Found tag masking policy reference: {fqn}") references.append(fqn) except ProgrammingError as err: - if err.errno in (ACCESS_CONTROL_ERR, UNSUPPORTED_FEATURE, DOES_NOT_EXIST_ERR): - # Skip tags we can't access or don't exist + if err.errno in (ACCESS_CONTROL_ERR, UNSUPPORTED_FEATURE, DOES_NOT_EXIST_ERR, INVALID_IDENTIFIER): + # Skip tags we can't access, that don't exist, or whose database doesn't + # exist yet (Snowflake reports that as INVALID_IDENTIFIER, not DOES_NOT_EXIST_ERR). logger.debug(f" Skipping tag {tag_fqn}: {err.msg}") continue else: @@ -5299,7 +5300,9 @@ def fetch_tag_masking_policy_reference(session: SnowflakeConnection, fqn: FQN) - "masking_policy_name": f"{policy_db}.{policy_schema}.{policy_name}", } except ProgrammingError as err: - if err.errno in (ACCESS_CONTROL_ERR, UNSUPPORTED_FEATURE, DOES_NOT_EXIST_ERR): + if err.errno in (ACCESS_CONTROL_ERR, UNSUPPORTED_FEATURE, DOES_NOT_EXIST_ERR, INVALID_IDENTIFIER): + # INVALID_IDENTIFIER covers the tag's own database not existing yet, e.g. when + # the same config that declares the reference also creates that database. logger.debug(f"Cannot fetch tag masking policy reference {fqn}: {err.msg}") return None else: diff --git a/tests/test_data_provider.py b/tests/test_data_provider.py index a4117ec..e69c7c7 100644 --- a/tests/test_data_provider.py +++ b/tests/test_data_provider.py @@ -49,6 +49,7 @@ fetch_shared_database, fetch_security_integration, fetch_task, + fetch_tag_masking_policy_reference, fetch_user, fetch_warehouse, fetch_streamlit, @@ -57,6 +58,7 @@ list_schema_scoped_resource, list_stages, list_tables, + list_tag_masking_policy_references, list_views, # Session functions fetch_account_locator, @@ -3736,3 +3738,61 @@ def test_an_empty_login_name_reads_as_none(self, mock_show, mock_execute): assert data["login_name"] is None assert data["display_name"] is None + + +class TestTagMaskingPolicyReferenceMissingDatabase: + """A tag's own database can not exist yet when the same config also creates it. + + Querying `.INFORMATION_SCHEMA.POLICY_REFERENCES` for that database fails + with INVALID_IDENTIFIER (2004), not DOES_NOT_EXIST_ERR (2003) -- Snowflake can't even + resolve the qualified name. Treat it the same as a not-yet-created reference. + """ + + @patch("snowcap.data_provider.execute") + def test_fetch_returns_none_when_database_does_not_exist_yet(self, mock_execute): + from snowflake.connector.errors import ProgrammingError + + mock_execute.side_effect = ProgrammingError( + errno=2004, msg="Invalid identifier GOVERNANCE.INFORMATION_SCHEMA.POLICY_REFERENCES" + ) + fqn = FQN( + database=ResourceName("GOVERNANCE"), + schema=ResourceName("TAGS"), + name=ResourceName("PII"), + params={"masking_policy": "governance.policies.mask_pii"}, + ) + + result = fetch_tag_masking_policy_reference(MagicMock(), fqn) + + assert result is None + + @patch("snowcap.data_provider.execute") + def test_fetch_raises_on_other_programming_errors(self, mock_execute): + from snowflake.connector.errors import ProgrammingError + + mock_execute.side_effect = ProgrammingError(errno=1234) + fqn = FQN( + database=ResourceName("GOVERNANCE"), + schema=ResourceName("TAGS"), + name=ResourceName("PII"), + params={"masking_policy": "governance.policies.mask_pii"}, + ) + + with pytest.raises(ProgrammingError): + fetch_tag_masking_policy_reference(MagicMock(), fqn) + + @patch("snowcap.data_provider.list_tags") + @patch("snowcap.data_provider.execute") + def test_list_skips_a_tag_whose_database_does_not_exist_yet(self, mock_execute, mock_list_tags): + from snowflake.connector.errors import ProgrammingError + + mock_list_tags.return_value = [ + FQN(database=ResourceName("GOVERNANCE"), schema=ResourceName("TAGS"), name=ResourceName("PII")) + ] + mock_execute.side_effect = ProgrammingError( + errno=2004, msg="Invalid identifier GOVERNANCE.INFORMATION_SCHEMA.POLICY_REFERENCES" + ) + + result = list_tag_masking_policy_references(MagicMock()) + + assert result == []