From c6f776de9f3884c041585a46f7db2993ab4db587 Mon Sep 17 00:00:00 2001 From: RoelW Date: Tue, 9 Jan 2024 14:42:59 +0100 Subject: [PATCH 1/5] feat: return empty list on querying empty table --- airflow/providers/odbc/hooks/odbc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/airflow/providers/odbc/hooks/odbc.py b/airflow/providers/odbc/hooks/odbc.py index 80ea09c9e8ed4..742fc3fd72c13 100644 --- a/airflow/providers/odbc/hooks/odbc.py +++ b/airflow/providers/odbc/hooks/odbc.py @@ -234,6 +234,8 @@ def _make_common_data_structure(self, result: Sequence[Row] | Row) -> list[tuple # instantiated typed Namedtuple, and will never do: https://github.com/python/mypy/issues/848 field_names: list[tuple[str, type]] | None = None if isinstance(result, Sequence): + if not result: + return [] field_names = [col[:2] for col in result[0].cursor_description] row_object = NamedTuple("Row", field_names) # type: ignore[misc] return cast(List[tuple], [row_object(*row) for row in result]) From 908d168bcd446a26debca6aef65f5ded73358a35 Mon Sep 17 00:00:00 2001 From: RoelW Date: Tue, 9 Jan 2024 16:44:35 +0100 Subject: [PATCH 2/5] feat: return empty list on querying empty table --- airflow/providers/databricks/hooks/databricks_sql.py | 2 ++ airflow/providers/odbc/hooks/odbc.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/airflow/providers/databricks/hooks/databricks_sql.py b/airflow/providers/databricks/hooks/databricks_sql.py index d18d804b04d1f..9ada91d236a21 100644 --- a/airflow/providers/databricks/hooks/databricks_sql.py +++ b/airflow/providers/databricks/hooks/databricks_sql.py @@ -278,6 +278,8 @@ def _make_common_data_structure(self, result: Sequence[Row] | Row) -> list[tuple """Transform the databricks Row objects into namedtuple.""" # Below ignored lines respect namedtuple docstring, but mypy do not support dynamically # instantiated namedtuple, and will never do: https://github.com/python/mypy/issues/848 + if not result: + return [] if isinstance(result, list): rows: list[Row] = result rows_fields = rows[0].__fields__ diff --git a/airflow/providers/odbc/hooks/odbc.py b/airflow/providers/odbc/hooks/odbc.py index 742fc3fd72c13..7b0c4d11e9e47 100644 --- a/airflow/providers/odbc/hooks/odbc.py +++ b/airflow/providers/odbc/hooks/odbc.py @@ -233,9 +233,9 @@ 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): - if not result: - return [] field_names = [col[:2] for col in result[0].cursor_description] row_object = NamedTuple("Row", field_names) # type: ignore[misc] return cast(List[tuple], [row_object(*row) for row in result]) From a69cd1b614e510d5ebba95e46c9c04edbf1494c3 Mon Sep 17 00:00:00 2001 From: RoelW Date: Tue, 9 Jan 2024 16:44:55 +0100 Subject: [PATCH 3/5] feat: added test for empty result --- tests/providers/odbc/hooks/test_odbc.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/providers/odbc/hooks/test_odbc.py b/tests/providers/odbc/hooks/test_odbc.py index 391a766df9d70..387c2fb692416 100644 --- a/tests/providers/odbc/hooks/test_odbc.py +++ b/tests/providers/odbc/hooks/test_odbc.py @@ -329,6 +329,25 @@ 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 ): From db2a5d4ad6dcabe159e8e162e852032d2caebe80 Mon Sep 17 00:00:00 2001 From: RoelW Date: Wed, 10 Jan 2024 10:38:35 +0100 Subject: [PATCH 4/5] feat: unsure of an empty return on databricks --- airflow/providers/databricks/hooks/databricks_sql.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/airflow/providers/databricks/hooks/databricks_sql.py b/airflow/providers/databricks/hooks/databricks_sql.py index 9ada91d236a21..d18d804b04d1f 100644 --- a/airflow/providers/databricks/hooks/databricks_sql.py +++ b/airflow/providers/databricks/hooks/databricks_sql.py @@ -278,8 +278,6 @@ def _make_common_data_structure(self, result: Sequence[Row] | Row) -> list[tuple """Transform the databricks Row objects into namedtuple.""" # Below ignored lines respect namedtuple docstring, but mypy do not support dynamically # instantiated namedtuple, and will never do: https://github.com/python/mypy/issues/848 - if not result: - return [] if isinstance(result, list): rows: list[Row] = result rows_fields = rows[0].__fields__ From 138eaef3e1b37ac2bdd7bd595c097f2cea1c12fb Mon Sep 17 00:00:00 2001 From: Roel <43408511+roel-w@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:50:46 +0100 Subject: [PATCH 5/5] Update tests/providers/odbc/hooks/test_odbc.py reformatting Co-authored-by: Wei Lee --- tests/providers/odbc/hooks/test_odbc.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/providers/odbc/hooks/test_odbc.py b/tests/providers/odbc/hooks/test_odbc.py index 387c2fb692416..64683d21e1971 100644 --- a/tests/providers/odbc/hooks/test_odbc.py +++ b/tests/providers/odbc/hooks/test_odbc.py @@ -329,9 +329,7 @@ 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 - ): + 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.