diff --git a/great_expectations/datasource/fluent/sql_datasource.py b/great_expectations/datasource/fluent/sql_datasource.py index 86ce699de110..52596cda2811 100644 --- a/great_expectations/datasource/fluent/sql_datasource.py +++ b/great_expectations/datasource/fluent/sql_datasource.py @@ -1171,9 +1171,17 @@ def test_connection(self) -> None: effective_schema = self._effective_schema_name if effective_schema and effective_schema not in schema_names: - raise TestConnectionError( # noqa: TRY003 # FIXME CoP - f'Attempt to connect to table: "{self.qualified_name}" failed because the schema ' - f'"{effective_schema}" does not exist.' + # Some backends (e.g. SQL Server / MSSQL) only list schemas the connected + # user has VIEW DEFINITION on via get_schema_names(). A user with only + # SELECT permission on a table can still access the schema, so a missing + # entry here does not reliably indicate the schema is absent. Log a warning + # and fall through to the table-level access check, which will surface a + # clear error if the schema or table genuinely does not exist (issue #10499). + LOGGER.warning( + f'Schema "{effective_schema}" was not found in the list returned by ' + "inspector.get_schema_names(). This can occur when the connected user " + "lacks VIEW DEFINITION permission on the schema (e.g. SQL Server). " + "Proceeding to validate table accessibility directly." ) try: diff --git a/tests/datasource/fluent/test_sql_datasources.py b/tests/datasource/fluent/test_sql_datasources.py index f3d0b773a9ac..c92f17f31796 100644 --- a/tests/datasource/fluent/test_sql_datasources.py +++ b/tests/datasource/fluent/test_sql_datasources.py @@ -473,5 +473,86 @@ def test_table_name_serialization_preserves_quotes( assert serialized["table_name"] == serialized_name +@pytest.mark.unit +class TestTableAssetTestConnection: + """Tests for TableAsset.test_connection schema-listing behaviour (issue #10499).""" + + def _make_table_asset(self, schema_name: str) -> TableAsset: + """Return a TableAsset with a fixed datasource whose schema_ matches schema_name.""" + ds = SQLDatasource( + name="my_datasource", + connection_string="sqlite:///", + ) + asset = ds.add_table_asset(name="my_asset", table_name="my_table") + # Override schema_name directly on the asset so _effective_schema_name returns it. + object.__setattr__(asset, "schema_name", schema_name) + return asset + + def test_schema_absent_from_get_schema_names_does_not_raise( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """When inspector.get_schema_names() omits the schema (e.g. because the + connected SQL Server user lacks VIEW DEFINITION on that schema), test_connection + should NOT raise. It should log a warning and let the table-level query decide + whether the asset is reachable (issue #10499).""" + asset = self._make_table_asset("my_schema") + + mock_engine = mock.MagicMock() + mock_inspector = mock.MagicMock() + # get_schema_names() returns only system schemas — omits "my_schema". + mock_inspector.get_schema_names.return_value = ["dbo", "guest", "information_schema"] + mock_conn = mock.MagicMock() + mock_engine.connect.return_value.__enter__ = mock.MagicMock(return_value=mock_conn) + mock_engine.connect.return_value.__exit__ = mock.MagicMock(return_value=False) + + with ( + mock.patch( + "great_expectations.datasource.fluent.sql_datasource.TableAsset.datasource", + new_callable=mock.PropertyMock, + return_value=mock.MagicMock(get_engine=mock.MagicMock(return_value=mock_engine)), + ), + mock.patch( + "great_expectations.datasource.fluent.sql_datasource.sa.inspect", + return_value=mock_inspector, + ), + ): + # Should not raise even though "my_schema" is not in schema_names. + asset.test_connection() + + def test_schema_absent_and_table_inaccessible_raises( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """When the schema is absent from get_schema_names() AND the table query also + fails, test_connection must still surface a TestConnectionError so the caller + knows the asset is not reachable.""" + from great_expectations.datasource.fluent.interfaces import TestConnectionError + + asset = self._make_table_asset("nonexistent_schema") + + mock_engine = mock.MagicMock() + mock_inspector = mock.MagicMock() + mock_inspector.get_schema_names.return_value = ["dbo"] + mock_conn = mock.MagicMock() + mock_conn.execute.side_effect = Exception("schema or table not found") + mock_engine.connect.return_value.__enter__ = mock.MagicMock(return_value=mock_conn) + mock_engine.connect.return_value.__exit__ = mock.MagicMock(return_value=False) + + with ( + mock.patch( + "great_expectations.datasource.fluent.sql_datasource.TableAsset.datasource", + new_callable=mock.PropertyMock, + return_value=mock.MagicMock(get_engine=mock.MagicMock(return_value=mock_engine)), + ), + mock.patch( + "great_expectations.datasource.fluent.sql_datasource.sa.inspect", + return_value=mock_inspector, + ), + ): + with pytest.raises(TestConnectionError): + asset.test_connection() + + if __name__ == "__main__": pytest.main([__file__, "-vv"])