kaxil commented on code in PR #75967:
URL: https://github.com/apache/airflow/pull/75967#discussion_r4501825210


##########
providers/vertica/src/airflow/providers/vertica/hooks/vertica.py:
##########
@@ -205,8 +234,20 @@ def run(
         """
         Overwrite the common sql run.
 
-        Will automatically replace fetch_all_handler by 
vertica_fetch_all_handler.
+        vertica-python only reports an error raised by a statement once its 
result set is reached, so
+        every result set is consumed after each executed statement, whatever 
the handler is (including
+        when no handler is passed). Otherwise a failing statement in a 
multi-statement query could be
+        silently ignored.
         """
-        if handler == fetch_all_handler:
+        if handler is None:
+            DbApiHook.run(
+                self, sql, autocommit, parameters, _drain_result_sets, 
split_statements, return_last
+            )
+            # Without a handler, DbApiHook.run does not collect descriptions; 
keep that behaviour.
+            self.descriptions = []
+            return None
+        if handler is fetch_all_handler:

Review Comment:
   `vertica_fetch_all_handler` is now the same as 
`_with_result_set_drain(fetch_all_handler)`, so could this branch go and every 
handler get wrapped, keeping `vertica_fetch_all_handler` only as a public name? 
The class docstring that says the hook uses a customized `fetch_all_handler` is 
also out of date now that every handler is drained.



##########
providers/vertica/src/airflow/providers/vertica/hooks/vertica.py:
##########
@@ -29,13 +30,47 @@
     from sqlalchemy.engine import URL
 
 
+def _drain_result_sets(cursor) -> None:
+    """
+    Advance the cursor through all remaining result sets.
+
+    vertica-python streams results: an error raised by a statement other than 
the first one in a
+    multi-statement query is only reported once the client reaches its result 
set. ``nextset()``
+    discards the remaining rows of the current set (without loading them in 
memory) and raises
+    the server error if the next set is an error.
+
+    ``nextset()`` also overwrites ``cursor.description`` with the columns of 
each next result set,
+    while ``DbApiHook.run`` reads it after the handler returns. The 
description of the result set
+    the handler consumed is restored, so that ``hook.descriptions`` matches 
the returned rows.
+
+    See https://github.com/apache/airflow/issues/32993 and
+    https://github.com/vertica/vertica-python/issues/197.
+    """
+    description = cursor.description
+    while cursor.nextset():

Review Comment:
   `nextset()` doesn't raise for an error that arrives after a later statement 
has started returning rows. `flush_to_end_of_result()` skips every message up 
to `ReadyForQuery`, `ErrorResponse` included, and vertica-python's own 
`test_nextset_with_error_2` asserts exactly that for `SELECT 1; SELECT a/b FROM 
t; SELECT 2` against a real server. The old `vertica_fetch_all_handler` caught 
it because it called `fetchone()` after each `nextset()`, so with the default 
handler that SQL now goes green where it used to fail, which is the case this 
PR sets out to close. Could the drain read each set through `fetchone()` before 
moving on (`while cursor.fetchone() is not None: pass` inside the `nextset()` 
loop)? That also avoids a hang on vertica-python 1.3.0 to 1.3.4, still allowed 
by the `>=1.3.0` floor, where `flush_to_end_of_result()` doesn't stop at 
`ReadyForQuery`. A test where `nextset()` returns `True` and the next 
`fetchone()` raises would pin it (the `MagicMock` cursors in the new tests 
 would need `fetchone` to return `None`).



##########
providers/vertica/tests/unit/vertica/hooks/test_vertica_sql.py:
##########
@@ -149,10 +149,11 @@ def test_vertica_run_queries(
 ):
     if split_statements:
         mock_cursor.fetchall.side_effect = cursor_results
-        mock_cursor.nextset.side_effect = [True] * (len(cursor_results) - 1) + 
[False]
     else:
         mock_cursor.fetchall.return_value = cursor_results
-        mock_cursor.nextset.side_effect = lambda: False
+    # Each statement executed separately produces a single result set.

Review Comment:
   This doesn't hold for the "Multiple queries, split_statements=False" case, 
which sends both statements in one `execute()` and gets two result sets on real 
Vertica. Could that case mock two sets, so the parametrized test goes through 
the drain too?



-- 
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