mobuchowski commented on code in PR #74805:
URL: https://github.com/apache/airflow/pull/74805#discussion_r4375460605


##########
providers/openlineage/src/airflow/providers/openlineage/utils/sql_hook_lineage.py:
##########
@@ -133,7 +133,8 @@ def _get_database_info(hook_id: int):
         try:
             return hook.get_openlineage_database_info(_get_connection(hook_id))

Review Comment:
   `_get_database_info` still calls `hook.get_openlineage_database_info(...)` 
directly inside `except Exception`. The other two call sites in this PR now use 
`getattr(..., None)`. Since this branch now logs at WARNING, a hook that simply 
doesn't implement the method also produces a warning.
   
   Several hooks send SQL hook lineage without being `DbApiHook` subclasses and 
without defining the method: `AthenaHook` (`athena.py:138`), `RedshiftDataHook` 
(`redshift_data.py:158`) and `HiveCliHook` (`hive.py:372`). `_get_hook_conn_id` 
resolves a conn id for them through `conn_name_attr` (e.g. `aws_default`), so 
`_get_database_info` runs. It hits `AttributeError: 'AthenaHook' object has no 
attribute 'get_openlineage_database_info'` and now logs:
   
   `WARNING Failed to get OpenLineage database info for aws_default: 
'AthenaHook' object has no attribute ...`
   
   That shows up on every task that uses these hooks. It's a supported "no 
database info" case, not a failure, and it looks exactly like the real errors 
this PR is trying to surface.
   
   Suggest using the same lookup as the other call sites:
   ```python
   get_database_info = getattr(hook, "get_openlineage_database_info", None)
   if get_database_info is None:
       return None
   try:
       return get_database_info(_get_connection(hook_id))
   except Exception as e:
       log.warning(...)
   ```



##########
providers/openlineage/tests/unit/openlineage/utils/test_sql_hook_lineage.py:
##########
@@ -191,6 +191,42 @@ def 
test_sql_parsing_failure_falls_back_to_normalized_sql_facet(self):
         assert call.kwargs["job_facets"]["sql"].query == 
SQLParser.normalize_sql("SELECT broken(")
         assert call.kwargs["run_facets"]["externalQuery"].externalQueryId == 
"qid-1"
 
+    def test_sql_parsing_failure_is_logged_as_warning(self, caplog):
+        self.mock_ns.return_value = "ns"
+        self.mock_facets_fn.side_effect = Exception("parse error")
+        mock_ti = mock.MagicMock(dag_id="dag_id", task_id="task_id")
+
+        with caplog.at_level(logging.WARNING, logger=_MODULE):
+            emit_lineage_from_sql_extras(
+                task_instance=mock_ti,
+                sql_extras=[_make_extra(sql="SELECT broken(", job_id="qid-1")],
+            )
+
+        assert "Failed to parse SQL for query 1: parse error" in caplog.text

Review Comment:
   The repo guidelines ask not to assert on `caplog.text`, since those 
string-matching APIs are slated for removal. Structured checks are preferred 
(`"event" in caplog` / `{"event": ..., ...} in caplog`). The same pattern 
appears at L219 here and at `test_manager.py:387-388`. Optional, since the 
existing tests in this file already do it.



##########
providers/openlineage/src/airflow/providers/openlineage/sqlparser.py:
##########
@@ -489,10 +489,8 @@ def get_openlineage_facets_with_sql(
         connection = hook.get_connection(conn_id)
 
     if database_info is None:
-        try:
-            database_info = hook.get_openlineage_database_info(connection)
-        except AttributeError:
-            database_info = None
+        get_database_info = getattr(hook, "get_openlineage_database_info", 
None)
+        database_info = get_database_info(connection) if get_database_info is 
not None else None

Review Comment:
   Nothing I know of triggers this today, but the same bug class this PR fixes 
remains a few lines below and in the operator. `DbApiHook` defines 
`get_openlineage_database_dialect`, `get_openlineage_default_schema` and 
`get_openlineage_database_specific_lineage` with defaults, so for SQL hooks 
these `except AttributeError` blocks never mean "method missing". They only 
ever catch an `AttributeError` raised *inside* the method, log it at debug and 
return no lineage:
   
   - `sqlparser.py:499-505`: dialect and default schema, logged as "failed to 
get database dialect"
   - `operators/sql.py:307-313`: the same pair in `SQLExecuteQueryOperator`
   - `operators/sql.py:337-340`: `get_openlineage_database_specific_lineage`, 
logged as "has no database specific lineage provided"
   
   For example, `SnowflakeHook.get_openlineage_default_schema` calls 
`_get_conn_params()`. A broken import there would silently produce empty 
lineage again. Worth switching these to the same `getattr` pattern in this PR 
for consistency? Optional.



##########
providers/common/sql/tests/unit/common/sql/operators/test_sql_execute.py:
##########
@@ -368,6 +368,40 @@ def get_db_hook(self):
     )
 
 
