From 473f31c86ce95355387dca0325cc4d75fad5ea31 Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Sat, 11 Apr 2026 09:59:35 +0530 Subject: [PATCH 1/6] Fixes #26199: Fix Snowflake duplicate constraint names dropping uniqueness in schema --- .../source/database/snowflake/metadata.py | 2 + .../source/database/snowflake/utils.py | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/ingestion/src/metadata/ingestion/source/database/snowflake/metadata.py b/ingestion/src/metadata/ingestion/source/database/snowflake/metadata.py index d3688fd92528..a962049abb13 100644 --- a/ingestion/src/metadata/ingestion/source/database/snowflake/metadata.py +++ b/ingestion/src/metadata/ingestion/source/database/snowflake/metadata.py @@ -106,6 +106,7 @@ ) from metadata.ingestion.source.database.snowflake.utils import ( _current_database_schema, + _get_schema_unique_constraints, get_columns, get_foreign_keys, get_pk_constraint, @@ -176,6 +177,7 @@ def __init__( SnowflakeDialect.get_all_view_definitions = get_all_view_definitions SnowflakeDialect.get_view_definition = get_view_definition SnowflakeDialect.get_unique_constraints = get_unique_constraints +SnowflakeDialect._get_schema_unique_constraints = _get_schema_unique_constraints SnowflakeDialect._get_schema_columns = get_schema_columns Inspector.get_table_names = get_table_names_reflection Inspector.get_view_names = get_view_names_reflection diff --git a/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py b/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py index 72ee49956e35..ff4a31b76ffd 100644 --- a/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py +++ b/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py @@ -619,6 +619,43 @@ def get_unique_constraints(self, connection, table_name, schema, **kw): ).get(table_name, []) +def _get_schema_unique_constraints(self, connection, schema, **kw): + result = connection.execute( + text( + f"SHOW /* sqlalchemy:_get_schema_unique_constraints */ " + f"UNIQUE KEYS IN SCHEMA {schema}" + ) + ) + unique_constraints = {} + for row in result: + name = self.normalize_name(row._mapping["constraint_name"]) + table_name = self.normalize_name(row._mapping["table_name"]) + + # OpenMetadata Patch: Append the table_name into the uniqueness dictionary + # to support DBs that allow duplicate constraint names across tables under the same schema + constraint_key = (name, table_name) + + if constraint_key not in unique_constraints: + unique_constraints[constraint_key] = { + "column_names": [self.normalize_name(row._mapping["column_name"])], + "name": name, + "table_name": table_name, + } + else: + unique_constraints[constraint_key]["column_names"].append( + self.normalize_name(row._mapping["column_name"]) + ) + + ans = {} + for constraint in unique_constraints.values(): + t_name = constraint.pop("table_name") + if t_name not in ans: + ans[t_name] = [] + ans[t_name].append(constraint) + + return ans + + @reflection.cache def get_columns(self, connection, table_name, schema=None, **kw): """ From 813d31c990955f7f203870c4caa8256260187f3a Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Sat, 11 Apr 2026 12:57:41 +0530 Subject: [PATCH 2/6] Fix python lint trailing whitespaces and line length --- .../metadata/ingestion/source/database/snowflake/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py b/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py index ff4a31b76ffd..31ce0fc913ab 100644 --- a/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py +++ b/ingestion/src/metadata/ingestion/source/database/snowflake/utils.py @@ -630,11 +630,11 @@ def _get_schema_unique_constraints(self, connection, schema, **kw): for row in result: name = self.normalize_name(row._mapping["constraint_name"]) table_name = self.normalize_name(row._mapping["table_name"]) - - # OpenMetadata Patch: Append the table_name into the uniqueness dictionary - # to support DBs that allow duplicate constraint names across tables under the same schema + + # OpenMetadata Patch: Append the table_name into the uniqueness dictionary + # to support DBs that allow duplicate constraint names across tables constraint_key = (name, table_name) - + if constraint_key not in unique_constraints: unique_constraints[constraint_key] = { "column_names": [self.normalize_name(row._mapping["column_name"])], From 0b6568276c8c49a0212df69e654326bd3f439f8e Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Mon, 13 Apr 2026 19:34:56 +0530 Subject: [PATCH 3/6] test: add backend test file --- .../openmetadata/service/DummyBackendTest.java | Bin 0 -> 172 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 openmetadata-service/src/test/java/org/openmetadata/service/DummyBackendTest.java diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/DummyBackendTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/DummyBackendTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b1e928e27340256b82cf22d0104f382b21a1e113 GIT binary patch literal 172 zcmYj~OA5kJ5Cm&2c!#&tO)lUjh)3`O8bcU0F&GsjPI4WGAD=Sb{KK=tBI{*Lx literal 0 HcmV?d00001 From 171af4de06bb7821810cf2eb0495300a7b6bb124 Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Mon, 13 Apr 2026 19:38:43 +0530 Subject: [PATCH 4/6] chore: remove placeholder java test breaking CI --- .../openmetadata/service/DummyBackendTest.java | Bin 172 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 openmetadata-service/src/test/java/org/openmetadata/service/DummyBackendTest.java diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/DummyBackendTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/DummyBackendTest.java deleted file mode 100644 index b1e928e27340256b82cf22d0104f382b21a1e113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmYj~OA5kJ5Cm&2c!#&tO)lUjh)3`O8bcU0F&GsjPI4WGAD=Sb{KK=tBI{*Lx From 99f7e8ec6af25db624a891b682ab0179a8eda5c2 Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Fri, 17 Apr 2026 10:16:38 +0530 Subject: [PATCH 5/6] test: Add mock tests reproducing Snowflake duplicate constraint bugs to fix #26199 --- .../database/test_snowflake_constraints.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 ingestion/tests/unit/topology/database/test_snowflake_constraints.py diff --git a/ingestion/tests/unit/topology/database/test_snowflake_constraints.py b/ingestion/tests/unit/topology/database/test_snowflake_constraints.py new file mode 100644 index 000000000000..ac278c6dba7f --- /dev/null +++ b/ingestion/tests/unit/topology/database/test_snowflake_constraints.py @@ -0,0 +1,53 @@ +from unittest.mock import Mock +from metadata.ingestion.source.database.snowflake.utils import _get_schema_unique_constraints + +def test_snowflake_unique_constraint_collision(): + # Mocking self (SnowflakeDialect) + dialect_mock = Mock() + dialect_mock.normalize_name = lambda name: name.lower() if name else name + + # Mocking connection + connection_mock = Mock() + + # Mocking the result of connection.execute(...) + # Simulating two tables 'table_1' and 'table_2' inside the same schema + # both sharing an identical constraint name like "unique_id" + row1 = Mock() + row1._mapping = { + "constraint_name": "unique_id", + "table_name": "table_1", + "column_name": "id" + } + + row2 = Mock() + row2._mapping = { + "constraint_name": "unique_id", + "table_name": "table_2", + "column_name": "id" + } + + # Composite constraint on table_2 (second column of the same unique key) + row3 = Mock() + row3._mapping = { + "constraint_name": "unique_id", + "table_name": "table_2", + "column_name": "email" + } + + result_mock = [row1, row2, row3] + connection_mock.execute.return_value = result_mock + + # Run the patched function + output = _get_schema_unique_constraints(dialect_mock, connection_mock, "public") + + # Output should correctly split the constraints for table_1 and table_2 without collision + assert "table_1" in output + assert "table_2" in output + + assert len(output["table_1"]) == 1 + assert output["table_1"][0]["name"] == "unique_id" + assert output["table_1"][0]["column_names"] == ["id"] + + assert len(output["table_2"]) == 1 + assert output["table_2"][0]["name"] == "unique_id" + assert output["table_2"][0]["column_names"] == ["id", "email"] From 57485bcc74384b0eaae348bf30ad357c94b8c3b3 Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Fri, 17 Apr 2026 10:40:09 +0530 Subject: [PATCH 6/6] fix: set proper UTF-8 encoding for unit test file --- .../tests/unit/topology/database/test_snowflake_constraints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ingestion/tests/unit/topology/database/test_snowflake_constraints.py b/ingestion/tests/unit/topology/database/test_snowflake_constraints.py index ac278c6dba7f..9ca3d0500af1 100644 --- a/ingestion/tests/unit/topology/database/test_snowflake_constraints.py +++ b/ingestion/tests/unit/topology/database/test_snowflake_constraints.py @@ -1,4 +1,4 @@ -from unittest.mock import Mock +from unittest.mock import Mock from metadata.ingestion.source.database.snowflake.utils import _get_schema_unique_constraints def test_snowflake_unique_constraint_collision():