diff --git a/NEWS b/NEWS index 0b9ebe61..6c5a79a9 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ What's new in psycopg 2.6.2 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Report the server response status on errors (such as :ticket:`#281`). +- Raise NotSupportedError on unhandled server response status + (:ticket:`#352`). What's new in psycopg 2.6.1 diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index 5a128382..6e788058 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -1597,11 +1597,26 @@ pq_fetch(cursorObject *curs, int no_result) ex = -1; break; - default: - Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn); + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + Dprintf("pq_fetch: uh-oh, something FAILED: status = %d pgconn = %p", + status, curs->conn); pq_raise(curs->conn, curs, NULL); ex = -1; break; + + default: + /* PGRES_COPY_BOTH, PGRES_SINGLE_TUPLE, future statuses */ + Dprintf("pq_fetch: got unsupported result: status = %d pgconn = %p", + status, curs->conn); + PyErr_Format(NotSupportedError, + "got server response with unsupported status %s", + PQresStatus(curs->pgres == NULL ? + PQstatus(curs->conn->pgconn) : PQresultStatus(curs->pgres))); + CLEARPGRES(curs->pgres); + ex = -1; + break; } /* error checking, close the connection if necessary (some critical errors diff --git a/tests/test_connection.py b/tests/test_connection.py index 340693e2..8eca900c 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -26,6 +26,7 @@ import os import time import threading from operator import attrgetter +from StringIO import StringIO import psycopg2 import psycopg2.errorcodes @@ -1067,6 +1068,17 @@ class AutocommitTests(ConnectingTestCase): self.assertEqual(cur.fetchone()[0], 'on') +class ReplicationTest(ConnectingTestCase): + @skip_before_postgres(9, 0) + def test_replication_not_supported(self): + conn = self.repl_connect() + if conn is None: return + cur = conn.cursor() + f = StringIO() + self.assertRaises(psycopg2.NotSupportedError, + cur.copy_expert, "START_REPLICATION 0/0", f) + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/testconfig.py b/tests/testconfig.py index f83ded84..0f995fbf 100644 --- a/tests/testconfig.py +++ b/tests/testconfig.py @@ -7,6 +7,8 @@ dbhost = os.environ.get('PSYCOPG2_TESTDB_HOST', None) dbport = os.environ.get('PSYCOPG2_TESTDB_PORT', None) dbuser = os.environ.get('PSYCOPG2_TESTDB_USER', None) dbpass = os.environ.get('PSYCOPG2_TESTDB_PASSWORD', None) +repl_dsn = os.environ.get('PSYCOPG2_TEST_REPL_DSN', + "dbname=psycopg2_test replication=1") # Check if we want to test psycopg's green path. green = os.environ.get('PSYCOPG2_TEST_GREEN', None) diff --git a/tests/testutils.py b/tests/testutils.py index 6a784320..db82dcd0 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -28,7 +28,7 @@ import os import platform import sys from functools import wraps -from testconfig import dsn +from testconfig import dsn, repl_dsn try: import unittest2 @@ -103,11 +103,35 @@ class ConnectingTestCase(unittest.TestCase): "%s (did you remember calling ConnectingTestCase.setUp()?)" % e) + if 'dsn' in kwargs: + conninfo = kwargs.pop('dsn') + else: + conninfo = dsn import psycopg2 - conn = psycopg2.connect(dsn, **kwargs) + conn = psycopg2.connect(conninfo, **kwargs) self._conns.append(conn) return conn + def repl_connect(self, **kwargs): + """Return a connection set up for replication + + The connection is on "PSYCOPG2_TEST_REPL_DSN" unless overridden by + a *dsn* kwarg. + + Should raise a skip test if not available, but guard for None on + old Python versions. + """ + if 'dsn' not in kwargs: + kwargs['dsn'] = repl_dsn + import psycopg2 + try: + conn = self.connect(**kwargs) + except psycopg2.OperationalError, e: + return self.skipTest("replication db not configured: %s" % e) + + conn.autocommit = True + return conn + def _get_conn(self): if not hasattr(self, '_the_conn'): self._the_conn = self.connect() @@ -351,4 +375,3 @@ class py3_raises_typeerror(object): if sys.version_info[0] >= 3: assert type is TypeError return True -