+def test_openlineage_hook_without_database_info_method_returns_empty_lineage():
+    from airflow.providers.openlineage.extractors import OperatorLineage
+
+    hook = mock.MagicMock(spec=["get_connection", "conn_name_attr", "conn_id"])
+    hook.conn_name_attr = "conn_id"
+    hook.conn_id = "sql_default"
+
+    class SQLExecuteQueryOperatorForTest(SQLExecuteQueryOperator):
+        def get_db_hook(self):
+            return hook
+
+    op = SQLExecuteQueryOperatorForTest(task_id=TASK_ID, sql="SELECT 1;")
+
+    assert op.get_openlineage_facets_on_start() == OperatorLineage()
+
+
+def test_openlineage_attribute_error_from_database_info_is_not_swallowed():
+    hook = mock.MagicMock()

Review Comment:
   Small one: the guidelines ask for `spec`/`autospec` on mocks. 
`mock.MagicMock(spec=DbApiHook)` would work here, in 
`test_sqlparser.py::test_attribute_error_from_database_info_is_not_swallowed` 
and in `test_sql_hook_lineage.py:208`. It also means a typo in the method name 
can't make the test pass by accident.



##########
providers/common/sql/src/airflow/providers/common/sql/operators/sql.py:
##########
@@ -297,11 +297,8 @@ def get_openlineage_facets_on_start(self) -> 
OperatorLineage | None:
             use_external_connection = True
 
         connection = hook.get_connection(getattr(hook, hook.conn_name_attr))
-        try:
-            database_info = hook.get_openlineage_database_info(connection)
-        except AttributeError:
-            self.log.debug("%s has no database info provided", hook)
-            database_info = None
+        get_database_info = getattr(hook, "get_openlineage_database_info", 
None)
+        database_info = get_database_info(connection) if get_database_info is 
not None else None

Review Comment:
   Before this PR, an `AttributeError` inside `get_openlineage_database_info` 
turned into `database_info = None`, and the callers kept whatever lineage they 
could build without it. Now the exception leaves the operator's facets method 
entirely. `DefaultExtractor._get_openlineage_facets` catches it and returns 
`None`, so **all** operator lineage for that task is lost, not just the 
SQL-parsing part:
   
   - `SQLExecuteQueryOperator.get_openlineage_facets_on_complete` calls 
`get_openlineage_facets_on_start()` (L335) without a guard. It no longer 
reaches `get_openlineage_database_specific_lineage` (query IDs, external query 
facets) or `_attach_check_facets` for check operators. The new test asserts 
this (`pytest.raises` on `on_complete`).
   - `MySQLToGCSOperator`, `PostgresToGCSOperator`, `OracleToGCSOperator`, 
`MSSQLToGCSOperator`, `TrinoToGCSOperator`, `CloudSQLExecuteQueryOperator` and 
`SpannerQueryDatabaseInstanceOperator` all call 
`get_openlineage_facets_with_sql`. Some of them, like 
`mysql_to_gcs.py:155-170`, have a deliberate fallback: when SQL parsing returns 
`None`, they still emit the GCS output datasets and the SQL facet. Now that 
fallback never runs, so those tasks lose their GCS outputs too.
   
   Other exception types already propagated this way, so this makes 
`AttributeError` consistent with them. Is losing the non-SQL lineage the 
intended tradeoff? The goal is a visible warning. You could get that while 
keeping partial lineage by catching `Exception` at these two call sites, 
logging at WARNING and falling back to `None`, which is what 
`sql_hook_lineage._get_database_info` already does.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to