Fixed infinite loop in pq_get_last_result after COPY

There will be an error downstream but we have to get out of this
function first.

Close #781
This commit is contained in:
Daniele Varrazzo 2018-10-10 23:23:56 +01:00
parent c442b3ec46
commit c314512115
4 changed files with 23 additions and 5 deletions

3
NEWS
View File

@ -33,7 +33,8 @@ What's new in psycopg 2.7.6
- Close named cursors if exist, even if `~cursor.execute()` wasn't called - Close named cursors if exist, even if `~cursor.execute()` wasn't called
(:ticket:`#746`). (:ticket:`#746`).
- Fixed building on modern FreeBSD versions with Python 3.7 (:ticket:`#755`) - Fixed building on modern FreeBSD versions with Python 3.7 (:ticket:`#755`).
- Fixed hang trying to :sql:`COPY` via `~cursor.execute()` (:ticket:`#781`).
What's new in psycopg 2.7.5 What's new in psycopg 2.7.5

View File

@ -1106,12 +1106,13 @@ pq_send_query(connectionObject *conn, const char *query)
* The function will block only if a command is active and the * The function will block only if a command is active and the
* necessary response data has not yet been read by PQconsumeInput. * necessary response data has not yet been read by PQconsumeInput.
* *
* The result should be disposed using PQclear() * The result should be disposed of using PQclear()
*/ */
PGresult * PGresult *
pq_get_last_result(connectionObject *conn) pq_get_last_result(connectionObject *conn)
{ {
PGresult *result = NULL, *res; PGresult *result = NULL, *res;
ExecStatusType status;
/* Read until PQgetResult gives a NULL */ /* Read until PQgetResult gives a NULL */
while (NULL != (res = PQgetResult(conn->pgconn))) { while (NULL != (res = PQgetResult(conn->pgconn))) {
@ -1124,11 +1125,15 @@ pq_get_last_result(connectionObject *conn)
PQclear(result); PQclear(result);
} }
result = res; result = res;
status = PQresultStatus(result);
Dprintf("pq_get_last_result: got result %s", PQresStatus(status));
/* After entering copy both mode, libpq will make a phony /* After entering copy mode, libpq will make a phony
* PGresult for us every time we query for it, so we need to * PGresult for us every time we query for it, so we need to
* break out of this endless loop. */ * break out of this endless loop. */
if (PQresultStatus(result) == PGRES_COPY_BOTH) { if (status == PGRES_COPY_BOTH
|| status == PGRES_COPY_OUT
|| status == PGRES_COPY_IN) {
break; break;
} }
} }

View File

@ -450,6 +450,12 @@ class AsyncTests(ConnectingTestCase):
else: else:
self.fail("no exception raised") self.fail("no exception raised")
@skip_before_postgres(8, 2)
def test_copy_no_hang(self):
cur = self.conn.cursor()
cur.execute("copy (select 1) to stdout")
self.assertRaises(psycopg2.ProgrammingError, self.wait, self.conn)
def test_suite(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -27,7 +27,7 @@ import psycopg2
import psycopg2.extensions import psycopg2.extensions
import psycopg2.extras import psycopg2.extras
from .testutils import ConnectingTestCase, slow from .testutils import ConnectingTestCase, skip_before_postgres, slow
class ConnectionStub(object): class ConnectionStub(object):
@ -111,6 +111,12 @@ class GreenTestCase(ConnectingTestCase):
curs.execute("select 1") curs.execute("select 1")
self.assertEqual(curs.fetchone()[0], 1) self.assertEqual(curs.fetchone()[0], 1)
@skip_before_postgres(8, 2)
def test_copy_no_hang(self):
cur = self.conn.cursor()
self.assertRaises(psycopg2.ProgrammingError,
cur.execute, "copy (select 1) to stdout")
class CallbackErrorTestCase(ConnectingTestCase): class CallbackErrorTestCase(ConnectingTestCase):
def setUp(self): def setUp(self):