diff --git a/NEWS b/NEWS index 60c7d4b3..0037c3bb 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ What's new in psycopg 2.4.1 - Correctly detect an empty query sent to the backend (ticket #46). - Fixed a SystemError clobbering libpq errors raised without SQLSTATE. Bug vivisectioned by Eric Snow. + - Fixed interaction between NamedTuple and server-side cursors. - Allow to specify --static-libpq on setup.py command line instead of just in 'setup.cfg'. Patch provided by Matthew Ryan (ticket #48). diff --git a/lib/extras.py b/lib/extras.py index dcbd65ed..1a4b730f 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -304,7 +304,14 @@ class NamedTupleCursor(_cursor): return [nt(*t) for t in ts] def __iter__(self): - return iter(self.fetchall()) + # Invoking _cursor.__iter__(self) goes to infinite recursion, + # so we do pagination by hand + while 1: + recs = self.fetchmany(self.itersize) + if not recs: + return + for rec in recs: + yield rec try: from collections import namedtuple diff --git a/tests/extras_dictcursor.py b/tests/extras_dictcursor.py index a92968b1..494eca88 100755 --- a/tests/extras_dictcursor.py +++ b/tests/extras_dictcursor.py @@ -14,9 +14,11 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. +import time +from datetime import timedelta import psycopg2 import psycopg2.extras -from testutils import unittest, skip_if_no_namedtuple +from testutils import unittest, skip_before_postgres, skip_if_no_namedtuple from testconfig import dsn @@ -293,6 +295,21 @@ class NamedTupleCursorTest(unittest.TestCase): recs = curs.fetchall() self.assertEqual(recs[0].i, 42) + @skip_if_no_namedtuple + @skip_before_postgres(8, 0) + def test_not_greedy(self): + curs = self.conn.cursor('tmp') + curs.itersize = 2 + curs.execute("""select clock_timestamp() as ts from generate_series(1,3)""") + recs = [] + for t in curs: + time.sleep(0.01) + recs.append(t) + + # check that the dataset was not fetched in a single gulp + self.assert_(recs[1].ts - recs[0].ts < timedelta(seconds=0.005)) + self.assert_(recs[2].ts - recs[1].ts > timedelta(seconds=0.0099)) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__)