diff --git a/NEWS b/NEWS index dd595968..b044c3e9 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,15 @@ +What's new in psycopg 2.4.3 +--------------------------- + + - Fixed segfault in case of transaction started with connection lost + (and possibly other events). + - Rollback connections in transaction or in error before putting them + back into a pool. Also discard broken connections (ticket #62). + - Lazy import of the slow uuid module, thanks to Marko Kreen. + - Fixed NamedTupleCursor.executemany() (ticket #65). + - Fixed --static-libpq setup option (ticket #64). + + What's new in psycopg 2.4.2 --------------------------- diff --git a/doc/src/pool.rst b/doc/src/pool.rst index 9d588e16..de8132f5 100644 --- a/doc/src/pool.rst +++ b/doc/src/pool.rst @@ -26,10 +26,12 @@ directly into the client application. Get a free connection and assign it to *key* if not `!None`. - .. method:: putconn(conn, key=None) + .. method:: putconn(conn, key=None, close=False) Put away a connection. + If *close* is `!True`, discard the connection from the pool. + .. method:: closeall Close all the connections handled by the pool. diff --git a/lib/extras.py b/lib/extras.py index a36faa8e..0d7bc981 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -275,7 +275,7 @@ class NamedTupleCursor(_cursor): def executemany(self, query, vars): self.Record = None - return _cursor.executemany(self, vars) + return _cursor.executemany(self, query, vars) def callproc(self, procname, vars=None): self.Record = None diff --git a/lib/pool.py b/lib/pool.py index 8a8fa539..da8d3057 100644 --- a/lib/pool.py +++ b/lib/pool.py @@ -25,6 +25,7 @@ This module implements thread-safe (and not) connection pools. # License for more details. import psycopg2 +import psycopg2.extensions as _ext try: import logging @@ -115,13 +116,27 @@ class AbstractConnectionPool(object): def _putconn(self, conn, key=None, close=False): """Put away a connection.""" if self.closed: raise PoolError("connection pool is closed") - if key is None: key = self._rused[id(conn)] + if key is None: key = self._rused.get(id(conn)) if not key: raise PoolError("trying to put unkeyed connection") if len(self._pool) < self.minconn and not close: - self._pool.append(conn) + # Return the connection into a consistent state before putting + # it back into the pool + if not conn.closed: + status = conn.get_transaction_status() + if status == _ext.TRANSACTION_STATUS_UNKNOWN: + # server connection lost + conn.close() + elif status != _ext.TRANSACTION_STATUS_IDLE: + # connection in error or in transaction + conn.rollback() + self._pool.append(conn) + else: + # regular idle connection + self._pool.append(conn) + # If the connection is closed, we just discard it. else: conn.close() diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index cee5ce4c..1f0d5da9 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -344,11 +344,13 @@ pq_execute_command_locked(connectionObject *conn, const char *query, } if (*pgres == NULL) { Dprintf("pq_execute_command_locked: PQexec returned NULL"); + PyEval_RestoreThread(*tstate); if (!PyErr_Occurred()) { const char *msg; msg = PQerrorMessage(conn->pgconn); if (msg && *msg) { *error = strdup(msg); } } + *tstate = PyEval_SaveThread(); goto cleanup; } @@ -635,11 +637,13 @@ pq_get_guc_locked( if (*pgres == NULL) { Dprintf("pq_get_guc_locked: PQexec returned NULL"); + PyEval_RestoreThread(*tstate); if (!PyErr_Occurred()) { const char *msg; msg = PQerrorMessage(conn->pgconn); if (msg && *msg) { *error = strdup(msg); } } + *tstate = PyEval_SaveThread(); goto cleanup; } if (PQresultStatus(*pgres) != PGRES_TUPLES_OK) { diff --git a/setup.py b/setup.py index e03876a8..161efaef 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ except ImportError: # Take a look at http://www.python.org/dev/peps/pep-0386/ # for a consistent versioning pattern. -PSYCOPG_VERSION = '2.4.2' +PSYCOPG_VERSION = '2.4.3.dev0' version_flags = ['dt', 'dec'] @@ -362,7 +362,7 @@ class psycopg_build_ext(build_ext): self.include_dirs.append(".") if self.static_libpq: - if not hasattr(self, 'link_objects'): + if not getattr(self, 'link_objects', None): self.link_objects = [] self.link_objects.append( os.path.join(pg_config_helper.query("libdir"), "libpq.a")) diff --git a/tests/extras_dictcursor.py b/tests/extras_dictcursor.py index 898c16c3..c74075b0 100755 --- a/tests/extras_dictcursor.py +++ b/tests/extras_dictcursor.py @@ -171,6 +171,17 @@ class NamedTupleCursorTest(unittest.TestCase): self.assertEqual(res[2].i, 3) self.assertEqual(res[2].s, 'baz') + @skip_if_no_namedtuple + def test_executemany(self): + curs = self.conn.cursor() + curs.executemany("delete from nttest where i = %s", + [(1,), (2,)]) + curs.execute("select * from nttest order by 1") + res = curs.fetchall() + self.assertEqual(1, len(res)) + self.assertEqual(res[0].i, 3) + self.assertEqual(res[0].s, 'baz') + @skip_if_no_namedtuple def test_iter(self): curs = self.conn.cursor()