diff --git a/airflow/providers/odbc/hooks/odbc.py b/airflow/providers/odbc/hooks/odbc.py index 80ea09c9e8ed4..7b0c4d11e9e47 100644 --- a/airflow/providers/odbc/hooks/odbc.py +++ b/airflow/providers/odbc/hooks/odbc.py @@ -233,6 +233,8 @@ def _make_common_data_structure(self, result: Sequence[Row] | Row) -> list[tuple # Below ignored lines respect NamedTuple docstring, but mypy do not support dynamically # instantiated typed Namedtuple, and will never do: https://github.com/python/mypy/issues/848 field_names: list[tuple[str, type]] | None = None + if not result: + return [] if isinstance(result, Sequence): field_names = [col[:2] for col in result[0].cursor_description] row_object = NamedTuple("Row", field_names) # type: ignore[misc] diff --git a/tests/providers/odbc/hooks/test_odbc.py b/tests/providers/odbc/hooks/test_odbc.py index 391a766df9d70..64683d21e1971 100644 --- a/tests/providers/odbc/hooks/test_odbc.py +++ b/tests/providers/odbc/hooks/test_odbc.py @@ -329,6 +329,23 @@ def mock_handler(*_): result = hook.run("SQL", handler=mock_handler) assert hook_result == result + def test_query_return_serializable_result_empty(self, pyodbc_row_mock, monkeypatch, pyodbc_instancecheck): + """ + Simulate a cursor.fetchall which returns an iterable of pyodbc.Row object, and check if this iterable + get converted into a list of tuples. + """ + pyodbc_result = [] + hook_result = [] + + def mock_handler(*_): + return pyodbc_result + + hook = self.get_hook() + with monkeypatch.context() as patcher: + patcher.setattr("pyodbc.Row", pyodbc_instancecheck) + result = hook.run("SQL", handler=mock_handler) + assert hook_result == result + def test_query_return_serializable_result_with_fetchone( self, pyodbc_row_mock, monkeypatch, pyodbc_instancecheck ):